@buoy-gg/jotai 7.0.33 → 7.0.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/lib/commonjs/jotai/components/JotaiAtomChangeItem.js +7 -52
  2. package/lib/commonjs/jotai/components/JotaiAtomDetailContent.js +8 -87
  3. package/lib/commonjs/jotai/components/JotaiEventFilterView.js +21 -299
  4. package/lib/commonjs/jotai/components/JotaiModal.js +20 -85
  5. package/lib/commonjs/jotai/hooks/useJotaiAtomChanges.js +33 -49
  6. package/lib/commonjs/jotai/sync/jotaiSyncAdapter.js +19 -66
  7. package/lib/commonjs/jotai/utils/jotaiStateStore.js +23 -188
  8. package/lib/commonjs/jotai/utils/snapshotProvider.js +7 -25
  9. package/lib/commonjs/preset.js +14 -22
  10. package/lib/module/jotai/components/JotaiAtomChangeItem.js +9 -54
  11. package/lib/module/jotai/components/JotaiAtomDetailContent.js +9 -88
  12. package/lib/module/jotai/components/JotaiEventFilterView.js +24 -302
  13. package/lib/module/jotai/components/JotaiModal.js +22 -87
  14. package/lib/module/jotai/hooks/useJotaiAtomChanges.js +34 -50
  15. package/lib/module/jotai/sync/jotaiSyncAdapter.js +20 -67
  16. package/lib/module/jotai/utils/jotaiStateStore.js +23 -188
  17. package/lib/module/jotai/utils/snapshotProvider.js +8 -26
  18. package/lib/module/preset.js +13 -20
  19. package/lib/typescript/jotai/components/JotaiAtomChangeItem.d.ts.map +1 -1
  20. package/lib/typescript/jotai/components/JotaiAtomDetailContent.d.ts.map +1 -1
  21. package/lib/typescript/jotai/components/JotaiEventFilterView.d.ts +2 -2
  22. package/lib/typescript/jotai/components/JotaiEventFilterView.d.ts.map +1 -1
  23. package/lib/typescript/jotai/components/JotaiModal.d.ts.map +1 -1
  24. package/lib/typescript/jotai/hooks/useJotaiAtomChanges.d.ts +2 -1
  25. package/lib/typescript/jotai/hooks/useJotaiAtomChanges.d.ts.map +1 -1
  26. package/lib/typescript/jotai/sync/jotaiSyncAdapter.d.ts +54 -68
  27. package/lib/typescript/jotai/sync/jotaiSyncAdapter.d.ts.map +1 -1
  28. package/lib/typescript/jotai/utils/jotaiStateStore.d.ts +2 -25
  29. package/lib/typescript/jotai/utils/jotaiStateStore.d.ts.map +1 -1
  30. package/lib/typescript/preset.d.ts +2 -8
  31. package/lib/typescript/preset.d.ts.map +1 -1
  32. package/package.json +3 -3
@@ -5,7 +5,6 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.JotaiAtomChangeItem = void 0;
7
7
  var _react = require("react");
8
- var _reactNative = require("react-native");
9
8
  var _sharedUi = require("@buoy-gg/shared-ui");
10
9
  var _jotaiStateStore = require("../utils/jotaiStateStore");
11
10
  var _jsxRuntime = require("react/jsx-runtime");
