react-native-inapp-inspector 1.1.1 → 1.1.3

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 (45) hide show
  1. package/README.md +14 -0
  2. package/dist/commonjs/components/ConsoleLogCard.js +18 -0
  3. package/dist/commonjs/components/JsonViewer.d.ts +2 -1
  4. package/dist/commonjs/components/JsonViewer.js +6 -4
  5. package/dist/commonjs/components/LogCard.js +17 -0
  6. package/dist/commonjs/components/MetaAccordion.js +26 -6
  7. package/dist/commonjs/components/NetworkIcons.d.ts +9 -2
  8. package/dist/commonjs/components/NetworkIcons.js +59 -3
  9. package/dist/commonjs/components/ReduxTreeView.js +80 -5
  10. package/dist/commonjs/constants/version.d.ts +1 -1
  11. package/dist/commonjs/constants/version.js +1 -1
  12. package/dist/commonjs/customHooks/reduxLogger.d.ts +21 -7
  13. package/dist/commonjs/customHooks/reduxLogger.js +147 -48
  14. package/dist/commonjs/customHooks/webViewLogger.js +13 -8
  15. package/dist/commonjs/helpers/settingsStore.d.ts +24 -0
  16. package/dist/commonjs/helpers/settingsStore.js +74 -0
  17. package/dist/commonjs/index.d.ts +1 -1
  18. package/dist/commonjs/index.js +897 -170
  19. package/dist/commonjs/styles/index.d.ts +40 -0
  20. package/dist/commonjs/styles/index.js +45 -2
  21. package/dist/commonjs/types/index.d.ts +4 -0
  22. package/dist/esm/components/ConsoleLogCard.js +18 -0
  23. package/dist/esm/components/JsonViewer.d.ts +2 -1
  24. package/dist/esm/components/JsonViewer.js +6 -4
  25. package/dist/esm/components/LogCard.js +17 -0
  26. package/dist/esm/components/MetaAccordion.js +27 -7
  27. package/dist/esm/components/NetworkIcons.d.ts +9 -2
  28. package/dist/esm/components/NetworkIcons.js +51 -2
  29. package/dist/esm/components/ReduxTreeView.js +81 -6
  30. package/dist/esm/constants/version.d.ts +1 -1
  31. package/dist/esm/constants/version.js +1 -1
  32. package/dist/esm/customHooks/reduxLogger.d.ts +21 -7
  33. package/dist/esm/customHooks/reduxLogger.js +145 -47
  34. package/dist/esm/customHooks/webViewLogger.js +13 -8
  35. package/dist/esm/helpers/settingsStore.d.ts +24 -0
  36. package/dist/esm/helpers/settingsStore.js +67 -0
  37. package/dist/esm/index.d.ts +1 -1
  38. package/dist/esm/index.js +896 -172
  39. package/dist/esm/styles/index.d.ts +40 -0
  40. package/dist/esm/styles/index.js +45 -2
  41. package/dist/esm/types/index.d.ts +4 -0
  42. package/example/App.tsx +199 -61
  43. package/example/ios/example.xcodeproj/project.pbxproj +0 -8
  44. package/example/package-lock.json +4 -3
  45. package/package.json +1 -1
@@ -35,6 +35,7 @@ export declare const getRawStyles: (colors: typeof AppColors) => {
35
35
  color: string;
36
36
  fontSize: number;
37
37
  letterSpacing: number;
38
+ paddingBottom: number;
38
39
  };
