@buoy-gg/zustand 7.0.29 → 7.0.31
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/zustand/components/ZustandActionButton.js +3 -1
- package/lib/commonjs/zustand/components/ZustandDetailViewToggle.js +7 -0
- package/lib/commonjs/zustand/components/ZustandEventFilterView.js +19 -4
- package/lib/commonjs/zustand/components/ZustandModal.js +173 -32
- package/lib/commonjs/zustand/components/ZustandStateChangeItem.js +36 -37
- package/lib/commonjs/zustand/components/ZustandStateDetailContent.js +22 -5
- package/lib/commonjs/zustand/components/ZustandStateInfoView.js +30 -7
- package/lib/commonjs/zustand/components/ZustandStoreBrowser.js +23 -126
- package/lib/commonjs/zustand/components/ZustandStoreDetailContent.js +471 -0
- package/lib/commonjs/zustand/components/ZustandStoreEditorScreen.js +393 -0
- package/lib/commonjs/zustand/sync/zustandSyncAdapter.js +54 -0
- package/lib/commonjs/zustand/utils/buoyZustandMiddleware.js +71 -7
- package/lib/commonjs/zustand/utils/editableState.js +139 -0
- package/lib/commonjs/zustand/utils/snapshotProvider.js +10 -37
- package/lib/commonjs/zustand/utils/testIDs.js +82 -0
- package/lib/commonjs/zustand/utils/zustandStateStore.js +16 -3
- package/lib/module/zustand/components/ZustandActionButton.js +3 -1
- package/lib/module/zustand/components/ZustandDetailViewToggle.js +7 -0
- package/lib/module/zustand/components/ZustandEventFilterView.js +19 -4
- package/lib/module/zustand/components/ZustandModal.js +173 -32
- package/lib/module/zustand/components/ZustandStateChangeItem.js +36 -37
- package/lib/module/zustand/components/ZustandStateDetailContent.js +22 -5
- package/lib/module/zustand/components/ZustandStateInfoView.js +30 -7
- package/lib/module/zustand/components/ZustandStoreBrowser.js +26 -129
- package/lib/module/zustand/components/ZustandStoreDetailContent.js +469 -0
- package/lib/module/zustand/components/ZustandStoreEditorScreen.js +389 -0
- package/lib/module/zustand/sync/zustandSyncAdapter.js +54 -0
- package/lib/module/zustand/utils/buoyZustandMiddleware.js +71 -7
- package/lib/module/zustand/utils/editableState.js +131 -0
- package/lib/module/zustand/utils/snapshotProvider.js +4 -30
- package/lib/module/zustand/utils/testIDs.js +74 -0
- package/lib/module/zustand/utils/zustandStateStore.js +16 -3
- package/lib/typescript/zustand/components/ZustandActionButton.d.ts +2 -1
- package/lib/typescript/zustand/components/ZustandActionButton.d.ts.map +1 -1
- package/lib/typescript/zustand/components/ZustandDetailViewToggle.d.ts.map +1 -1
- package/lib/typescript/zustand/components/ZustandEventFilterView.d.ts.map +1 -1
- package/lib/typescript/zustand/components/ZustandModal.d.ts.map +1 -1
- package/lib/typescript/zustand/components/ZustandStateChangeItem.d.ts +6 -0
- package/lib/typescript/zustand/components/ZustandStateChangeItem.d.ts.map +1 -1
- package/lib/typescript/zustand/components/ZustandStateDetailContent.d.ts.map +1 -1
- package/lib/typescript/zustand/components/ZustandStateInfoView.d.ts.map +1 -1
- package/lib/typescript/zustand/components/ZustandStoreBrowser.d.ts +7 -9
- package/lib/typescript/zustand/components/ZustandStoreBrowser.d.ts.map +1 -1
- package/lib/typescript/zustand/components/ZustandStoreDetailContent.d.ts +40 -0
- package/lib/typescript/zustand/components/ZustandStoreDetailContent.d.ts.map +1 -0
- package/lib/typescript/zustand/components/ZustandStoreEditorScreen.d.ts +42 -0
- package/lib/typescript/zustand/components/ZustandStoreEditorScreen.d.ts.map +1 -0
- package/lib/typescript/zustand/sync/zustandSyncAdapter.d.ts +35 -0
- package/lib/typescript/zustand/sync/zustandSyncAdapter.d.ts.map +1 -1
- package/lib/typescript/zustand/types/index.d.ts +13 -0
- package/lib/typescript/zustand/types/index.d.ts.map +1 -1
- package/lib/typescript/zustand/utils/buoyZustandMiddleware.d.ts +31 -3
- package/lib/typescript/zustand/utils/buoyZustandMiddleware.d.ts.map +1 -1
- package/lib/typescript/zustand/utils/editableState.d.ts +65 -0
- package/lib/typescript/zustand/utils/editableState.d.ts.map +1 -0
- package/lib/typescript/zustand/utils/testIDs.d.ts +54 -0
- package/lib/typescript/zustand/utils/testIDs.d.ts.map +1 -0
- package/lib/typescript/zustand/utils/zustandStateStore.d.ts +1 -0
- package/lib/typescript/zustand/utils/zustandStateStore.d.ts.map +1 -1
- package/package.json +3 -3
|
@@ -44,7 +44,8 @@ function ZustandActionButton({
|
|
|
44
44
|
onPress,
|
|
45
45
|
text,
|
|
46
46
|
type,
|
|
47
|
-
disabled = false
|
|
47
|
+
disabled = false,
|
|
48
|
+
testID
|
|
48
49
|
}) {
|
|
49
50
|
const config = buttonConfigs[type];
|
|
50
51
|
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
|
|
@@ -56,6 +57,7 @@ function ZustandActionButton({
|
|
|
56
57
|
opacity: disabled ? 0.5 : 1
|
|
57
58
|
}],
|
|
58
59
|
activeOpacity: 0.7,
|
|
60
|
+
testID: testID,
|
|
59
61
|
accessibilityRole: "button",
|
|
60
62
|
accessibilityLabel: text,
|
|
61
63
|
accessibilityState: {
|
|
@@ -53,6 +53,13 @@ const ZustandDetailViewToggle = exports.ZustandDetailViewToggle = /*#__PURE__*/(
|
|
|
53
53
|
onPress: () => !isDisabled && onViewChange(config.key),
|
|
54
54
|
activeOpacity: isDisabled ? 1 : 0.8,
|
|
55
55
|
disabled: isDisabled,
|
|
56
|
+
testID: `buoy-zustand-view-${config.key}`,
|
|
57
|
+
accessibilityRole: "button",
|
|
58
|
+
accessibilityLabel: config.label,
|
|
59
|
+
accessibilityState: {
|
|
60
|
+
disabled: isDisabled,
|
|
61
|
+
selected: isActive
|
|
62
|
+
},
|
|
56
63
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
57
64
|
style: styles.cardContent,
|
|
58
65
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(IconComponent, {
|
|
@@ -8,6 +8,7 @@ var _react = require("react");
|
|
|
8
8
|
var _reactNative = require("react-native");
|
|
9
9
|
var _sharedUi = require("@buoy-gg/shared-ui");
|
|
10
10
|
var _zustandStateStore = require("../utils/zustandStateStore");
|
|
11
|
+
var _testIDs = require("../utils/testIDs");
|
|
11
12
|
var _jsxRuntime = require("react/jsx-runtime");
|
|
12
13
|
/** Returns the store color for a given pattern if it exactly matches a registered store name */
|
|
13
14
|
function getStoreColorForPattern(pattern, stores) {
|
|
@@ -39,6 +40,7 @@ function ZustandEventFilterView({
|
|
|
39
40
|
style: styles.container,
|
|
40
41
|
contentContainerStyle: styles.scrollContent,
|
|
41
42
|
showsVerticalScrollIndicator: false,
|
|
43
|
+
testID: `${_testIDs.ZUSTAND_TEST_ID}-filters-scroll`,
|
|
42
44
|
children: [stores.length > 0 && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
43
45
|
style: styles.section,
|
|
44
46
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_sharedUi.SectionHeader, {
|
|
@@ -46,7 +48,8 @@ function ZustandEventFilterView({
|
|
|
46
48
|
children: "REGISTERED STORES"
|
|
47
49
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.SectionHeader.Badge, {
|
|
48
50
|
count: stores.length,
|
|
49
|
-
color: _sharedUi.night.color.accent
|
|
51
|
+
color: _sharedUi.night.color.accent,
|
|
52
|
+
testID: `${_testIDs.ZUSTAND_TEST_ID}-filters-store-count`
|
|
50
53
|
})]
|
|
51
54
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ScrollView, {
|
|
52
55
|
style: styles.storeList,
|
|
@@ -60,6 +63,12 @@ function ZustandEventFilterView({
|
|
|
60
63
|
style: [styles.storeRow, filtered && styles.storeRowFiltered],
|
|
61
64
|
onPress: () => onAddPattern(store.name),
|
|
62
65
|
disabled: filtered,
|
|
66
|
+
testID: `${_testIDs.ZUSTAND_TEST_ID}-filters-store-${(0, _testIDs.slugForTestID)(store.name)}`,
|
|
67
|
+
accessibilityRole: "button",
|
|
68
|
+
accessibilityLabel: `Hide ${store.name}`,
|
|
69
|
+
accessibilityState: {
|
|
70
|
+
disabled: filtered
|
|
71
|
+
},
|
|
63
72
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
64
73
|
style: [styles.storeDot, {
|
|
65
74
|
backgroundColor: filtered ? _sharedUi.night.color.textTertiary : storeColor
|
|
@@ -93,14 +102,16 @@ function ZustandEventFilterView({
|
|
|
93
102
|
children: "STORE NAME FILTERS"
|
|
94
103
|
}), ignoredPatterns.size > 0 && /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.SectionHeader.Badge, {
|
|
95
104
|
count: ignoredPatterns.size,
|
|
96
|
-
color: _sharedUi.night.color.accent
|
|
105
|
+
color: _sharedUi.night.color.accent,
|
|
106
|
+
testID: `${_testIDs.ZUSTAND_TEST_ID}-filters-count`
|
|
97
107
|
})]
|
|
98
108
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
99
109
|
style: styles.addInputWrapper,
|
|
100
110
|
children: !showAddInput ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.AddFilterButton, {
|
|
101
111
|
onPress: () => setShowAddInput(true),
|
|
102
112
|
color: _sharedUi.night.color.accent,
|
|
103
|
-
label: "Add store name pattern"
|
|
113
|
+
label: "Add store name pattern",
|
|
114
|
+
testID: `${_testIDs.ZUSTAND_TEST_ID}-filters-add-open`
|
|
104
115
|
}) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.AddFilterInput, {
|
|
105
116
|
value: inputValue,
|
|
106
117
|
onChange: setInputValue,
|
|
@@ -110,7 +121,8 @@ function ZustandEventFilterView({
|
|
|
110
121
|
setInputValue("");
|
|
111
122
|
},
|
|
112
123
|
placeholder: "Enter store name pattern to hide...",
|
|
113
|
-
color: _sharedUi.night.color.accent
|
|
124
|
+
color: _sharedUi.night.color.accent,
|
|
125
|
+
testID: `${_testIDs.ZUSTAND_TEST_ID}-filters-add-input`
|
|
114
126
|
})
|
|
115
127
|
}), ignoredPatterns.size > 0 && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
116
128
|
style: styles.patternList,
|
|
@@ -120,6 +132,9 @@ function ZustandEventFilterView({
|
|
|
120
132
|
style: styles.patternRow,
|
|
121
133
|
onPress: () => onTogglePattern(pattern),
|
|
122
134
|
activeOpacity: 0.8,
|
|
135
|
+
testID: `${_testIDs.ZUSTAND_TEST_ID}-filters-chip-${(0, _testIDs.slugForTestID)(pattern)}`,
|
|
136
|
+
accessibilityRole: "button",
|
|
137
|
+
accessibilityLabel: `Remove filter ${pattern}`,
|
|
123
138
|
children: [storeColor ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
124
139
|
style: [styles.storeDot, {
|
|
125
140
|
backgroundColor: storeColor
|
|
@@ -13,7 +13,9 @@ var _useZustandStateChanges = require("../hooks/useZustandStateChanges");
|
|
|
13
13
|
var _ZustandStateChangeItem = require("./ZustandStateChangeItem");
|
|
14
14
|
var _ZustandStateDetailContent = require("./ZustandStateDetailContent");
|
|
15
15
|
var _ZustandStoreBrowser = require("./ZustandStoreBrowser");
|
|
16
|
+
var _ZustandStoreDetailContent = require("./ZustandStoreDetailContent");
|
|
16
17
|
var _ZustandEventFilterView = require("./ZustandEventFilterView");
|
|
18
|
+
var _testIDs = require("../utils/testIDs");
|
|
17
19
|
var _jsxRuntime = require("react/jsx-runtime");
|
|
18
20
|
/**
|
|
19
21
|
* Main Zustand DevTools modal
|
|
@@ -23,19 +25,32 @@ var _jsxRuntime = require("react/jsx-runtime");
|
|
|
23
25
|
* - Events tab: Live state change monitoring with diffs
|
|
24
26
|
*/
|
|
25
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Cell clipping, off in e2e bundles only.
|
|
30
|
+
*
|
|
31
|
+
* `removeClippedSubviews` unmounts the views of rows scrolled out of the
|
|
32
|
+
* window, which is what makes a long change list cheap — and which makes
|
|
33
|
+
* Detox report a perfectly rendered row as un-hittable. The env var is
|
|
34
|
+
* inlined at bundle time, so a bundler that does NOT inline it leaves the
|
|
35
|
+
* production behaviour exactly as it was.
|
|
36
|
+
*/
|
|
37
|
+
const CLIP_OFFSCREEN_CELLS = !(typeof process !== "undefined" && process.env?.EXPO_PUBLIC_E2E);
|
|
26
38
|
function EventsEmptyState({
|
|
27
39
|
isEnabled
|
|
28
40
|
}) {
|
|
29
41
|
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
30
42
|
style: styles.emptyState,
|
|
43
|
+
testID: `${_testIDs.ZUSTAND_TEST_ID}-empty`,
|
|
31
44
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Box, {
|
|
32
45
|
size: 32,
|
|
33
46
|
color: _sharedUi.night.color.textTertiary
|
|
34
47
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
35
48
|
style: styles.emptyTitle,
|
|
49
|
+
testID: `${_testIDs.ZUSTAND_TEST_ID}-empty-title`,
|
|
36
50
|
children: "No state changes"
|
|
37
51
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
38
52
|
style: styles.emptyText,
|
|
53
|
+
testID: `${_testIDs.ZUSTAND_TEST_ID}-empty-text`,
|
|
39
54
|
children: isEnabled ? "State changes will appear here as stores update." : "Enable capture to start recording state changes"
|
|
40
55
|
})]
|
|
41
56
|
});
|
|
@@ -59,6 +74,11 @@ function ZustandModal({
|
|
|
59
74
|
const [showProModal, setShowProModal] = (0, _react.useState)(false);
|
|
60
75
|
const [activeTab, setActiveTab] = (0, _react.useState)(initialTab);
|
|
61
76
|
const [selectedHistoryStore, setSelectedHistoryStore] = (0, _react.useState)(null);
|
|
77
|
+
/**
|
|
78
|
+
* Which store's DETAIL screen is open (live editor + details + tree —
|
|
79
|
+
* the React Query interaction). Null means the browser list.
|
|
80
|
+
*/
|
|
81
|
+
const [selectedStoreName, setSelectedStoreName] = (0, _react.useState)(null);
|
|
62
82
|
const [showFilters, setShowFilters] = (0, _react.useState)(false);
|
|
63
83
|
const [ignoredPatterns, setIgnoredPatterns] = (0, _react.useState)(new Set());
|
|
64
84
|
const {
|
|
@@ -84,19 +104,26 @@ function ZustandModal({
|
|
|
84
104
|
return stores.filter(s => !Array.from(ignoredPatterns).some(p => s.name.toLowerCase().includes(p.toLowerCase())));
|
|
85
105
|
}, [stores, ignoredPatterns]);
|
|
86
106
|
|
|
107
|
+
// Scope to a store BEFORE the tier cap, not after.
|
|
108
|
+
//
|
|
109
|
+
// The other way round, the cap was applied to the whole log and a store's
|
|
110
|
+
// history was whatever survived it — so a store with one change, made a
|
|
111
|
+
// while ago, opened an EMPTY history from a card that said "Changes: 1" and
|
|
112
|
+
// offered a "view history" link. The cap is a limit on how much of a list
|
|
113
|
+
// you can see; which list you are looking at is decided first.
|
|
114
|
+
const scopedChanges = (0, _react.useMemo)(() => {
|
|
115
|
+
if (!selectedHistoryStore) return displayedChanges;
|
|
116
|
+
return displayedChanges.filter(c => c.storeName === selectedHistoryStore);
|
|
117
|
+
}, [displayedChanges, selectedHistoryStore]);
|
|
118
|
+
|
|
87
119
|
// Pro is Infinity here, so slice() returns everything and the locked count
|
|
88
120
|
// floors at 0 — no tier branching needed at the call site.
|
|
89
|
-
const visibleChanges = (0, _react.useMemo)(() =>
|
|
90
|
-
const lockedChangeCount = (0, _react.useMemo)(() => Math.max(0,
|
|
121
|
+
const visibleChanges = (0, _react.useMemo)(() => scopedChanges.slice(0, changeLimit), [scopedChanges, changeLimit]);
|
|
122
|
+
const lockedChangeCount = (0, _react.useMemo)(() => Math.max(0, scopedChanges.length - changeLimit), [scopedChanges.length, changeLimit]);
|
|
91
123
|
const hasLockedChanges = lockedChangeCount > 0;
|
|
92
124
|
|
|
93
|
-
|
|
94
|
-
const activeChanges =
|
|
95
|
-
if (selectedHistoryStore) {
|
|
96
|
-
return visibleChanges.filter(c => c.storeName === selectedHistoryStore);
|
|
97
|
-
}
|
|
98
|
-
return visibleChanges;
|
|
99
|
-
}, [visibleChanges, selectedHistoryStore]);
|
|
125
|
+
/** The list the detail stepper walks: already scoped, already capped. */
|
|
126
|
+
const activeChanges = visibleChanges;
|
|
100
127
|
|
|
101
128
|
// Selection state — track by ID so new events don't shift position
|
|
102
129
|
const [selectedChangeId, setSelectedChangeId] = (0, _react.useState)(null);
|
|
@@ -146,6 +173,16 @@ function ZustandModal({
|
|
|
146
173
|
}));
|
|
147
174
|
}
|
|
148
175
|
};
|
|
176
|
+
|
|
177
|
+
/** Drop the query AND put the header back. The X, and only the X. */
|
|
178
|
+
const closeSearch = (0, _react.useCallback)(() => {
|
|
179
|
+
setSearchText("");
|
|
180
|
+
setFilter(prev => ({
|
|
181
|
+
...prev,
|
|
182
|
+
searchText: ""
|
|
183
|
+
}));
|
|
184
|
+
setIsSearchActive(false);
|
|
185
|
+
}, [setFilter]);
|
|
149
186
|
const handleChangePress = (0, _react.useCallback)(change => {
|
|
150
187
|
setSelectedChangeId(change.id);
|
|
151
188
|
}, []);
|
|
@@ -169,6 +206,16 @@ function ZustandModal({
|
|
|
169
206
|
setSelectedHistoryStore(null);
|
|
170
207
|
setSelectedChangeId(null);
|
|
171
208
|
}, []);
|
|
209
|
+
const handleOpenStore = (0, _react.useCallback)(storeName => {
|
|
210
|
+
setSelectedStoreName(storeName);
|
|
211
|
+
// The desktop mirror hydrates a store's full state on demand — this is
|
|
212
|
+
// the moment it becomes needed (was fired on card-expand before the
|
|
213
|
+
// detail screen replaced expansion).
|
|
214
|
+
onStoreExpand?.(storeName);
|
|
215
|
+
}, [onStoreExpand]);
|
|
216
|
+
const handleBackFromStore = (0, _react.useCallback)(() => {
|
|
217
|
+
setSelectedStoreName(null);
|
|
218
|
+
}, []);
|
|
172
219
|
const getStoresSnapshot = (0, _react.useCallback)(() => {
|
|
173
220
|
return displayedStores.reduce((acc, store) => {
|
|
174
221
|
try {
|
|
@@ -223,6 +270,7 @@ function ZustandModal({
|
|
|
223
270
|
const handleTabChange = (0, _react.useCallback)(tab => {
|
|
224
271
|
setActiveTab(tab);
|
|
225
272
|
setSelectedHistoryStore(null);
|
|
273
|
+
setSelectedStoreName(null);
|
|
226
274
|
setSelectedChangeId(null);
|
|
227
275
|
setShowFilters(false);
|
|
228
276
|
if (searchText) {
|
|
@@ -235,14 +283,20 @@ function ZustandModal({
|
|
|
235
283
|
setIsSearchActive(false);
|
|
236
284
|
}, [searchText, setFilter]);
|
|
237
285
|
const keyExtractor = (0, _react.useCallback)(item => item.id, []);
|
|
286
|
+
|
|
287
|
+
// Built for the whole list rather than per row: the ordinal a row carries is
|
|
288
|
+
// a fact about the list around it. See ../utils/testIDs.ts for why a store
|
|
289
|
+
// name alone cannot address a change row.
|
|
290
|
+
const rowTestIDs = (0, _react.useMemo)(() => (0, _testIDs.buildChangeRowTestIDs)(activeChanges), [activeChanges]);
|
|
238
291
|
const renderItem = (0, _react.useCallback)(({
|
|
239
292
|
item
|
|
240
293
|
}) => {
|
|
241
294
|
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_ZustandStateChangeItem.ZustandStateChangeItem, {
|
|
242
295
|
change: item,
|
|
243
|
-
onPress: handleChangePress
|
|
296
|
+
onPress: handleChangePress,
|
|
297
|
+
testID: rowTestIDs.get(item.id)
|
|
244
298
|
});
|
|
245
|
-
}, [handleChangePress]);
|
|
299
|
+
}, [handleChangePress, rowTestIDs]);
|
|
246
300
|
|
|
247
301
|
// ---- Header rendering ----
|
|
248
302
|
|
|
@@ -255,7 +309,8 @@ function ZustandModal({
|
|
|
255
309
|
onBack: () => setShowFilters(false)
|
|
256
310
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ModalHeader.Content, {
|
|
257
311
|
title: "Filters",
|
|
258
|
-
centered: true
|
|
312
|
+
centered: true,
|
|
313
|
+
testID: `${_testIDs.ZUSTAND_TEST_ID}-filters-title`
|
|
259
314
|
})]
|
|
260
315
|
});
|
|
261
316
|
}
|
|
@@ -267,7 +322,8 @@ function ZustandModal({
|
|
|
267
322
|
onBack: handleBack
|
|
268
323
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ModalHeader.Content, {
|
|
269
324
|
title: `${selectedChange.storeName}/setState`,
|
|
270
|
-
centered: true
|
|
325
|
+
centered: true,
|
|
326
|
+
testID: `${_testIDs.ZUSTAND_TEST_ID}-detail-title`
|
|
271
327
|
})]
|
|
272
328
|
});
|
|
273
329
|
}
|
|
@@ -279,17 +335,34 @@ function ZustandModal({
|
|
|
279
335
|
onBack: handleBackFromHistory
|
|
280
336
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ModalHeader.Content, {
|
|
281
337
|
title: `${selectedHistoryStore} History`,
|
|
282
|
-
centered: true
|
|
338
|
+
centered: true,
|
|
339
|
+
testID: `${_testIDs.ZUSTAND_TEST_ID}-history-title`
|
|
283
340
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ModalHeader.Actions, {
|
|
284
341
|
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ToolbarCopyButton, {
|
|
285
342
|
value: getHistorySnapshot,
|
|
286
343
|
disabled: activeChanges.length === 0,
|
|
287
|
-
buttonStyle: styles.headerActionButton
|
|
344
|
+
buttonStyle: styles.headerActionButton,
|
|
345
|
+
testID: `${_testIDs.ZUSTAND_TEST_ID}-copy`
|
|
288
346
|
})
|
|
289
347
|
})]
|
|
290
348
|
});
|
|
291
349
|
}
|
|
292
350
|
|
|
351
|
+
// Store detail header — the live editor screen. AFTER history: history is
|
|
352
|
+
// reached FROM the detail screen and stacks over it, so when both are set
|
|
353
|
+
// history owns the header, and its back lands you back on the detail.
|
|
354
|
+
if (selectedStoreName) {
|
|
355
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_sharedUi.ModalHeader, {
|
|
356
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ModalHeader.Navigation, {
|
|
357
|
+
onBack: handleBackFromStore
|
|
358
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ModalHeader.Content, {
|
|
359
|
+
title: selectedStoreName,
|
|
360
|
+
centered: true,
|
|
361
|
+
testID: `${_testIDs.ZUSTAND_TEST_ID}-store-detail-title`
|
|
362
|
+
})]
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
|
|
293
366
|
// Main view with tabs
|
|
294
367
|
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_sharedUi.ModalHeader, {
|
|
295
368
|
children: [onBack && /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ModalHeader.Navigation, {
|
|
@@ -298,27 +371,42 @@ function ZustandModal({
|
|
|
298
371
|
title: "",
|
|
299
372
|
children: isSearchActive ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
300
373
|
style: styles.headerSearchContainer,
|
|
374
|
+
testID: `${_testIDs.ZUSTAND_TEST_ID}-search`,
|
|
301
375
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Search, {
|
|
302
376
|
size: 14,
|
|
303
377
|
color: _sharedUi.night.color.textSecondary
|
|
304
378
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TextInput, {
|
|
305
379
|
ref: searchInputRef,
|
|
380
|
+
testID: `${_testIDs.ZUSTAND_TEST_ID}-search-field`,
|
|
306
381
|
style: styles.headerSearchInput,
|
|
307
382
|
placeholder: activeTab === "stores" ? "Search stores..." : "Search events...",
|
|
308
383
|
placeholderTextColor: _sharedUi.night.color.placeholder,
|
|
309
384
|
value: searchText,
|
|
310
385
|
onChangeText: handleSearch,
|
|
311
|
-
onSubmitEditing: () =>
|
|
312
|
-
|
|
386
|
+
onSubmitEditing: () => searchInputRef.current?.blur()
|
|
387
|
+
// Stay open while the query is still narrowing the list.
|
|
388
|
+
//
|
|
389
|
+
// Collapsing on blur hid the reason rows were missing: the
|
|
390
|
+
// header went back to its TABS — whose counts are of the
|
|
391
|
+
// WHOLE capture on the stores side — while the list stayed
|
|
392
|
+
// filtered, so the tool showed "Stores (4)" above a single
|
|
393
|
+
// row and offered nothing to explain the gap. The only way
|
|
394
|
+
// back was to reopen a field the user had no reason to think
|
|
395
|
+
// was still armed.
|
|
396
|
+
,
|
|
397
|
+
onBlur: () => {
|
|
398
|
+
if (searchText.length === 0) setIsSearchActive(false);
|
|
399
|
+
},
|
|
400
|
+
accessibilityLabel: "Search Zustand stores and events",
|
|
313
401
|
autoCapitalize: "none",
|
|
314
402
|
autoCorrect: false,
|
|
315
403
|
returnKeyType: "search"
|
|
316
|
-
}),
|
|
317
|
-
onPress:
|
|
318
|
-
handleSearch("");
|
|
319
|
-
setIsSearchActive(false);
|
|
320
|
-
},
|
|
404
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
|
|
405
|
+
onPress: closeSearch,
|
|
321
406
|
style: styles.headerSearchClear,
|
|
407
|
+
testID: `${_testIDs.ZUSTAND_TEST_ID}-search-clear`,
|
|
408
|
+
accessibilityRole: "button",
|
|
409
|
+
accessibilityLabel: "Close search",
|
|
322
410
|
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.X, {
|
|
323
411
|
size: 14,
|
|
324
412
|
color: _sharedUi.night.color.textSecondary
|
|
@@ -333,12 +421,16 @@ function ZustandModal({
|
|
|
333
421
|
label: `Events${displayedChanges.length > 0 ? ` (${displayedChanges.length})` : ""}`
|
|
334
422
|
}],
|
|
335
423
|
activeTab: activeTab,
|
|
336
|
-
onTabChange: handleTabChange
|
|
424
|
+
onTabChange: handleTabChange,
|
|
425
|
+
testIDPrefix: `${_testIDs.ZUSTAND_TEST_ID}-tab`
|
|
337
426
|
})
|
|
338
427
|
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_sharedUi.ModalHeader.Actions, {
|
|
339
428
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
|
|
340
429
|
onPress: () => setIsSearchActive(true),
|
|
341
430
|
style: styles.headerActionButton,
|
|
431
|
+
testID: `${_testIDs.ZUSTAND_TEST_ID}-search-open`,
|
|
432
|
+
accessibilityRole: "button",
|
|
433
|
+
accessibilityLabel: "Search",
|
|
342
434
|
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Search, {
|
|
343
435
|
size: 14,
|
|
344
436
|
color: _sharedUi.night.color.textSecondary
|
|
@@ -346,6 +438,9 @@ function ZustandModal({
|
|
|
346
438
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
|
|
347
439
|
onPress: () => setShowFilters(true),
|
|
348
440
|
style: [styles.headerActionButton, hasActiveFilters && styles.headerActionButtonActive],
|
|
441
|
+
testID: `${_testIDs.ZUSTAND_TEST_ID}-filters-open`,
|
|
442
|
+
accessibilityRole: "button",
|
|
443
|
+
accessibilityLabel: "Filters",
|
|
349
444
|
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Filter, {
|
|
350
445
|
size: 14,
|
|
351
446
|
color: hasActiveFilters ? _sharedUi.night.color.accent : _sharedUi.night.color.textSecondary
|
|
@@ -353,20 +448,24 @@ function ZustandModal({
|
|
|
353
448
|
}), activeTab === "stores" && /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ToolbarCopyButton, {
|
|
354
449
|
value: getStoresSnapshot,
|
|
355
450
|
disabled: displayedStores.length === 0,
|
|
356
|
-
buttonStyle: styles.headerActionButton
|
|
451
|
+
buttonStyle: styles.headerActionButton,
|
|
452
|
+
testID: `${_testIDs.ZUSTAND_TEST_ID}-copy`
|
|
357
453
|
}), activeTab === "events" && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
|
|
358
454
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ToolbarCopyButton, {
|
|
359
455
|
value: getEventsSnapshot,
|
|
360
456
|
disabled: displayedChanges.length === 0,
|
|
361
|
-
buttonStyle: styles.headerActionButton
|
|
457
|
+
buttonStyle: styles.headerActionButton,
|
|
458
|
+
testID: `${_testIDs.ZUSTAND_TEST_ID}-copy`
|
|
362
459
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.PowerToggleButton, {
|
|
363
460
|
isEnabled: isEnabled,
|
|
364
461
|
onToggle: toggleCapture,
|
|
365
|
-
accessibilityLabel: "Toggle Zustand state capture"
|
|
462
|
+
accessibilityLabel: "Toggle Zustand state capture",
|
|
463
|
+
testID: `${_testIDs.ZUSTAND_TEST_ID}-power`
|
|
366
464
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ToolbarClearButton, {
|
|
367
465
|
onPress: clearChanges,
|
|
368
466
|
disabled: displayedChanges.length === 0,
|
|
369
|
-
buttonStyle: styles.headerActionButton
|
|
467
|
+
buttonStyle: styles.headerActionButton,
|
|
468
|
+
testID: `${_testIDs.ZUSTAND_TEST_ID}-clear`
|
|
370
469
|
})]
|
|
371
470
|
})]
|
|
372
471
|
})]
|
|
@@ -401,18 +500,20 @@ function ZustandModal({
|
|
|
401
500
|
// Store history view — filtered to one store
|
|
402
501
|
if (selectedHistoryStore) {
|
|
403
502
|
return activeChanges.length > 0 ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.FlatList, {
|
|
503
|
+
testID: `${_testIDs.ZUSTAND_TEST_ID}-history-list`,
|
|
404
504
|
data: activeChanges,
|
|
405
505
|
renderItem: renderItem,
|
|
406
506
|
keyExtractor: keyExtractor,
|
|
407
507
|
contentContainerStyle: styles.listContent,
|
|
408
508
|
showsVerticalScrollIndicator: true,
|
|
409
|
-
removeClippedSubviews:
|
|
509
|
+
removeClippedSubviews: CLIP_OFFSCREEN_CELLS,
|
|
410
510
|
initialNumToRender: 12,
|
|
411
511
|
maxToRenderPerBatch: 8,
|
|
412
512
|
windowSize: 5,
|
|
413
513
|
updateCellsBatchingPeriod: 50
|
|
414
514
|
}) : /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
415
515
|
style: styles.emptyState,
|
|
516
|
+
testID: `${_testIDs.ZUSTAND_TEST_ID}-history-empty`,
|
|
416
517
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Box, {
|
|
417
518
|
size: 32,
|
|
418
519
|
color: _sharedUi.night.color.textTertiary
|
|
@@ -426,14 +527,41 @@ function ZustandModal({
|
|
|
426
527
|
});
|
|
427
528
|
}
|
|
428
529
|
|
|
530
|
+
// Store detail — live editor + details + read-only tree. After history
|
|
531
|
+
// in this chain for the same reason as the header: history stacks OVER
|
|
532
|
+
// the detail screen it was opened from.
|
|
533
|
+
if (selectedStoreName) {
|
|
534
|
+
const selectedStore = stores.find(s => s.name === selectedStoreName) ?? null;
|
|
535
|
+
return selectedStore ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_ZustandStoreDetailContent.ZustandStoreDetailContent, {
|
|
536
|
+
store: selectedStore,
|
|
537
|
+
onViewHistory: handleViewHistory
|
|
538
|
+
}) :
|
|
539
|
+
/*#__PURE__*/
|
|
540
|
+
// The store fell out of the registry mid-look (a remote mirror
|
|
541
|
+
// rebuilding, a dev reload). Say so rather than rendering nothing.
|
|
542
|
+
(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
543
|
+
style: styles.emptyState,
|
|
544
|
+
testID: `${_testIDs.ZUSTAND_TEST_ID}-store-detail-gone`,
|
|
545
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Box, {
|
|
546
|
+
size: 32,
|
|
547
|
+
color: _sharedUi.night.color.textTertiary
|
|
548
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
549
|
+
style: styles.emptyTitle,
|
|
550
|
+
children: "Store no longer registered"
|
|
551
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
|
|
552
|
+
style: styles.emptyText,
|
|
553
|
+
children: [selectedStoreName, " is not in the registry any more."]
|
|
554
|
+
})]
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
|
|
429
558
|
// Stores browse tab
|
|
430
559
|
if (activeTab === "stores") {
|
|
431
560
|
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_ZustandStoreBrowser.ZustandStoreBrowser, {
|
|
432
561
|
stores: displayedStores,
|
|
433
562
|
searchQuery: searchText,
|
|
434
|
-
onViewHistory: handleViewHistory,
|
|
435
563
|
isLoading: storesLoading,
|
|
436
|
-
|
|
564
|
+
onOpenStore: handleOpenStore
|
|
437
565
|
});
|
|
438
566
|
}
|
|
439
567
|
|
|
@@ -441,6 +569,7 @@ function ZustandModal({
|
|
|
441
569
|
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
|
|
442
570
|
children: [!isEnabled && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
443
571
|
style: styles.disabledBanner,
|
|
572
|
+
testID: `${_testIDs.ZUSTAND_TEST_ID}-banner-disabled`,
|
|
444
573
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Power, {
|
|
445
574
|
size: 14,
|
|
446
575
|
color: _sharedUi.night.color.warning
|
|
@@ -452,11 +581,14 @@ function ZustandModal({
|
|
|
452
581
|
style: styles.lockedBanner,
|
|
453
582
|
onPress: handleUpgradePress,
|
|
454
583
|
activeOpacity: 0.8,
|
|
584
|
+
testID: `${_testIDs.ZUSTAND_TEST_ID}-locked`,
|
|
585
|
+
accessibilityRole: "button",
|
|
455
586
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Lock, {
|
|
456
587
|
size: 14,
|
|
457
588
|
color: _sharedUi.night.color.accent
|
|
458
589
|
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
|
|
459
590
|
style: styles.lockedText,
|
|
591
|
+
testID: `${_testIDs.ZUSTAND_TEST_ID}-locked-text`,
|
|
460
592
|
children: [lockedChangeCount, " older", " ", lockedChangeCount === 1 ? "change" : "changes", " locked"]
|
|
461
593
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
462
594
|
style: styles.upgradeBadge,
|
|
@@ -467,12 +599,13 @@ function ZustandModal({
|
|
|
467
599
|
})]
|
|
468
600
|
}), visibleChanges.length > 0 ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.FlatList, {
|
|
469
601
|
ref: flatListRef,
|
|
602
|
+
testID: `${_testIDs.ZUSTAND_TEST_ID}-list`,
|
|
470
603
|
data: visibleChanges,
|
|
471
604
|
renderItem: renderItem,
|
|
472
605
|
keyExtractor: keyExtractor,
|
|
473
606
|
contentContainerStyle: styles.listContent,
|
|
474
607
|
showsVerticalScrollIndicator: true,
|
|
475
|
-
removeClippedSubviews:
|
|
608
|
+
removeClippedSubviews: CLIP_OFFSCREEN_CELLS,
|
|
476
609
|
initialNumToRender: 12,
|
|
477
610
|
maxToRenderPerBatch: 8,
|
|
478
611
|
windowSize: 5,
|
|
@@ -496,7 +629,15 @@ function ZustandModal({
|
|
|
496
629
|
// (inside JsModal's ScrollView wrapper every row mounts — the Network
|
|
497
630
|
// tool measured 17 JS FPS / 133% CPU at max load from this exact
|
|
498
631
|
// pattern; see tool-bench batches 2026-07-06).
|
|
499
|
-
|
|
632
|
+
//
|
|
633
|
+
// The STORES tab owns its scrolling too, for a different reason: the
|
|
634
|
+
// browser hosts the store editor as an absolute-fill overlay over a
|
|
635
|
+
// flex:1 root, and inside JsModal's ScrollView wrapper that root
|
|
636
|
+
// collapses to zero height — the list still paints (iOS overflow), but
|
|
637
|
+
// the editor opens into a 0pt box and never appears. The nested-flex:1-
|
|
638
|
+
// in-JsModal trap; storage's browser disables the wrapper for the same
|
|
639
|
+
// reason.
|
|
640
|
+
const listOwnsScroll = !showFilters && !(selectedChange && selectedChangeIndex !== null) && (selectedHistoryStore != null || activeTab === "events" || activeTab === "stores");
|
|
500
641
|
const body = /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
501
642
|
style: styles.container,
|
|
502
643
|
children: renderContent()
|
|
@@ -95,47 +95,43 @@ function getPrimaryText(change) {
|
|
|
95
95
|
// ({change, onPress} — onPress is a stable useCallback in the modal).
|
|
96
96
|
const ZustandStateChangeItem = exports.ZustandStateChangeItem = /*#__PURE__*/(0, _react.memo)(function ZustandStateChangeItem({
|
|
97
97
|
change,
|
|
98
|
-
onPress
|
|
98
|
+
onPress,
|
|
99
|
+
testID
|
|
99
100
|
}) {
|
|
100
101
|
const statusColor = getCategoryColor(change.category, change.storeName);
|
|
101
102
|
const statusLabel = getStatusLabel(change.category, change.storeName);
|
|
102
103
|
const sublabel = getSublabel(change);
|
|
103
104
|
const primaryText = getPrimaryText(change);
|
|
104
105
|
const badgeText = getBadgeText(change);
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
color: _sharedUi.buoyColors.warning
|
|
135
|
-
})
|
|
136
|
-
})]
|
|
137
|
-
});
|
|
138
|
-
}
|
|
106
|
+
|
|
107
|
+
// Always a custom badge, even when there is no icon to hang off it. The
|
|
108
|
+
// alternative — letting CompactRow draw `badgeText` itself — leaves the one
|
|
109
|
+
// row whose badge matters most (a set that changed NOTHING) as the only row
|
|
110
|
+
// in the list with no handle on its badge.
|
|
111
|
+
const customBadge = /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
112
|
+
style: styles.badgeContainer,
|
|
113
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
114
|
+
style: [styles.badgeText, {
|
|
115
|
+
color: statusColor
|
|
116
|
+
}],
|
|
117
|
+
testID: testID ? `${testID}-badge` : undefined,
|
|
118
|
+
children: badgeText
|
|
119
|
+
}), change.isSlowUpdate ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
120
|
+
style: styles.slowBadge,
|
|
121
|
+
testID: testID ? `${testID}-slow` : undefined,
|
|
122
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Clock, {
|
|
123
|
+
size: 12,
|
|
124
|
+
color: _sharedUi.buoyColors.error
|
|
125
|
+
})
|
|
126
|
+
}) : change.hasStateChange ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
127
|
+
style: styles.changeBadge,
|
|
128
|
+
testID: testID ? `${testID}-change` : undefined,
|
|
129
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Zap, {
|
|
130
|
+
size: 12,
|
|
131
|
+
color: _sharedUi.buoyColors.warning
|
|
132
|
+
})
|
|
133
|
+
}) : null]
|
|
134
|
+
});
|
|
139
135
|
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.CompactRow, {
|
|
140
136
|
statusDotColor: statusColor,
|
|
141
137
|
statusLabel: statusLabel,
|
|
@@ -145,10 +141,13 @@ const ZustandStateChangeItem = exports.ZustandStateChangeItem = /*#__PURE__*/(0,
|
|
|
145
141
|
timestamp: change.timestamp
|
|
146
142
|
}),
|
|
147
143
|
customBadge: customBadge,
|
|
148
|
-
badgeText: customBadge ? undefined : badgeText,
|
|
149
144
|
badgeColor: statusColor,
|
|
150
145
|
showChevron: true,
|
|
151
|
-
onPress: () => onPress(change)
|
|
146
|
+
onPress: () => onPress(change),
|
|
147
|
+
testID: testID,
|
|
148
|
+
statusTestID: testID ? `${testID}-status` : undefined,
|
|
149
|
+
statusSublabelTestID: testID ? `${testID}-sublabel` : undefined,
|
|
150
|
+
primaryTestID: testID ? `${testID}-keys` : undefined
|
|
152
151
|
});
|
|
153
152
|
});
|
|
154
153
|
const styles = _reactNative.StyleSheet.create({
|