@@ -72,59 +71,15 @@ const JotaiAtomChangeItem = exports.JotaiAtomChangeItem = /*#__PURE__*/(0, _reac
72
71
  const sublabel = getSublabel(change);
73
72
  const primaryText = getPrimaryText(change);
74
73
  const badgeText = getBadgeText(change);
75
-
76
- // Always a custom badge, even when there is no icon to hang off it. The
77
- // alternative — letting CompactRow draw `badgeText` itself — leaves the one
78
- // row whose badge matters most (a write that moved the atom's identity but
79
- // none of its contents) as the only row in the list with no handle on its
80
- // badge. Same call, and the same reason, as ZustandStateChangeItem.
81
- const customBadge = /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
82
- style: styles.badgeContainer,
83
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
84
- style: [styles.badgeText, {
85
- color: statusColor
86
- }],
87
- testID: testID ? `${testID}-badge` : undefined,
88
- children: badgeText
89
- }), change.hasValueChange ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
90
- style: styles.changeBadge,
91
- testID: testID ? `${testID}-change` : undefined,
92
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Zap, {
93
- size: 12,
94
- color: _sharedUi.buoyColors.warning
95
- })
96
- }) : null]
97
- });
98
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.CompactRow, {
99
- statusDotColor: statusColor,
74
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ChangeRow, {
75
+ statusColor: statusColor,
100
76
  statusLabel: statusLabel,
101
- statusSublabel: sublabel,
77
+ sublabel: sublabel,
102
78
  primaryText: primaryText,
103
- bottomRightText: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.RelativeTime, {
104
- timestamp: change.timestamp
105
- }),
106
- customBadge: customBadge,
107
- badgeColor: statusColor,
108
- showChevron: true,
79
+ timestamp: change.timestamp,
80
+ badgeText: badgeText,
81
+ changed: change.hasValueChange,
109
82
  onPress: () => onPress(change),
110
- testID: testID,
111
- statusTestID: testID ? `${testID}-status` : undefined,
112
- statusSublabelTestID: testID ? `${testID}-sublabel` : undefined,
113
- primaryTestID: testID ? `${testID}-keys` : undefined
83
+ testID: testID
114
84
  });
115
- });
116
- const styles = _reactNative.StyleSheet.create({
117
- badgeContainer: {
118
- flexDirection: "row",
119
- alignItems: "center",
120
- gap: 4
121
- },
122
- badgeText: {
123
- fontSize: 11,
124
- fontWeight: "600",
125
- fontFamily: "monospace"
126
- },
127
- changeBadge: {
128
- paddingHorizontal: 4
129
- }
130
85
  });
@@ -29,61 +29,17 @@ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r
29
29
  const VIEW_CONFIGS = [{
30
30
  key: "change",
31
31
  label: "CHANGE",
32
- icon: _sharedUi.FileText,
33
- activeColor: _sharedUi.night.color.accent
32
+ icon: _sharedUi.FileText
34
33
  }, {
35
34
  key: "value",
36
35
  label: "VALUE",
37
- icon: _sharedUi.Database,
38
- activeColor: _sharedUi.night.color.accent
36
+ icon: _sharedUi.Database
39
37
  }, {
40
38
  key: "diff",
41
39
  label: "DIFF",
42
- icon: _sharedUi.GitBranch,
43
- activeColor: _sharedUi.night.color.accent
40
+ icon: _sharedUi.GitBranch
44
41
  }];
45
- function DetailViewToggle({
46
- activeView,
47
- onViewChange,
48
- diffDisabled
49
- }) {
50
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
51
- style: toggleStyles.container,
52
- children: VIEW_CONFIGS.map(config => {
53
- const isActive = activeView === config.key;
54
- const isDisabled = config.key === "diff" && diffDisabled;
55
- const IconComponent = config.icon;
56
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
57
- style: [toggleStyles.card, isActive && [toggleStyles.cardActive, {
58
- borderColor: config.activeColor
59
- }], isDisabled && toggleStyles.cardDisabled],
60
- onPress: () => !isDisabled && onViewChange(config.key),
61
- activeOpacity: 0.7,
62
- testID: `${_testIDs.JOTAI_TEST_ID}-view-${config.key}`,
63
- accessibilityRole: "button",
64
- accessibilityLabel: config.label,
65
- accessibilityState: {
66
- disabled: isDisabled,
67
- selected: isActive
68
- },
69
- children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
70
- style: toggleStyles.cardContent,
71
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(IconComponent, {
72
- size: 14,
73
- color: isActive ? config.activeColor : isDisabled ? _sharedUi.night.color.textTertiary : _sharedUi.night.color.textSecondary
74
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
75
- style: [toggleStyles.cardLabel, isActive && {
76
- color: config.activeColor
77
- }, isDisabled && {
78
- color: _sharedUi.night.color.textTertiary
79
- }],
80
- children: config.label
81
- })]
82
- })
83
- }, config.key);
84
- })
85
- });
86
- }
42
+ const DIFF_DISABLED = ["diff"];
87
43
 
88
44
  // ---- Helpers ----
89
45
 