39
40
  headerButtonGroup: {
40
41
  flexDirection: string;
@@ -174,6 +175,26 @@ export declare const getRawStyles: (colors: typeof AppColors) => {
174
175
  listContent: {
175
176
  paddingBottom: number;
176
177
  };
178
+ scrollTopBtn: {
179
+ position: string;
180
+ bottom: number;
181
+ right: number;
182
+ width: number;
183
+ height: number;
184
+ borderRadius: number;
185
+ backgroundColor: string;
186
+ alignItems: string;
187
+ justifyContent: string;
188
+ shadowColor: string;
189
+ shadowOffset: {
190
+ width: number;
191
+ height: number;
192
+ };
193
+ shadowOpacity: number;
194
+ shadowRadius: number;
195
+ elevation: number;
196
+ zIndex: number;
197
+ };
177
198
  detailScroll: {
178
199
  flex: number;
179
200
  };
@@ -215,6 +236,22 @@ export declare const getRawStyles: (colors: typeof AppColors) => {
215
236
  borderRadius: number;
216
237
  backgroundColor: string;
217
238
  };
239
+ fabShineClip: {
240
+ position: string;
241
+ width: number;
242
+ height: number;
243
+ borderRadius: number;
244
+ overflow: string;
245
+ };
246
+ fabShineStreak: {
247
+ position: string;
248
+ top: number;
249
+ width: number;
250
+ height: number;
251
+ transform: {
252
+ rotate: string;
253
+ }[];
254
+ };
218
255
  fab: {
219
256
  width: number;
220
257
  height: number;
@@ -1420,6 +1457,9 @@ export declare const getRawStyles: (colors: typeof AppColors) => {
1420
1457
  };
1421
1458
  headerGradient: {
1422
1459
  width: string;
1460
+ borderTopLeftRadius: number;
1461
+ borderTopRightRadius: number;
1462
+ overflow: string;
1423
1463
  };
1424
1464
  statusChip: {
1425
1465
  paddingHorizontal: number;
@@ -62,8 +62,9 @@ const getRawStyles = (colors) => ({
62
62
  headerTitle: {
63
63
  fontFamily: AppFonts_1.AppFonts.interBold,
64
64
  color: colors.primaryLight,
65
- fontSize: 18,
65
+ fontSize: 15,
66
66
  letterSpacing: 0.3,
67
+ paddingBottom: 4,
67
68
  },
68
69
  headerButtonGroup: {
69
70
  flexDirection: 'row',
@@ -187,6 +188,24 @@ const getRawStyles = (colors) => ({
187
188
  borderColor: 'rgba(255, 255, 255, 0.08)',
188
189
  },
189
190
  listContent: { paddingBottom: 12 },
191
+ // #2 — scroll-to-top button, always shown at the bottom right.
192
+ scrollTopBtn: {
193
+ position: 'absolute',
194
+ bottom: 25,
195
+ right: 15,
196
+ width: 38,
197
+ height: 38,
198
+ borderRadius: 19,
199
+ backgroundColor: colors.purple,
200
+ alignItems: 'center',
201
+ justifyContent: 'center',
202
+ shadowColor: '#000000',
203
+ shadowOffset: { width: 0, height: 3 },
204
+ shadowOpacity: 0.25,
205
+ shadowRadius: 5,
206
+ elevation: 6,
207
+ zIndex: 50,
208
+ },
190
209
  detailScroll: { flex: 1 },
191
210
  detailContent: { paddingHorizontal: 6, paddingTop: 8, paddingBottom: 20 },
192
211
  fabWrapper: {
@@ -219,6 +238,22 @@ const getRawStyles = (colors) => ({
219
238
  borderRadius: 30,
220
239
  backgroundColor: `${colors.purple}25`,
221
240
  },
241
+ // #4 — circular clipping mask for the FAB shine sweep.
242
+ fabShineClip: {
243
+ position: 'absolute',
244
+ width: 62,
245
+ height: 62,
246
+ borderRadius: 31,
247
+ overflow: 'hidden',
248
+ },
249
+ // #4 — diagonal light streak that sweeps across the launcher.
250
+ fabShineStreak: {
251
+ position: 'absolute',
252
+ top: -20,
253
+ width: 26,
254
+ height: 102,
255
+ transform: [{ rotate: '25deg' }],
256
+ },
222
257
  fab: {
223
258
  width: 56,
224
259
  height: 56,
@@ -1119,7 +1154,12 @@ const getRawStyles = (colors) => ({
1119
1154
  color: colors.grayTextWeak,
1120
1155
  fontSize: 11,
1121
1156
  },
1122
- diffBlock: { paddingTop: 12, paddingHorizontal: 12, paddingBottom: 16, gap: 4 },
1157
+ diffBlock: {
1158
+ paddingTop: 12,
1159
+ paddingHorizontal: 12,
1160
+ paddingBottom: 16,
1161
+ gap: 4,
1162
+ },
1123
1163
  diffAdded: {
1124
1164
  color: colors.greenColor,
1125
1165
  fontFamily: react_native_1.Platform.OS === 'ios' ? 'Menlo' : 'monospace',
@@ -1321,6 +1361,9 @@ const getRawStyles = (colors) => ({
1321
1361
  },
1322
1362
  headerGradient: {
1323
1363
  width: '100%',
1364
+ borderTopLeftRadius: 20,
1365
+ borderTopRightRadius: 20,
1366
+ overflow: 'hidden',
1324
1367
  },
1325
1368
  // Status chip used in MetaAccordion
1326
1369
  statusChip: {
@@ -7,6 +7,8 @@ export interface ConsoleLog {
7
7
  timestamp: number;
8
8
  caller?: string;
9
9
  sourceMethod?: 'log' | 'info' | 'warn' | 'error';
10
+ /** #9 — number of consecutive identical logs collapsed into this entry. */
11
+ duplicateCount?: number;
10
12
  }
11
13
  export interface AnalyticsEvent {
12
14
  id: number;
@@ -36,6 +38,8 @@ export interface NetworkLog {
36
38
  response?: unknown;
37
39
  requestHeaders?: Record<string, string>;
38
40
  responseHeaders?: Record<string, string>;
41
+ /** #9 — number of consecutive identical requests collapsed into this entry. */
42
+ duplicateCount?: number;
39
43
  }
40
44
  export interface RouteInfo {
41
45
  path: string;
@@ -281,6 +281,24 @@ export const ConsoleLogCard = React.memo(function ConsoleLogCard({ item, searchS
281
281
  : `Object{${Object.keys(jsonContent.data).length}}`}
282
282
  </Text>
283
283
  </View>)}
284
+ {/* #9 — collapsed duplicate counter */}
285
+ {'duplicateCount' in item &&
286
+ item.duplicateCount != null &&
287
+ item.duplicateCount > 1 && (<View style={[
288
+ styles.badge,
289
+ {
290
+ backgroundColor: 'rgba(104, 75, 155, 0.1)',
291
+ borderColor: 'rgba(104, 75, 155, 0.25)',
292
+ borderWidth: 1,
293
+ },
294
+ ]}>
295
+ <Text style={[
296
+ styles.badgeText,
297
+ { color: AppColors.purple, fontWeight: '700' },
298
+ ]}>
299
+ ×{item.duplicateCount}
300
+ </Text>
301
+ </View>)}
284
302
  {isAnalyticsError && (<View style={[
285
303
  styles.badge,
286
304
  { backgroundColor: `${AppColors.skyBlue}15` },
@@ -1,8 +1,9 @@
1
1
  import React from 'react';
2
- declare const JsonViewer: ({ data, search, forceOpen, defaultExpandDepth, }: {
2
+ declare const JsonViewer: ({ data, search, forceOpen, defaultExpandDepth, wrap, }: {
3
3
  data: unknown;
4
4
  search?: string;
5
5
  forceOpen?: boolean;
6
6
  defaultExpandDepth?: number;
7
+ wrap?: boolean;
7
8
  }) => React.JSX.Element;
8
9
  export default JsonViewer;
@@ -4,11 +4,13 @@ import { ScrollView, View } from 'react-native';
4
4
  import TreeNode from './TreeNode';
5
5
  // Stylesheet
6
6
  import styles from '../styles';
7
- const JsonViewer = ({ data, search, forceOpen, defaultExpandDepth, }) => {
7
+ const JsonViewer = ({ data, search, forceOpen, defaultExpandDepth, wrap, }) => {
8
+ const tree = (<TreeNode data={data} search={search} forceOpen={forceOpen} defaultExpandDepth={defaultExpandDepth}/>);
9
+ if (wrap) {
10
+ return <View style={[styles.codeBlock, { width: '100%' }]}>{tree}</View>;
11
+ }
8
12
  return (<ScrollView horizontal showsHorizontalScrollIndicator={true} style={styles.codeBlockScroll}>
9
- <View style={styles.codeBlock}>
10
- <TreeNode data={data} search={search} forceOpen={forceOpen} defaultExpandDepth={defaultExpandDepth}/>
11
- </View>
13
+ <View style={styles.codeBlock}>{tree}</View>
12
14
  </ScrollView>);
13
15
  };
14
16
  export default JsonViewer;
@@ -78,6 +78,23 @@ const LogCard = React.memo(function LogCard({ item, onPress, timelineMinStart, t
78
78
  .json
79
79
  </Text>
80
80
  </View>)}
81
+
82
+ {/* #9 — collapsed duplicate counter */}
83
+ {item.duplicateCount != null && item.duplicateCount > 1 && (<View style={[
84
+ styles.chip,
85
+ {
86
+ backgroundColor: `${AppColors.purple}15`,
87
+ borderColor: `${AppColors.purple}30`,
88
+ marginLeft: 6,
89
+ },
90
+ ]}>
91
+ <Text style={[
92
+ styles.chipText,
93
+ { color: AppColors.purple, fontWeight: '700' },
94
+ ]}>
95
+ ×{item.duplicateCount}
96
+ </Text>
97
+ </View>)}
81
98
  </View>
82
99
 
83
100
  <View style={styles.cardBottomRow}>
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { View, Pressable, Text, Animated } from 'react-native';
2
+ import { View, Pressable, Text, Animated, Linking } from 'react-native';
3
3
  // Constants
4
4
  import { DURATION_FAST_MS, DURATION_SLOW_MS } from '../constants';
5
5
  // Custom Hooks
@@ -40,7 +40,7 @@ const MetaAccordion = ({ status, statusColor, duration, size, triggeredAt, metho
40
40
  </Text>
41
41
  </View>
42
42
  <View style={styles.metaDivider}/>
43
-
43
+
44
44
  <View style={styles.metaRow}>
45
45
  <View style={styles.metaLabelRow}>
46
46
  <TerminalIcon color={AppColors.grayTextWeak} size={14}/>
@@ -51,9 +51,12 @@ const MetaAccordion = ({ status, statusColor, duration, size, triggeredAt, metho
51
51
  {
52
52
  borderColor: 'rgba(107, 78, 255, 0.25)',
53
53
  backgroundColor: 'rgba(107, 78, 255, 0.08)',
54
- }
54
+ },
55
+ ]}>
56
+ <Text style={[
57
+ styles.statusText,
58
+ { color: AppColors.purple, fontFamily: AppFonts.interBold },
55
59
  ]}>
56
- <Text style={[styles.statusText, { color: AppColors.purple, fontFamily: AppFonts.interBold }]}>
57
60
  {method || 'GET'}
58
61
  </Text>
59
62
  </View>
@@ -91,7 +94,9 @@ const MetaAccordion = ({ status, statusColor, duration, size, triggeredAt, metho
91
94
  <GlobeIcon color={AppColors.grayTextWeak} size={14}/>
92
95
  <Text style={styles.metaLabel}>Content Type</Text>
93
96
  </View>
94
- <Text style={styles.metaValue}>{contentType || 'application/json'}</Text>
97
+ <Text style={styles.metaValue}>
98
+ {contentType || 'application/json'}
99
+ </Text>
95
100
  </View>
96
101
  <View style={styles.metaDivider}/>
97
102
 
@@ -125,7 +130,7 @@ const MetaAccordion = ({ status, statusColor, duration, size, triggeredAt, metho
125
130
  </View>
126
131
  </View>
127
132
  <View style={styles.metaDivider}/>
128
-
133
+
129
134
  <View style={styles.metaRow}>
130
135
  <View style={styles.metaLabelRow}>
131
136
  <SizeIcon color={AppColors.grayTextWeak}/>
@@ -140,7 +145,22 @@ const MetaAccordion = ({ status, statusColor, duration, size, triggeredAt, metho
140
145
  <GlobeIcon color={AppColors.grayTextWeak} size={14}/>
141
146
  <Text style={styles.metaLabel}>Full URL</Text>
142
147
  </View>
143
- <Text selectable={true} numberOfLines={3} ellipsizeMode="tail" style={[styles.metaValue, { fontSize: 11.5, color: AppColors.grayTextWeak, flex: 1, textAlign: 'right', lineHeight: 16 }]}>
148
+ <Text selectable={true} numberOfLines={3} ellipsizeMode="tail" onPress={() => {
149
+ if (url) {
150
+ Linking.openURL(url).catch(() => { });
151
+ }
152
+ }} style={[
153
+ styles.metaValue,
154
+ {
155
+ fontSize: 11.5,
156
+ color: url ? AppColors.purple : AppColors.grayTextWeak,
157
+ textDecorationLine: url ? 'underline' : 'none',
158
+ fontFamily: AppFonts.interMedium,
159
+ flex: 1,
160
+ textAlign: 'right',
161
+ lineHeight: 16,
162
+ },
163
+ ]}>
144
164
  {url || '—'}
145
165
  </Text>
146
166
  </View>
@@ -40,5 +40,12 @@ export declare const HtmlIcon: ({ color, size }: any) => React.JSX.Element;
40
40
  export declare const CssIcon: ({ color, size }: any) => React.JSX.Element;
41
41
  export declare const JsIcon: ({ color, size }: any) => React.JSX.Element;
42
42
  export declare const EyeIcon: ({ color, size }: any) => React.JSX.Element;
43
- export declare const SettingsIcon: ({ color, size }: any) => React.JSX.Element;
44
- export declare const FolderIcon: ({ color, size }: any) => React.JSX.Element;
43
+ export declare const SettingsIcon: ({ color, size, }: any) => React.JSX.Element;
44
+ export declare const FolderIcon: ({ color, size, }: any) => React.JSX.Element;
45
+ export declare const WipeIcon: ({ color, size }: any) => React.JSX.Element;
46
+ export declare const LayersIcon: ({ color, size, }: any) => React.JSX.Element;
47
+ export declare const UserIcon: ({ color, size }: any) => React.JSX.Element;
48
+ export declare const InfoCircleIcon: ({ color, size, }: any) => React.JSX.Element;
49
+ export declare const WarningTriangleIcon: ({ color, size, }: any) => React.JSX.Element;
50
+ export declare const ErrorCircleIcon: ({ color, size, }: any) => React.JSX.Element;
51
+ export declare const TrendingUpIcon: ({ color, size, }: any) => React.JSX.Element;
@@ -237,14 +237,63 @@ export const EyeIcon = ({ color = AppColors.grayTextWeak, size = 14 }) => {
237
237
  <Circle cx="12" cy="12" r="3" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
238
238
  </Svg>);
239
239
  };
240
- export const SettingsIcon = ({ color = AppColors.grayTextWeak, size = 14 }) => {
240
+ export const SettingsIcon = ({ color = AppColors.grayTextWeak, size = 14, }) => {
241
241
  return (<Svg width={size} height={size} viewBox="0 0 24 24" fill="none">
242
242
  <Path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
243
243
  <Circle cx="12" cy="12" r="3" stroke={color} strokeWidth="2"/>
244
244
  </Svg>);
245
245
  };
246
- export const FolderIcon = ({ color = AppColors.grayTextWeak, size = 14 }) => {
246
+ export const FolderIcon = ({ color = AppColors.grayTextWeak, size = 14, }) => {
247
247
  return (<Svg width={size} height={size} viewBox="0 0 24 24" fill="none">
248
248
  <Path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
249
249
  </Svg>);
250
250
  };
251
+ // #3 — Broom/sweep "wipe" icon used by the header Clear-Everything button.
252
+ export const WipeIcon = ({ color = '#FFFFFF', size = 16 }) => {
253
+ return (<Svg width={size} height={size} viewBox="0 0 24 24" fill="none">
254
+ {/* Handle */}
255
+ <Path d="M19.5 3.5L12.7 10.3" stroke={color} strokeWidth="2" strokeLinecap="round"/>
256
+ {/* Brush head */}
257
+ <Path d="M13.5 9.5l1.5 1.5c.8.8.8 2 0 2.8L10 19l-5-5 5.2-5.5c.8-.8 2-.8 2.8 0z" stroke={color} strokeWidth="2" strokeLinejoin="round"/>
258
+ {/* Bristle strokes */}
259
+ <Path d="M7.5 16.5L5.5 18.5M10 19l-1.5 1.5" stroke={color} strokeWidth="2" strokeLinecap="round"/>
260
+ {/* Dust sparks */}
261
+ <Path d="M3 11.5h.01M5.5 8.5h.01" stroke={color} strokeWidth="2.4" strokeLinecap="round"/>
262
+ </Svg>);
263
+ };
264
+ // #7 — Icons for the inner filter chips / sub tabs.
265
+ export const LayersIcon = ({ color = AppColors.grayTextWeak, size = 12, }) => {
266
+ return (<Svg width={size} height={size} viewBox="0 0 24 24" fill="none">
267
+ <Path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
268
+ </Svg>);
269
+ };
270
+ export const UserIcon = ({ color = AppColors.grayTextWeak, size = 12 }) => {
271
+ return (<Svg width={size} height={size} viewBox="0 0 24 24" fill="none">
272
+ <Path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
273
+ <Circle cx="12" cy="7" r="4" stroke={color} strokeWidth="2"/>
274
+ </Svg>);
275
+ };
276
+ export const InfoCircleIcon = ({ color = AppColors.grayTextWeak, size = 12, }) => {
277
+ return (<Svg width={size} height={size} viewBox="0 0 24 24" fill="none">
278
+ <Circle cx="12" cy="12" r="10" stroke={color} strokeWidth="2"/>
279
+ <Path d="M12 16v-4M12 8h.01" stroke={color} strokeWidth="2" strokeLinecap="round"/>
280
+ </Svg>);
281
+ };
282
+ export const WarningTriangleIcon = ({ color = AppColors.grayTextWeak, size = 12, }) => {
283
+ return (<Svg width={size} height={size} viewBox="0 0 24 24" fill="none">
284
+ <Path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
285
+ <Path d="M12 9v4M12 17h.01" stroke={color} strokeWidth="2" strokeLinecap="round"/>
286
+ </Svg>);
287
+ };
288
+ export const ErrorCircleIcon = ({ color = AppColors.grayTextWeak, size = 12, }) => {
289
+ return (<Svg width={size} height={size} viewBox="0 0 24 24" fill="none">
290
+ <Circle cx="12" cy="12" r="10" stroke={color} strokeWidth="2"/>
291
+ <Path d="M15 9l-6 6M9 9l6 6" stroke={color} strokeWidth="2" strokeLinecap="round"/>
292
+ </Svg>);
293
+ };
294
+ export const TrendingUpIcon = ({ color = AppColors.grayTextWeak, size = 12, }) => {
295
+ return (<Svg width={size} height={size} viewBox="0 0 24 24" fill="none">
296
+ <Path d="M23 6l-9.5 9.5-5-5L1 18" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
297
+ <Path d="M17 6h6v6" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
298
+ </Svg>);
299
+ };
@@ -2,9 +2,47 @@ import React, { useEffect, useRef, useState } from 'react';
2
2
  import { Animated, LayoutAnimation, Platform, Pressable, StyleSheet, Text, UIManager, View, } from 'react-native';
3
3
  import { AppColors } from '../styles/AppColors';
4
4
  import { AppFonts } from '../styles/AppFonts';
5
- import { ChevronIcon } from './NetworkIcons';
5
+ import { ChevronIcon, CopyIcon, CheckIcon } from './NetworkIcons';
6
6
  import Svg, { Path } from 'react-native-svg';
7
7
  import AnimatedEntrance from './AnimatedEntrance';
8
+ import { copyToClipboard } from '../helpers';
9
+ // #15 — copy-to-clipboard control for a single dispatched action
10
+ const ActionCopyButton = ({ value }) => {
11
+ const [copied, setCopied] = useState(false);
12
+ return (<Pressable hitSlop={10} onPress={() => {
13
+ copyToClipboard(value(), 'Action');
14
+ setCopied(true);
15
+ setTimeout(() => setCopied(false), 1200);
16
+ }} style={{
17
+ width: 26,
18
+ height: 26,
19
+ borderRadius: 7,
20
+ alignItems: 'center',
21
+ justifyContent: 'center',
22
+ backgroundColor: copied
23
+ ? `${AppColors.greenColor}1A`
24
+ : AppColors.grayBackground,
25
+ borderWidth: 1,
26
+ borderColor: AppColors.dividerColor,
27
+ }}>
28
+ {copied ? (<CheckIcon color={AppColors.greenColor} size={13}/>) : (<CopyIcon color={AppColors.grayTextWeak} size={13}/>)}
29
+ </Pressable>);
30
+ };
31
+ // #15 — derive the redux module / folder name from an action type.
32
+ // Supports RTK slice convention ("booking/setDate" -> "Booking") and
33
+ // falls back to the first affected slice.
34
+ const getActionModule = (type, affectedSlices) => {
35
+ if (type && type.includes('/')) {
36
+ const prefix = type.split('/')[0];
37
+ if (prefix)
38
+ return prefix.charAt(0).toUpperCase() + prefix.slice(1);
39
+ }
40
+ if (affectedSlices && affectedSlices.length > 0) {
41
+ const s = affectedSlices[0];
42
+ return s.charAt(0).toUpperCase() + s.slice(1);
43
+ }
44
+ return null;
45
+ };
8
46
  // Custom icons
9
47
  const DatabaseIcon = ({ color = AppColors.grayTextWeak, size = 12 }) => (<Svg width={size} height={size} viewBox="0 0 24 24" fill="none">
10
48
  <Path d="M12 2C6.5 2 2 4.2 2 7v10c0 2.8 4.5 5 10 5s10-2.2 10-5V7c0-2.8-4.5-5-10-5z" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
@@ -309,12 +347,49 @@ export const ReduxActionTimeline = ({ history, onClear, search, }) => {
309
347
  },
310
348
  ]}>
311
349
  <View style={timelineStyles.cardHeader}>
312
- <View style={timelineStyles.typeBadge}>
313
- <Text style={timelineStyles.typeText}>{item.type}</Text>
350
+ <View style={{
351
+ flexDirection: 'row',
352
+ alignItems: 'center',
353
+ gap: 6,
354
+ flex: 1,
355
+ }}>
356
+ {(() => {
357
+ const moduleName = getActionModule(item.type, item.affectedSlices);
358
+ return moduleName ? (<View style={{
359
+ paddingHorizontal: 7,
360
+ paddingVertical: 2,
361
+ borderRadius: 6,
362
+ backgroundColor: `${AppColors.purple}14`,
363
+ borderWidth: 1,
364
+ borderColor: `${AppColors.purple}33`,
365
+ }}>
366
+ <Text style={{
367
+ fontFamily: AppFonts.interBold,
368
+ fontSize: 9.5,
369
+ color: AppColors.purple,
370
+ letterSpacing: 0.2,
371
+ }}>
372
+ {moduleName}
373
+ </Text>
374
+ </View>) : null;
375
+ })()}
376
+ <View style={timelineStyles.typeBadge}>
377
+ <Text style={timelineStyles.typeText}>{item.type}</Text>
378
+ </View>
379
+ </View>
380
+ <View style={{
381
+ flexDirection: 'row',
382
+ alignItems: 'center',
383
+ gap: 8,
384
+ }}>
385
+ <Text style={timelineStyles.timestamp}>
386
+ {item.timestamp}
387
+ </Text>
388
+ <ActionCopyButton value={() => ({
389
+ type: item.type,
390
+ payload: item.payload,
391
+ })}/>
314
392
  </View>
315
- <Text style={timelineStyles.timestamp}>
316
- {item.timestamp}
317
- </Text>
318
393
  </View>
319
394
 
320
395
  {item.affectedSlices.length > 0 && (<View style={timelineStyles.slicesRow}>
@@ -1 +1 @@
1
- export declare const LIB_VERSION = "1.1.1";
1
+ export declare const LIB_VERSION = "1.1.3";
@@ -1,3 +1,3 @@
1
1
  // AUTO-GENERATED FILE — do not edit by hand.
2
2
  // Regenerated from package.json on every build by scripts/gen-version.js.
3
- export const LIB_VERSION = '1.1.1';
3
+ export const LIB_VERSION = '1.1.3';
@@ -1,16 +1,30 @@
1
- export declare const getReduxState: () => any;
2
- export declare const setReduxAutoRefresh: (val: boolean) => void;
3
- export declare const getReduxAutoRefresh: () => boolean;
4
- export declare const getLastActionForReducer: () => Record<string, any>;
5
- export declare const clearLastActionForReducer: () => void;
6
- export declare const getActionHistory: () => {
1
+ export interface ReduxHistoryEntry {
7
2
  id: number;
8
3
  type: string;
9
4
  payload: any;
10
5
  timestamp: string;
11
6
  affectedSlices: string[];
12
- }[];
7
+ }
8
+ export declare const getReduxState: () => any;
9
+ export declare const setReduxAutoRefresh: (val: boolean) => void;
10
+ export declare const getReduxAutoRefresh: () => boolean;
11
+ export declare const getLastActionForReducer: () => Record<string, any>;
12
+ export declare const clearLastActionForReducer: () => void;
13
+ export declare const getActionHistory: () => ReduxHistoryEntry[];
13
14
  export declare const clearActionHistory: () => void;
14
15
  export declare const setReduxState: (state: any) => void;
15
16
  export declare const subscribeReduxState: (cb: () => void) => () => void;
17
+ /**
18
+ * Standard Redux middleware — add it to your store to capture every action,
19
+ * including those dispatched from thunks, sagas and RTK Query:
20
+ *
21
+ * const store = configureStore({
22
+ * reducer,
23
+ * middleware: gDM => gDM().concat(inspectorReduxMiddleware),
24
+ * });
25
+ *
26
+ * Pair with `connectReduxStore(store)` (safe — they de-duplicate) or rely on
27
+ * the middleware alone; the initial snapshot is taken on the first action.
28
+ */
29
+ export declare const inspectorReduxMiddleware: (storeApi: any) => (next: (action: any) => any) => (action: any) => any;
16
30
  export declare const connectReduxStore: (store: any) => void;