@@ -432,10 +388,12 @@ function JotaiAtomDetailContent({
432
388
  const showInternalFooter = !disableInternalFooter && totalChanges > 1;
433
389
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
434
390
  style: styles.container,
435
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(DetailViewToggle, {
391
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.DetailViewToggle, {
392
+ views: VIEW_CONFIGS,
436
393
  activeView: activeView,
437
394
  onViewChange: setActiveView,
438
- diffDisabled: diffDisabled
395
+ disabledKeys: diffDisabled ? DIFF_DISABLED : undefined,
396
+ testIDPrefix: _testIDs.JOTAI_TEST_ID
439
397
  }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
440
398
  style: [styles.contentArea, showInternalFooter && styles.contentAreaFooterPad],
441
399
  children: [activeView === "change" && /*#__PURE__*/(0, _jsxRuntime.jsx)(JotaiAtomInfoView, {
@@ -587,43 +545,6 @@ const styles = _reactNative.StyleSheet.create({
587
545
  fontFamily: "monospace"
588
546
  }
589
547
  });
590
- const toggleStyles = _reactNative.StyleSheet.create({
591
- container: {
592
- flexDirection: "row",
593
- gap: 8,
594
- padding: 14,
595
- // Transparent so the night sheet's star field shows through.
596
- backgroundColor: "transparent"
597
- },
598
- card: {
599
- flex: 1,
600
- backgroundColor: _sharedUi.night.color.surface,
601
- borderRadius: 12,
602
- borderWidth: 1,
603
- borderColor: _sharedUi.night.color.border,
604
- paddingVertical: 10,
605
- paddingHorizontal: 10,
606
- justifyContent: "center"
607
- },
608
- cardActive: {
609
- borderWidth: 1.5,
610
- backgroundColor: _sharedUi.night.color.accentSoft
611
- },
612
- cardDisabled: {
613
- opacity: 0.5
614
- },
615
- cardContent: {
616
- flexDirection: "row",
617
- alignItems: "center",
618
- gap: 6
619
- },
620
- cardLabel: {
621
- fontSize: 10,
622
- fontWeight: "700",
623
- letterSpacing: 0.5,
624
- color: _sharedUi.night.color.textSecondary
625
- }
626
- });
627
548
  const infoStyles = _reactNative.StyleSheet.create({
628
549
  container: {
629
550
  flex: 1
@@ -5,317 +5,39 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.JotaiEventFilterView = JotaiEventFilterView;
7
7
  var _react = require("react");
8
- var _reactNative = require("react-native");
9
8
  var _sharedUi = require("@buoy-gg/shared-ui");
10
- var _jotaiStateStore = require("../utils/jotaiStateStore");
11
9
  var _testIDs = require("../utils/testIDs");
12
10
  var _jsxRuntime = require("react/jsx-runtime");
13
11
  /**
14
12
  * JotaiEventFilterView
15
13
  *
16
- * Filter configuration UI for hiding atoms from the Atoms and Events tabs.
17
- * Mirrors ZustandEventFilterView.tsx from @buoy-gg/zustand.
14
+ * The shared `EntityFilterView` with jotai's nouns: watched atoms you can
15
+ * tap to hide, active atom-name patterns, and how it works.
18
16
  */
19
17
 
20
- function getAtomColorForPattern(pattern, atoms) {
21
- const match = atoms.find(a => a.label.toLowerCase().includes(pattern.toLowerCase()));
22
- return match ? match.color : null;
23
- }
18
+ const EXAMPLES = ["• count -> hides countAtom from atoms & events", "• auth -> hides authAtom, authStatusAtom", "• user -> hides userAtom from both tabs"];
24
19
  function JotaiEventFilterView({
25
20
  ignoredPatterns,
26
21
  onTogglePattern,
27
22
  onAddPattern,
28
23
  atoms
29
24
  }) {
30
- const [newPattern, setNewPattern] = (0, _react.useState)("");
31
- const handleAddPattern = () => {
32
- const trimmed = newPattern.trim();
33
- if (trimmed && !ignoredPatterns.has(trimmed)) {
34
- onAddPattern(trimmed);
35
- setNewPattern("");
36
- }
37
- };
38
- return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.ScrollView, {
39
- style: styles.container,
40
- contentContainerStyle: styles.scrollContent,
41
- showsVerticalScrollIndicator: false,
42
- nestedScrollEnabled: true,
43
- testID: `${_testIDs.JOTAI_TEST_ID}-filters-scroll`,
44
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
45
- style: styles.section,
46
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_sharedUi.SectionHeader, {
47
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.SectionHeader.Icon, {
48
- icon: _sharedUi.Filter,
49
- color: _sharedUi.night.color.textSecondary,
50
- size: 12
51
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.SectionHeader.Title, {
52
- children: "REGISTERED ATOMS"
53
- })]
54
- }), atoms.length === 0 ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
55
- style: styles.emptyText,
56
- testID: `${_testIDs.JOTAI_TEST_ID}-filters-atoms-empty`,
57
- children: "No atoms registered yet."
58
- }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ScrollView, {
59
- style: styles.atomList,
60
- contentContainerStyle: styles.atomListContent,
61
- nestedScrollEnabled: true,
62
- testID: `${_testIDs.JOTAI_TEST_ID}-filters-atom-list`,
63
- children: atoms.map(atom => {
64
- const atomColor = _jotaiStateStore.jotaiStateStore.getAtomColor(atom.label);
65
- const isFiltered = Array.from(ignoredPatterns).some(p => atom.label.toLowerCase().includes(p.toLowerCase()));
66
- return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
67
- style: [styles.atomRow, isFiltered && styles.atomRowFiltered],
68
- onPress: () => onTogglePattern(atom.label),
69
- activeOpacity: 0.7,
70
- testID: `${_testIDs.JOTAI_TEST_ID}-filters-atom-${(0, _testIDs.slugForTestID)(atom.label)}`,
71
- accessibilityRole: "button",
72
- accessibilityLabel: isFiltered ? `Show ${atom.label}` : `Hide ${atom.label}`,
73
- accessibilityState: {
74
- selected: isFiltered
75
- },
76
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
77
- style: [styles.atomDot, {
78
- backgroundColor: atomColor
79
- }]
80
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
81
- style: [styles.atomRowText, {
82
- color: atomColor
83
- }, isFiltered && styles.atomRowTextFiltered],
84
- numberOfLines: 1,
85
- children: atom.label
86
- }), isFiltered ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
87
- style: styles.filteredLabel,
88
- children: "filtered"
89
- }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
90
- style: [styles.addLabel, {
91
- color: atomColor
92
- }],
93
- children: "+ add"
94
- })]
95
- }, atom.label);
96
- })
97
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
98
- style: styles.addInputWrapper,
99
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TextInput, {
100
- testID: `${_testIDs.JOTAI_TEST_ID}-filters-add-input`,
101
- accessibilityLabel: "Add an ignore pattern",
102
- value: newPattern,
103
- onChangeText: setNewPattern,
104
- onSubmitEditing: handleAddPattern,
105
- placeholder: "Type a pattern and press Enter...",
106
- placeholderTextColor: _sharedUi.night.color.placeholder,
107
- returnKeyType: "done",
108
- autoCapitalize: "none",
109
- autoCorrect: false,
110
- style: styles.patternInput
111
- })
112
- })]
113
- }), ignoredPatterns.size > 0 && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
114
- style: styles.section,
115
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_sharedUi.SectionHeader, {
116
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.SectionHeader.Icon, {
117
- icon: _sharedUi.Filter,
118
- color: _sharedUi.night.color.textSecondary,
119
- size: 12
120
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.SectionHeader.Title, {
121
- children: "ACTIVE FILTERS"
122
- })]
123
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
124
- style: styles.patternList,
125
- children: Array.from(ignoredPatterns).map(pattern => {
126
- const atomColor = getAtomColorForPattern(pattern, atoms);
127
- return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
128
- style: styles.patternRow,
129
- onPress: () => onTogglePattern(pattern),
130
- activeOpacity: 0.8,
131
- testID: `${_testIDs.JOTAI_TEST_ID}-filters-chip-${(0, _testIDs.slugForTestID)(pattern)}`,
132
- accessibilityRole: "button",
133
- accessibilityLabel: `Remove filter ${pattern}`,
134
- children: [atomColor ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
135
- style: [styles.atomDot, {
136
- backgroundColor: atomColor
137
- }]
138
- }) : null, /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
139
- style: [styles.patternText, atomColor ? {
140
- color: atomColor
141
- } : null],
142
- numberOfLines: 1,
143
- children: pattern
144
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.X, {
145
- size: 12,
146
- color: _sharedUi.night.color.accent + "80"
147
- })]
148
- }, pattern);
149
- })
150
- })]
151
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
152
- style: styles.section,
153
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_sharedUi.SectionHeader, {
154
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.SectionHeader.Icon, {
155
- icon: _sharedUi.Filter,
156
- color: _sharedUi.night.color.textSecondary,
157
- size: 12
158
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.SectionHeader.Title, {
159
- children: "HOW ATOM FILTERS WORK"
160
- })]
161
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
162
- style: styles.howItWorksText,
163
- children: "Patterns hide matching atoms from both the Atoms tab and the Events tab. Tap an atom above to quickly add it as a filter."
164
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
165
- style: styles.examplesContainer,
166
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
167
- style: styles.examplesTitle,
168
- children: "EXAMPLES:"
169
- }), ["• count -> hides countAtom from atoms & events", "• auth -> hides authAtom, authStatusAtom", "• user -> hides userAtom from both tabs"].map(ex => /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
170
- style: styles.exampleItem,
171
- children: ex
172
- }, ex))]
173
- })]
174
- })]
25
+ const entities = (0, _react.useMemo)(() => atoms.map(atom => ({
26
+ name: atom.label,
27
+ color: atom.color
28
+ })), [atoms]);
29
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.EntityFilterView, {
30
+ entities: entities,
31
+ ignoredPatterns: ignoredPatterns,
32
+ onTogglePattern: onTogglePattern,
33
+ onAddPattern: onAddPattern,
34
+ entityNoun: "ATOMS",
35
+ entitySingular: "atom",
36
+ entityKey: "atom",
37
+ testIDPrefix: _testIDs.JOTAI_TEST_ID,
38
+ slug: _testIDs.slugForTestID,
39
+ howItWorks: "Patterns hide matching atoms from both the Atoms tab and the Events tab. Tap an atom above to quickly add it as a filter.",
40
+ examples: EXAMPLES,
41
+ emptyMessage: "No atoms are being watched yet."
175
42
  });
176
- }
177
- const styles = _reactNative.StyleSheet.create({
178
- container: {
179
- flex: 1,
180
- // Transparent so the night sheet's star field shows through.
181
- backgroundColor: "transparent"
182
- },
183
- scrollContent: {
184
- paddingTop: 16,
185
- paddingHorizontal: 16,
186
- paddingBottom: 24,
187
- gap: 16
188
- },
189
- section: {
190
- backgroundColor: _sharedUi.night.color.surface,
191
- borderRadius: 16,
192
- borderWidth: 1,
193
- borderColor: _sharedUi.night.color.border,
194
- overflow: "hidden"
195
- },
196
- atomList: {
197
- maxHeight: 180
198
- },
199
- atomListContent: {
200
- paddingHorizontal: 16,
201
- paddingTop: 8,
202
- paddingBottom: 16,
203
- gap: 10
204
- },
205
- atomRow: {
206
- flexDirection: "row",
207
- alignItems: "center",
208
- gap: 10,
209
- paddingVertical: 10,
210
- paddingHorizontal: 12,
211
- backgroundColor: _sharedUi.night.color.surfaceElevated,
212
- borderRadius: 8,
213
- borderWidth: 1,
214
- borderColor: _sharedUi.night.color.border
215
- },
216
- atomRowFiltered: {
217
- opacity: 0.45
218
- },
219
- atomDot: {
220
- width: 7,
221
- height: 7,
222
- borderRadius: 3.5
223
- },
224
- atomRowText: {
225
- flex: 1,
226
- fontSize: 12,
227
- fontWeight: "600",
228
- fontFamily: "monospace"
229
- },
230
- atomRowTextFiltered: {
231
- color: _sharedUi.night.color.textTertiary,
232
- textDecorationLine: "line-through"
233
- },
234
- filteredLabel: {
235
- fontSize: 10,
236
- fontWeight: "500",
237
- color: _sharedUi.night.color.textTertiary,
238
- fontFamily: "monospace"
239
- },
240
- addLabel: {
241
- fontSize: 10,
242
- fontWeight: "600",
243
- fontFamily: "monospace"
244
- },
245
- addInputWrapper: {
246
- paddingHorizontal: 16,
247
- paddingTop: 8,
248
- paddingBottom: 8
249
- },
250
- patternList: {
251
- paddingHorizontal: 16,
252
- paddingBottom: 16,
253
- gap: 8
254
- },
255
- patternRow: {
256
- flexDirection: "row",
257
- alignItems: "center",
258
- justifyContent: "space-between",
259
- paddingVertical: 10,
260
- paddingHorizontal: 12,
261
- backgroundColor: _sharedUi.night.color.surfaceElevated,
262
- borderRadius: 8,
263
- borderWidth: 1,
264
- borderColor: _sharedUi.night.color.border,
265
- gap: 8
266
- },
267
- patternText: {
268
- flex: 1,
269
- fontSize: 12,
270
- fontFamily: "monospace",
271
- color: _sharedUi.night.color.text
272
- },
273
- howItWorksText: {
274
- fontSize: 11,
275
- color: _sharedUi.night.color.textSecondary,
276
- lineHeight: 16,
277
- marginTop: 8,
278
- paddingHorizontal: 16,
279
- fontFamily: "monospace"
280
- },
281
- examplesContainer: {
282
- paddingTop: 8,
283
- paddingHorizontal: 16,
284
- paddingBottom: 16,
285
- borderTopWidth: 1,
286
- borderTopColor: _sharedUi.night.color.border,
287
- marginTop: 12
288
- },
289
- examplesTitle: {
290
- fontSize: 10,
291
- fontWeight: "600",
292
- color: _sharedUi.night.color.textTertiary,
293
- fontFamily: "monospace",
294
- letterSpacing: 0.5,
295
- marginBottom: 6
296
- },
297
- exampleItem: {
298
- fontSize: 10,
299
- color: _sharedUi.night.color.textTertiary,
300
- fontFamily: "monospace",
301
- lineHeight: 16
302
- },
303
- emptyText: {
304
- fontSize: 11,
305
- color: _sharedUi.night.color.textTertiary,
306
- paddingHorizontal: 16,
307
- paddingVertical: 12,
308
- fontFamily: "monospace"
309
- },
310
- patternInput: {
311
- borderWidth: 1,
312
- borderColor: _sharedUi.night.color.accentBorder,
313
- borderRadius: 8,
314
- paddingHorizontal: 12,
315
- paddingVertical: 8,
316
- fontSize: 12,
317
- color: _sharedUi.night.color.text,
318
- fontFamily: "monospace",
319
- backgroundColor: _sharedUi.night.color.surfaceElevated
320
- }
321
- });
43
+ }
@@ -25,17 +25,6 @@ var _jsxRuntime = require("react/jsx-runtime");
25
25
  * - Events tab: Live atom change monitoring with diffs
26
26
  */
27
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);
38
-
39
28
  /**
40
29
  * The change log's "nothing here". Everything but the nouns is the shared
41
30
  * precedence — see `ToolEmptyState` in shared-ui for why it lives there.
@@ -358,49 +347,24 @@ function JotaiModal({
358
347
  onBack: onBack
359
348
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ModalHeader.Content, {
360
349
  title: "",
361
- children: isSearchActive ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
362
- style: styles.headerSearchContainer,
363
- testID: `${_testIDs.JOTAI_TEST_ID}-search`,
364
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Search, {
365
- size: 14,
366
- color: _sharedUi.night.color.textSecondary
367
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TextInput, {
368
- ref: searchInputRef,
369
- testID: `${_testIDs.JOTAI_TEST_ID}-search-field`,
370
- style: styles.headerSearchInput,
371
- placeholder: activeTab === "atoms" ? "Search atoms..." : "Search events...",
372
- placeholderTextColor: _sharedUi.night.color.placeholder,
373
- value: searchText,
374
- onChangeText: handleSearch,
375
- onSubmitEditing: () => searchInputRef.current?.blur()
376
- // Stay open while the query is still narrowing the list.
377
- //
378
- // Collapsing on blur hid the reason rows were missing: the
379
- // header went back to its TABS — whose counts are of the
380
- // WHOLE capture — while the list stayed filtered, so the tool
381
- // showed "Atoms (8)" above a single row and offered nothing to
382
- // explain the gap. The only way back was to reopen a field the
383
- // user had no reason to think was still armed. Same shape, and
384
- // the same fix, as the Redux, Events and Zustand tools.
385
- ,
386
- onBlur: () => {
387
- if (searchText.length === 0) setIsSearchActive(false);
388
- },
389
- accessibilityLabel: "Search Jotai atoms and events",
390
- autoCapitalize: "none",
391
- autoCorrect: false,
392
- returnKeyType: "search"
393
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
394
- onPress: closeSearch,
395
- style: styles.headerSearchClear,
396
- testID: `${_testIDs.JOTAI_TEST_ID}-search-clear`,
397
- accessibilityRole: "button",
398
- accessibilityLabel: "Close search",
399
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.X, {
400
- size: 14,
401
- color: _sharedUi.night.color.textSecondary
402
- })
403
- })]
350
+ children: isSearchActive ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ModalHeader.Search, {
351
+ value: searchText,
352
+ onChangeText: handleSearch,
353
+ onCancel: closeSearch,
354
+ placeholder: activeTab === "atoms" ? "Search atoms..." : "Search events...",
355
+ accessibilityLabel: "Search Jotai atoms and events",
356
+ iconSize: 14,
357
+ testID: `${_testIDs.JOTAI_TEST_ID}-search-field`,
358
+ containerTestID: `${_testIDs.JOTAI_TEST_ID}-search`,
359
+ clearTestID: `${_testIDs.JOTAI_TEST_ID}-search-clear`
360
+ // Stay open while the query is still narrowing the list: the
361
+ // header would otherwise go back to its TABS — whose counts are
362
+ // of the WHOLE capture — above a filtered list, with nothing to
363
+ // explain the gap.
364
+ ,
365
+ onBlur: () => {
366
+ if (searchText.length === 0) setIsSearchActive(false);
367
+ }
404
368
  }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.TabSelector, {
405
369
  tabs: [{
406
370
  key: "atoms",
@@ -490,11 +454,7 @@ function JotaiModal({
490
454
  keyExtractor: keyExtractor,
491
455
  contentContainerStyle: styles.listContent,
492
456
  showsVerticalScrollIndicator: true,
493
- removeClippedSubviews: CLIP_OFFSCREEN_CELLS,
494
- initialNumToRender: 12,
495
- maxToRenderPerBatch: 8,
496
- windowSize: 5,
497
- updateCellsBatchingPeriod: 50
457
+ ..._sharedUi.EVENT_LIST_PRESET
498
458
  }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ToolEmptyState, {
499
459
  testID: `${_testIDs.JOTAI_TEST_ID}-history-empty`,
500
460
  ...(0, _sharedUi.resolveListEmptyState)({
@@ -583,11 +543,7 @@ function JotaiModal({
583
543
  keyExtractor: keyExtractor,
584
544
  contentContainerStyle: styles.listContent,
585
545
  showsVerticalScrollIndicator: true,
586
- removeClippedSubviews: CLIP_OFFSCREEN_CELLS,
587
- initialNumToRender: 12,
588
- maxToRenderPerBatch: 8,
589
- windowSize: 5,
590
- updateCellsBatchingPeriod: 50
546
+ ..._sharedUi.EVENT_LIST_PRESET
591
547
  }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(EventsEmptyState, {
592
548
  isEnabled: isEnabled,
593
549
  searchText: searchText,
@@ -701,27 +657,6 @@ const styles = _reactNative.StyleSheet.create({
701
657
  // Transparent so JsModal's ToolBackground shows through (the modal paints opaque base beneath it).
702
658
  backgroundColor: "transparent"
703
659
  },
704
- headerSearchContainer: {
705
- flexDirection: "row",
706
- alignItems: "center",
707
- backgroundColor: _sharedUi.night.color.surfaceElevated,
708
- borderRadius: _sharedUi.night.radius.row,
709
- borderWidth: 1,
710
- borderColor: _sharedUi.night.color.border,
711
- paddingHorizontal: 12,
712
- paddingVertical: 5
713
- },
714
- headerSearchInput: {
715
- flex: 1,
716
- color: _sharedUi.night.color.text,
717
- fontSize: 13,
718
- marginLeft: 6,
719
- paddingVertical: 2
720
- },
721
- headerSearchClear: {
722
- marginLeft: 6,
723
- padding: 4
724
- },
725
660
  headerActionButton: {
726
661
  width: 32,
727
662
  height: 32,