react-native-debug-toolkit 2.0.0 → 2.1.0

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 (228) hide show
  1. package/README.md +72 -72
  2. package/README.zh-CN.md +209 -0
  3. package/lib/commonjs/components/ClipboardTab.js +15 -16
  4. package/lib/commonjs/components/ClipboardTab.js.map +1 -1
  5. package/lib/commonjs/components/ConsoleLogTab.js +69 -202
  6. package/lib/commonjs/components/ConsoleLogTab.js.map +1 -1
  7. package/lib/commonjs/components/DebugPanel.js +230 -0
  8. package/lib/commonjs/components/DebugPanel.js.map +1 -0
  9. package/lib/commonjs/components/DebugView.js +66 -0
  10. package/lib/commonjs/components/DebugView.js.map +1 -0
  11. package/lib/commonjs/components/EnvironmentTab.js +22 -23
  12. package/lib/commonjs/components/EnvironmentTab.js.map +1 -1
  13. package/lib/commonjs/components/FeatureTabBar.js +182 -0
  14. package/lib/commonjs/components/FeatureTabBar.js.map +1 -0
  15. package/lib/commonjs/components/FloatIcon.js +187 -0
  16. package/lib/commonjs/components/FloatIcon.js.map +1 -0
  17. package/lib/commonjs/components/FloatPanelView.js +140 -723
  18. package/lib/commonjs/components/FloatPanelView.js.map +1 -1
  19. package/lib/commonjs/components/NavigationLogTab.js +8 -8
  20. package/lib/commonjs/components/NavigationLogTab.js.map +1 -1
  21. package/lib/commonjs/components/NetworkLogTab.js +179 -350
  22. package/lib/commonjs/components/NetworkLogTab.js.map +1 -1
  23. package/lib/commonjs/components/ThirdPartyLibsTab.js +8 -8
  24. package/lib/commonjs/components/ThirdPartyLibsTab.js.map +1 -1
  25. package/lib/commonjs/components/TrackLogTab.js +106 -248
  26. package/lib/commonjs/components/TrackLogTab.js.map +1 -1
  27. package/lib/commonjs/components/ZustandLogTab.js +148 -288
  28. package/lib/commonjs/components/ZustandLogTab.js.map +1 -1
  29. package/lib/commonjs/components/shared/CollapsibleSection.js +4 -4
  30. package/lib/commonjs/components/shared/CollapsibleSection.js.map +1 -1
  31. package/lib/commonjs/components/shared/CopyButton.js +3 -3
  32. package/lib/commonjs/components/shared/CopyButton.js.map +1 -1
  33. package/lib/commonjs/components/shared/LogListScreen.js +174 -0
  34. package/lib/commonjs/components/shared/LogListScreen.js.map +1 -0
  35. package/lib/commonjs/core/DebugToolkit.js +92 -112
  36. package/lib/commonjs/core/DebugToolkit.js.map +1 -1
  37. package/lib/commonjs/core/DebugToolkitProvider.js +30 -28
  38. package/lib/commonjs/core/DebugToolkitProvider.js.map +1 -1
  39. package/lib/commonjs/features/ClipboardFeature.js +6 -2
  40. package/lib/commonjs/features/ClipboardFeature.js.map +1 -1
  41. package/lib/commonjs/features/ConsoleLogFeature.js +66 -49
  42. package/lib/commonjs/features/ConsoleLogFeature.js.map +1 -1
  43. package/lib/commonjs/features/EnvironmentFeature.js +5 -13
  44. package/lib/commonjs/features/EnvironmentFeature.js.map +1 -1
  45. package/lib/commonjs/features/NavigationLogFeature.js +17 -38
  46. package/lib/commonjs/features/NavigationLogFeature.js.map +1 -1
  47. package/lib/commonjs/features/NetworkFeature.js +35 -256
  48. package/lib/commonjs/features/NetworkFeature.js.map +1 -1
  49. package/lib/commonjs/features/TrackFeature.js +17 -38
  50. package/lib/commonjs/features/TrackFeature.js.map +1 -1
  51. package/lib/commonjs/features/ZustandLogFeature.js +21 -38
  52. package/lib/commonjs/features/ZustandLogFeature.js.map +1 -1
  53. package/lib/commonjs/hooks/useNavigationLogger.js.map +1 -1
  54. package/lib/commonjs/index.js +7 -20
  55. package/lib/commonjs/index.js.map +1 -1
  56. package/lib/commonjs/initialize.js +19 -96
  57. package/lib/commonjs/initialize.js.map +1 -1
  58. package/lib/commonjs/interceptors/networkInterceptor.js +466 -0
  59. package/lib/commonjs/interceptors/networkInterceptor.js.map +1 -0
  60. package/lib/commonjs/utils/colors.js +48 -0
  61. package/lib/commonjs/utils/colors.js.map +1 -0
  62. package/lib/commonjs/utils/createChannelFeature.js +48 -0
  63. package/lib/commonjs/utils/createChannelFeature.js.map +1 -0
  64. package/lib/commonjs/utils/layout.js +8 -0
  65. package/lib/commonjs/utils/layout.js.map +1 -0
  66. package/lib/commonjs/utils/urlRewriterRegistry.js +14 -0
  67. package/lib/commonjs/utils/urlRewriterRegistry.js.map +1 -0
  68. package/lib/module/components/ClipboardTab.js +8 -8
  69. package/lib/module/components/ClipboardTab.js.map +1 -1
  70. package/lib/module/components/ConsoleLogTab.js +66 -199
  71. package/lib/module/components/ConsoleLogTab.js.map +1 -1
  72. package/lib/module/components/DebugPanel.js +225 -0
  73. package/lib/module/components/DebugPanel.js.map +1 -0
  74. package/lib/module/components/DebugView.js +61 -0
  75. package/lib/module/components/DebugView.js.map +1 -0
  76. package/lib/module/components/EnvironmentTab.js +3 -3
  77. package/lib/module/components/EnvironmentTab.js.map +1 -1
  78. package/lib/module/components/FeatureTabBar.js +177 -0
  79. package/lib/module/components/FeatureTabBar.js.map +1 -0
  80. package/lib/module/components/FloatIcon.js +182 -0
  81. package/lib/module/components/FloatIcon.js.map +1 -0
  82. package/lib/module/components/FloatPanelView.js +141 -723
  83. package/lib/module/components/FloatPanelView.js.map +1 -1
  84. package/lib/module/components/NavigationLogTab.js +1 -1
  85. package/lib/module/components/NavigationLogTab.js.map +1 -1
  86. package/lib/module/components/NetworkLogTab.js +167 -338
  87. package/lib/module/components/NetworkLogTab.js.map +1 -1
  88. package/lib/module/components/ThirdPartyLibsTab.js +1 -1
  89. package/lib/module/components/ThirdPartyLibsTab.js.map +1 -1
  90. package/lib/module/components/TrackLogTab.js +94 -236
  91. package/lib/module/components/TrackLogTab.js.map +1 -1
  92. package/lib/module/components/ZustandLogTab.js +136 -276
  93. package/lib/module/components/ZustandLogTab.js.map +1 -1
  94. package/lib/module/components/shared/CollapsibleSection.js +1 -1
  95. package/lib/module/components/shared/CollapsibleSection.js.map +1 -1
  96. package/lib/module/components/shared/CopyButton.js +1 -1
  97. package/lib/module/components/shared/CopyButton.js.map +1 -1
  98. package/lib/module/components/shared/LogListScreen.js +169 -0
  99. package/lib/module/components/shared/LogListScreen.js.map +1 -0
  100. package/lib/module/core/DebugToolkit.js +92 -111
  101. package/lib/module/core/DebugToolkit.js.map +1 -1
  102. package/lib/module/core/DebugToolkitProvider.js +31 -29
  103. package/lib/module/core/DebugToolkitProvider.js.map +1 -1
  104. package/lib/module/features/ClipboardFeature.js +6 -2
  105. package/lib/module/features/ClipboardFeature.js.map +1 -1
  106. package/lib/module/features/ConsoleLogFeature.js +65 -49
  107. package/lib/module/features/ConsoleLogFeature.js.map +1 -1
  108. package/lib/module/features/EnvironmentFeature.js +5 -13
  109. package/lib/module/features/EnvironmentFeature.js.map +1 -1
  110. package/lib/module/features/NavigationLogFeature.js +16 -38
  111. package/lib/module/features/NavigationLogFeature.js.map +1 -1
  112. package/lib/module/features/NetworkFeature.js +34 -255
  113. package/lib/module/features/NetworkFeature.js.map +1 -1
  114. package/lib/module/features/TrackFeature.js +16 -38
  115. package/lib/module/features/TrackFeature.js.map +1 -1
  116. package/lib/module/features/ZustandLogFeature.js +22 -38
  117. package/lib/module/features/ZustandLogFeature.js.map +1 -1
  118. package/lib/module/hooks/useNavigationLogger.js.map +1 -1
  119. package/lib/module/index.js +2 -3
  120. package/lib/module/index.js.map +1 -1
  121. package/lib/module/initialize.js +19 -95
  122. package/lib/module/initialize.js.map +1 -1
  123. package/lib/module/interceptors/networkInterceptor.js +460 -0
  124. package/lib/module/interceptors/networkInterceptor.js.map +1 -0
  125. package/lib/module/utils/colors.js +43 -0
  126. package/lib/module/utils/colors.js.map +1 -0
  127. package/lib/module/utils/createChannelFeature.js +44 -0
  128. package/lib/module/utils/createChannelFeature.js.map +1 -0
  129. package/lib/module/utils/layout.js +4 -0
  130. package/lib/module/utils/layout.js.map +1 -0
  131. package/lib/module/utils/urlRewriterRegistry.js +10 -0
  132. package/lib/module/utils/urlRewriterRegistry.js.map +1 -0
  133. package/lib/typescript/src/components/ClipboardTab.d.ts.map +1 -1
  134. package/lib/typescript/src/components/ConsoleLogTab.d.ts.map +1 -1
  135. package/lib/typescript/src/components/DebugPanel.d.ts +9 -0
  136. package/lib/typescript/src/components/DebugPanel.d.ts.map +1 -0
  137. package/lib/typescript/src/components/DebugView.d.ts +19 -0
  138. package/lib/typescript/src/components/DebugView.d.ts.map +1 -0
  139. package/lib/typescript/src/components/EnvironmentTab.d.ts.map +1 -1
  140. package/lib/typescript/src/components/FeatureTabBar.d.ts +13 -0
  141. package/lib/typescript/src/components/FeatureTabBar.d.ts.map +1 -0
  142. package/lib/typescript/src/components/FloatIcon.d.ts +12 -0
  143. package/lib/typescript/src/components/FloatIcon.d.ts.map +1 -0
  144. package/lib/typescript/src/components/FloatPanelView.d.ts +4 -59
  145. package/lib/typescript/src/components/FloatPanelView.d.ts.map +1 -1
  146. package/lib/typescript/src/components/NetworkLogTab.d.ts.map +1 -1
  147. package/lib/typescript/src/components/ThirdPartyLibsTab.d.ts.map +1 -1
  148. package/lib/typescript/src/components/TrackLogTab.d.ts.map +1 -1
  149. package/lib/typescript/src/components/ZustandLogTab.d.ts.map +1 -1
  150. package/lib/typescript/src/components/shared/LogListScreen.d.ts +21 -0
  151. package/lib/typescript/src/components/shared/LogListScreen.d.ts.map +1 -0
  152. package/lib/typescript/src/core/DebugToolkit.d.ts +11 -18
  153. package/lib/typescript/src/core/DebugToolkit.d.ts.map +1 -1
  154. package/lib/typescript/src/core/DebugToolkitProvider.d.ts +2 -5
  155. package/lib/typescript/src/core/DebugToolkitProvider.d.ts.map +1 -1
  156. package/lib/typescript/src/features/ClipboardFeature.d.ts +4 -0
  157. package/lib/typescript/src/features/ClipboardFeature.d.ts.map +1 -1
  158. package/lib/typescript/src/features/ConsoleLogFeature.d.ts +2 -0
  159. package/lib/typescript/src/features/ConsoleLogFeature.d.ts.map +1 -1
  160. package/lib/typescript/src/features/EnvironmentFeature.d.ts.map +1 -1
  161. package/lib/typescript/src/features/NavigationLogFeature.d.ts +2 -0
  162. package/lib/typescript/src/features/NavigationLogFeature.d.ts.map +1 -1
  163. package/lib/typescript/src/features/NetworkFeature.d.ts +7 -20
  164. package/lib/typescript/src/features/NetworkFeature.d.ts.map +1 -1
  165. package/lib/typescript/src/features/TrackFeature.d.ts +2 -0
  166. package/lib/typescript/src/features/TrackFeature.d.ts.map +1 -1
  167. package/lib/typescript/src/features/ZustandLogFeature.d.ts +2 -0
  168. package/lib/typescript/src/features/ZustandLogFeature.d.ts.map +1 -1
  169. package/lib/typescript/src/hooks/useNavigationLogger.d.ts +1 -8
  170. package/lib/typescript/src/hooks/useNavigationLogger.d.ts.map +1 -1
  171. package/lib/typescript/src/index.d.ts +5 -5
  172. package/lib/typescript/src/index.d.ts.map +1 -1
  173. package/lib/typescript/src/initialize.d.ts +3 -22
  174. package/lib/typescript/src/initialize.d.ts.map +1 -1
  175. package/lib/typescript/src/interceptors/networkInterceptor.d.ts +18 -0
  176. package/lib/typescript/src/interceptors/networkInterceptor.d.ts.map +1 -0
  177. package/lib/typescript/src/types/index.d.ts +26 -29
  178. package/lib/typescript/src/types/index.d.ts.map +1 -1
  179. package/lib/typescript/src/utils/colors.d.ts +21 -0
  180. package/lib/typescript/src/utils/colors.d.ts.map +1 -0
  181. package/lib/typescript/src/utils/createChannelFeature.d.ts +18 -0
  182. package/lib/typescript/src/utils/createChannelFeature.d.ts.map +1 -0
  183. package/lib/typescript/src/utils/layout.d.ts +2 -0
  184. package/lib/typescript/src/utils/layout.d.ts.map +1 -0
  185. package/lib/typescript/src/utils/urlRewriterRegistry.d.ts +7 -0
  186. package/lib/typescript/src/utils/urlRewriterRegistry.d.ts.map +1 -0
  187. package/package.json +5 -1
  188. package/src/components/ClipboardTab.tsx +8 -8
  189. package/src/components/ConsoleLogTab.tsx +49 -163
  190. package/src/components/DebugPanel.tsx +215 -0
  191. package/src/components/DebugView.tsx +80 -0
  192. package/src/components/EnvironmentTab.tsx +3 -3
  193. package/src/components/FeatureTabBar.tsx +204 -0
  194. package/src/components/FloatIcon.tsx +171 -0
  195. package/src/components/FloatPanelView.tsx +135 -647
  196. package/src/components/NavigationLogTab.tsx +1 -1
  197. package/src/components/NetworkLogTab.tsx +128 -269
  198. package/src/components/ThirdPartyLibsTab.tsx +3 -3
  199. package/src/components/TrackLogTab.tsx +53 -188
  200. package/src/components/ZustandLogTab.tsx +79 -181
  201. package/src/components/shared/CollapsibleSection.tsx +1 -1
  202. package/src/components/shared/CopyButton.tsx +1 -1
  203. package/src/components/shared/LogListScreen.tsx +164 -0
  204. package/src/core/DebugToolkit.tsx +114 -138
  205. package/src/core/DebugToolkitProvider.tsx +32 -38
  206. package/src/features/ClipboardFeature.ts +6 -2
  207. package/src/features/ConsoleLogFeature.ts +66 -68
  208. package/src/features/EnvironmentFeature.ts +5 -13
  209. package/src/features/NavigationLogFeature.ts +12 -42
  210. package/src/features/NetworkFeature.ts +43 -405
  211. package/src/features/TrackFeature.ts +14 -49
  212. package/src/features/ZustandLogFeature.ts +16 -42
  213. package/src/hooks/useNavigationLogger.ts +1 -6
  214. package/src/index.ts +5 -9
  215. package/src/initialize.ts +28 -120
  216. package/src/interceptors/networkInterceptor.ts +646 -0
  217. package/src/types/index.ts +25 -36
  218. package/src/utils/colors.ts +38 -0
  219. package/src/utils/createChannelFeature.ts +55 -0
  220. package/src/utils/layout.ts +1 -0
  221. package/src/utils/urlRewriterRegistry.ts +10 -0
  222. package/lib/commonjs/utils/constants.js +0 -135
  223. package/lib/commonjs/utils/constants.js.map +0 -1
  224. package/lib/module/utils/constants.js +0 -130
  225. package/lib/module/utils/constants.js.map +0 -1
  226. package/lib/typescript/src/utils/constants.d.ts +0 -96
  227. package/lib/typescript/src/utils/constants.d.ts.map +0 -1
  228. package/src/utils/constants.ts +0 -91
@@ -0,0 +1,164 @@
1
+ import React, { useMemo, useState } from 'react';
2
+ import {
3
+ View,
4
+ Text,
5
+ FlatList,
6
+ TouchableOpacity,
7
+ StyleSheet,
8
+ Animated,
9
+ } from 'react-native';
10
+ import { Colors } from '../../utils/colors';
11
+ import { useSlideDetailAnimation } from '../../hooks/useSlideDetailAnimation';
12
+
13
+ interface LogListItem {
14
+ id: string;
15
+ }
16
+
17
+ interface LogListScreenProps<T extends LogListItem> {
18
+ data: T[];
19
+ renderRow: (item: T) => React.ReactElement;
20
+ renderListHeader?: () => React.ReactElement;
21
+ renderDetailHeader?: (item: T) => React.ReactNode;
22
+ renderDetailBody: (item: T) => React.ReactElement;
23
+ emptyText: string;
24
+ reversed?: boolean;
25
+ }
26
+
27
+ /**
28
+ * Shared list→detail screen with slide animation.
29
+ * Handles: selected state, FlatList, empty state, back button, push navigation.
30
+ * Each tab provides renderRow, renderDetailBody, and optional renderDetailHeader.
31
+ */
32
+ export function LogListScreen<T extends LogListItem>({
33
+ data,
34
+ renderRow,
35
+ renderListHeader,
36
+ renderDetailHeader,
37
+ renderDetailBody,
38
+ emptyText,
39
+ reversed = true,
40
+ }: LogListScreenProps<T>) {
41
+ const [selected, setSelected] = useState<T | null>(null);
42
+ const { detailTranslateX, listTranslateX, listOpacity } = useSlideDetailAnimation(selected);
43
+
44
+ const displayData = useMemo(
45
+ () => (reversed ? [...data].reverse() : data),
46
+ [data, reversed],
47
+ );
48
+
49
+ return (
50
+ <View style={styles.container}>
51
+ {/* List */}
52
+ <Animated.View
53
+ style={[
54
+ styles.listWrap,
55
+ selected
56
+ ? { opacity: listOpacity, transform: [{ translateX: listTranslateX }] }
57
+ : null,
58
+ ]}
59
+ >
60
+ {renderListHeader?.()}
61
+ {displayData.length === 0 ? (
62
+ <View style={styles.emptyContainer}>
63
+ <Text style={styles.emptyIcon}>~</Text>
64
+ <Text style={styles.empty}>{emptyText}</Text>
65
+ </View>
66
+ ) : (
67
+ <FlatList
68
+ data={displayData}
69
+ renderItem={({ item }) => (
70
+ <TouchableOpacity
71
+ style={styles.card}
72
+ onPress={() => setSelected(item)}
73
+ activeOpacity={0.6}
74
+ >
75
+ {renderRow(item)}
76
+ </TouchableOpacity>
77
+ )}
78
+ keyExtractor={(item) => item.id}
79
+ contentContainerStyle={styles.listContent}
80
+ initialNumToRender={20}
81
+ maxToRenderPerBatch={10}
82
+ windowSize={5}
83
+ removeClippedSubviews={true}
84
+ />
85
+ )}
86
+ </Animated.View>
87
+
88
+ {/* Detail */}
89
+ {selected && (
90
+ <Animated.View
91
+ style={[styles.detailOverlay, { transform: [{ translateX: detailTranslateX }] }]}
92
+ >
93
+ <View style={styles.detailWrap}>
94
+ <View style={styles.detailHeader}>
95
+ <TouchableOpacity
96
+ onPress={() => setSelected(null)}
97
+ style={styles.backBtn}
98
+ activeOpacity={0.6}
99
+ >
100
+ <Text style={styles.backIcon}>‹</Text>
101
+ <Text style={styles.backText}>Back</Text>
102
+ </TouchableOpacity>
103
+ {renderDetailHeader?.(selected)}
104
+ </View>
105
+ {renderDetailBody(selected)}
106
+ </View>
107
+ </Animated.View>
108
+ )}
109
+ </View>
110
+ );
111
+ }
112
+
113
+ const styles = StyleSheet.create({
114
+ container: { flex: 1, backgroundColor: Colors.background },
115
+ listWrap: { position: 'absolute', top: 0, left: 0, right: 0, bottom: 0 },
116
+ listContent: { padding: 12 },
117
+ emptyContainer: { flex: 1, alignItems: 'center', justifyContent: 'center' },
118
+ emptyIcon: { fontSize: 40, color: Colors.textLight, marginBottom: 4 },
119
+ empty: { textAlign: 'center', color: Colors.textLight, fontSize: 14 },
120
+ card: {
121
+ backgroundColor: Colors.surface,
122
+ borderRadius: 12,
123
+ marginBottom: 8,
124
+ overflow: 'hidden',
125
+ },
126
+ detailOverlay: {
127
+ position: 'absolute',
128
+ top: 0,
129
+ left: 0,
130
+ right: 0,
131
+ bottom: 0,
132
+ backgroundColor: Colors.background,
133
+ },
134
+ detailWrap: { flex: 1, backgroundColor: Colors.background },
135
+ detailHeader: {
136
+ flexDirection: 'row',
137
+ alignItems: 'center',
138
+ paddingHorizontal: 8,
139
+ paddingVertical: 10,
140
+ backgroundColor: Colors.surface,
141
+ borderBottomWidth: StyleSheet.hairlineWidth,
142
+ borderBottomColor: Colors.border,
143
+ gap: 8,
144
+ },
145
+ backBtn: {
146
+ flexDirection: 'row',
147
+ alignItems: 'center',
148
+ paddingHorizontal: 6,
149
+ paddingVertical: 4,
150
+ borderRadius: 8,
151
+ },
152
+ backIcon: {
153
+ fontSize: 24,
154
+ fontWeight: '300',
155
+ color: Colors.primary,
156
+ marginTop: -2,
157
+ marginRight: 2,
158
+ },
159
+ backText: {
160
+ fontSize: 16,
161
+ color: Colors.primary,
162
+ fontWeight: '500',
163
+ },
164
+ });
@@ -1,174 +1,150 @@
1
- import type { DebugFeature } from '../types';
2
-
3
- export class DebugToolkit {
4
- private static instance: DebugToolkit | null = null;
5
- private _features: DebugFeature[] = [];
6
- private _enabled: boolean;
7
- private _panelVisible: boolean = false;
8
- private renderCallback: (() => void) | null = null;
9
-
10
- private constructor(enabled = true) {
11
- this._enabled = enabled;
12
- }
13
-
14
- static getInstance(enabled = true): DebugToolkit {
15
- if (!DebugToolkit.instance) {
16
- DebugToolkit.instance = new DebugToolkit(enabled);
17
- } else {
18
- DebugToolkit.instance._enabled = enabled;
19
- }
20
- return DebugToolkit.instance;
21
- }
1
+ import type { AnyDebugFeature } from '../types';
22
2
 
23
- get features(): DebugFeature[] {
24
- return [...this._features];
25
- }
3
+ type Listener = () => void;
26
4
 
27
- get enabled(): boolean {
28
- return this._enabled;
29
- }
5
+ const listeners = new Set<Listener>();
6
+ let _features: AnyDebugFeature[] = [];
7
+ let _panelVisible = false;
8
+ let _enabled = true;
30
9
 
31
- get panelVisible(): boolean {
32
- return this._panelVisible;
33
- }
34
-
35
- registerRenderCallback(cb: () => void): void {
36
- if (this.renderCallback) {
37
- console.warn(
38
- '[DebugToolkit] A render callback is already registered. Overwriting. ' +
39
- 'Make sure only one <DebugToolkitProvider> is mounted at a time.',
40
- );
41
- }
42
- this.renderCallback = cb;
43
- }
10
+ function notify(): void {
11
+ listeners.forEach((l) => l());
12
+ }
44
13
 
45
- unregisterRenderCallback(): void {
46
- this.renderCallback = null;
47
- }
14
+ function setupFeature(feature: AnyDebugFeature): void {
15
+ feature.setup?.();
16
+ }
48
17
 
49
- setEnabled(enabled: boolean): void {
50
- if (this._enabled === enabled) {
51
- return;
52
- }
18
+ function cleanupFeature(feature: AnyDebugFeature): void {
19
+ feature.cleanup?.();
20
+ }
53
21
 
54
- this._enabled = enabled;
22
+ export const DebugToolkit = {
23
+ subscribe(listener: Listener): () => void {
24
+ listeners.add(listener);
25
+ return () => {
26
+ listeners.delete(listener);
27
+ };
28
+ },
55
29
 
30
+ get features(): AnyDebugFeature[] {
31
+ return [..._features];
32
+ },
33
+
34
+ get enabled(): boolean {
35
+ return _enabled;
36
+ },
37
+
38
+ get panelVisible(): boolean {
39
+ return _panelVisible;
40
+ },
41
+
42
+ setEnabled(enabled: boolean): void {
43
+ if (_enabled === enabled) return;
44
+ _enabled = enabled;
56
45
  if (!enabled) {
57
- this.hidePanel();
58
- this._features.forEach((feature) => feature.cleanup?.());
59
- this._features = [];
46
+ _panelVisible = false;
47
+ _features.forEach(cleanupFeature);
48
+ _features = [];
60
49
  }
61
- }
50
+ notify();
51
+ },
52
+
53
+ replaceFeatures(features: AnyDebugFeature[]): void {
54
+ if (!_enabled) return;
55
+ const next = features.filter(
56
+ (f, i, arr) =>
57
+ f && typeof f.name === 'string' && arr.findIndex((item) => item.name === f.name) === i,
58
+ );
59
+ const prevMap = new Map(_features.map((f) => [f.name, f]));
60
+ const nextMap = new Map(next.map((f) => [f.name, f]));
61
+
62
+ _features.forEach((f) => {
63
+ if (!nextMap.get(f.name) || nextMap.get(f.name) !== f) cleanupFeature(f);
64
+ });
65
+ next.forEach((f) => {
66
+ if (!prevMap.get(f.name) || prevMap.get(f.name) !== f) setupFeature(f);
67
+ });
62
68
 
63
- private updatePanel(): void {
64
- this.renderCallback?.();
65
- }
69
+ _features = next;
70
+ notify();
71
+ },
66
72
 
67
- addFeature(feature: DebugFeature): void {
68
- if (!this._enabled) {
73
+ addFeature(feature: AnyDebugFeature): void {
74
+ if (!_enabled || !feature || typeof feature.name !== 'string') {
69
75
  return;
70
76
  }
71
- if (feature && typeof feature.name === 'string') {
72
- const existingIndex = this._features.findIndex((item) => item.name === feature.name);
73
-
74
- if (existingIndex >= 0) {
75
- const currentFeature = this._features[existingIndex]!;
76
- if (currentFeature === feature) {
77
- return;
78
- }
79
-
80
- currentFeature.cleanup?.();
81
- this._features = this._features.map((item, index) =>
82
- index === existingIndex ? feature : item,
83
- );
84
- } else {
85
- this._features = [...this._features, feature];
77
+
78
+ const existingIndex = _features.findIndex((f) => f.name === feature.name);
79
+ if (existingIndex >= 0) {
80
+ const existing = _features[existingIndex]!;
81
+ if (existing === feature) {
82
+ return;
86
83
  }
87
84
 
88
- feature.setup?.();
89
- this.updatePanel();
85
+ cleanupFeature(existing);
86
+ setupFeature(feature);
87
+ _features = [
88
+ ..._features.slice(0, existingIndex),
89
+ feature,
90
+ ..._features.slice(existingIndex + 1),
91
+ ];
92
+ notify();
93
+ return;
90
94
  }
91
- }
95
+
96
+ setupFeature(feature);
97
+ _features = [..._features, feature];
98
+ notify();
99
+ },
92
100
 
93
101
  removeFeature(name: string): void {
94
- const idx = this._features.findIndex((f) => f.name === name);
95
- if (idx >= 0) {
96
- this._features[idx]!.cleanup?.();
97
- this._features = this._features.filter((feature) => feature.name !== name);
98
- this.updatePanel();
102
+ if (!_enabled) {
103
+ return;
99
104
  }
100
- }
101
105
 
102
- replaceFeatures(features: DebugFeature[]): void {
103
- if (!this._enabled) {
106
+ const feature = _features.find((f) => f.name === name);
107
+ if (!feature) {
104
108
  return;
105
109
  }
106
110
 
107
- const nextFeatures = features.filter(
108
- (feature, index, array) =>
109
- feature &&
110
- typeof feature.name === 'string' &&
111
- array.findIndex((item) => item.name === feature.name) === index,
112
- );
113
- const previousByName = new Map(this._features.map((feature) => [feature.name, feature]));
114
- const nextByName = new Map(nextFeatures.map((feature) => [feature.name, feature]));
115
-
116
- this._features.forEach((feature) => {
117
- const nextFeature = nextByName.get(feature.name);
118
- if (!nextFeature || nextFeature !== feature) {
119
- feature.cleanup?.();
120
- }
121
- });
122
-
123
- nextFeatures.forEach((feature) => {
124
- const currentFeature = previousByName.get(feature.name);
125
- if (!currentFeature || currentFeature !== feature) {
126
- feature.setup?.();
127
- }
128
- });
129
-
130
- this._features = nextFeatures;
131
- this.updatePanel();
132
- }
111
+ cleanupFeature(feature);
112
+ _features = _features.filter((f) => f.name !== name);
113
+ if (_features.length === 0) {
114
+ _panelVisible = false;
115
+ }
116
+ notify();
117
+ },
133
118
 
134
119
  reset(): void {
135
- this.hidePanel();
136
- this._features.forEach((feature) => feature.cleanup?.());
137
- this._features = [];
138
- }
120
+ _panelVisible = false;
121
+ _features.forEach(cleanupFeature);
122
+ _features = [];
123
+ notify();
124
+ },
139
125
 
140
126
  hasFeatures(): boolean {
141
- return this._features.length > 0;
142
- }
127
+ return _features.length > 0;
128
+ },
143
129
 
144
130
  showPanel(): void {
145
- if (!this._enabled) {
146
- return;
147
- }
148
- this._panelVisible = true;
149
- this.updatePanel();
150
- }
131
+ if (!_enabled) return;
132
+ _panelVisible = true;
133
+ notify();
134
+ },
151
135
 
152
136
  hidePanel(): void {
153
- this._panelVisible = false;
154
- this.updatePanel();
155
- }
137
+ _panelVisible = false;
138
+ notify();
139
+ },
156
140
 
157
141
  clearAll(): void {
158
- this._features.forEach((f) => {
159
- if (f.clear) {
160
- f.clear();
161
- } else {
162
- f.cleanup?.();
163
- f.setup?.();
164
- }
165
- });
166
- this.updatePanel();
167
- }
142
+ _features.forEach((f) => f.clear?.());
143
+ notify();
144
+ },
168
145
 
169
146
  destroy(): void {
170
147
  this.reset();
171
- this.unregisterRenderCallback();
172
- DebugToolkit.instance = null;
173
- }
174
- }
148
+ listeners.clear();
149
+ },
150
+ };
@@ -1,23 +1,20 @@
1
- import React, { createContext, useContext, useEffect, useState, type ReactNode } from 'react';
1
+ import React, { createContext, useCallback, useContext, useEffect, useMemo, useState, type ReactNode } from 'react';
2
2
  import { DebugToolkit } from './DebugToolkit';
3
3
  import { FloatPanelView } from '../components/FloatPanelView';
4
- import type { DebugFeature } from '../types';
4
+ import type { AnyDebugFeature } from '../types';
5
5
 
6
6
  interface ToolkitContextValue {
7
- features: DebugFeature[];
7
+ features: AnyDebugFeature[];
8
8
  showPanel: () => void;
9
9
  hidePanel: () => void;
10
- addFeature: (feature: DebugFeature) => void;
11
- removeFeature: (name: string) => void;
12
10
  clearAll: () => void;
13
- destroy: () => void;
14
11
  }
15
12
 
16
13
  const ToolkitContext = createContext<ToolkitContextValue | null>(null);
17
14
 
18
15
  interface ProviderState {
19
16
  panelVisible: boolean;
20
- features: DebugFeature[];
17
+ features: AnyDebugFeature[];
21
18
  }
22
19
 
23
20
  interface DebugToolkitProviderProps {
@@ -25,43 +22,41 @@ interface DebugToolkitProviderProps {
25
22
  }
26
23
 
27
24
  export function DebugToolkitProvider({ children }: DebugToolkitProviderProps) {
28
- const [state, setState] = useState<ProviderState>(() => {
29
- const toolkit = DebugToolkit.getInstance();
30
- return {
31
- panelVisible: toolkit.panelVisible,
32
- features: toolkit.features,
33
- };
34
- });
25
+ const [state, setState] = useState<ProviderState>(() => ({
26
+ panelVisible: DebugToolkit.panelVisible,
27
+ features: DebugToolkit.features,
28
+ }));
35
29
 
36
30
  useEffect(() => {
37
- const toolkit = DebugToolkit.getInstance();
31
+ // Sync current state — initializeDebugToolkit() may have been called
32
+ // in a child useEffect that runs before this parent effect subscribes.
33
+ setState({
34
+ panelVisible: DebugToolkit.panelVisible,
35
+ features: DebugToolkit.features,
36
+ });
38
37
 
39
- const syncState = () => {
38
+ const unsubscribe = DebugToolkit.subscribe(() => {
40
39
  setState({
41
- panelVisible: toolkit.panelVisible,
42
- features: toolkit.features,
40
+ panelVisible: DebugToolkit.panelVisible,
41
+ features: DebugToolkit.features,
43
42
  });
44
- };
45
-
46
- toolkit.registerRenderCallback(syncState);
47
- syncState();
48
-
49
- return () => {
50
- toolkit.unregisterRenderCallback();
51
- };
43
+ });
44
+ return unsubscribe;
52
45
  }, []);
53
46
 
54
- const toolkit = DebugToolkit.getInstance();
47
+ const showPanel = useCallback(() => { DebugToolkit.showPanel(); }, []);
48
+ const hidePanel = useCallback(() => { DebugToolkit.hidePanel(); }, []);
49
+ const clearAll = useCallback(() => { DebugToolkit.clearAll(); }, []);
55
50
 
56
- const contextValue: ToolkitContextValue = {
57
- features: state.features,
58
- showPanel: toolkit.showPanel.bind(toolkit),
59
- hidePanel: toolkit.hidePanel.bind(toolkit),
60
- addFeature: toolkit.addFeature.bind(toolkit),
61
- removeFeature: toolkit.removeFeature.bind(toolkit),
62
- clearAll: toolkit.clearAll.bind(toolkit),
63
- destroy: toolkit.destroy.bind(toolkit),
64
- };
51
+ const contextValue = useMemo<ToolkitContextValue>(
52
+ () => ({
53
+ features: state.features,
54
+ showPanel,
55
+ hidePanel,
56
+ clearAll,
57
+ }),
58
+ [state.features, showPanel, hidePanel, clearAll],
59
+ );
65
60
 
66
61
  return (
67
62
  <ToolkitContext.Provider value={contextValue}>
@@ -69,8 +64,7 @@ export function DebugToolkitProvider({ children }: DebugToolkitProviderProps) {
69
64
  {state.panelVisible && (
70
65
  <FloatPanelView
71
66
  features={state.features}
72
- onClose={() => toolkit.hidePanel()}
73
- onClearAll={() => toolkit.clearAll()}
67
+ onClearAll={() => DebugToolkit.clearAll()}
74
68
  />
75
69
  )}
76
70
  </ToolkitContext.Provider>
@@ -1,11 +1,15 @@
1
1
  import { ClipboardTab } from '../components/ClipboardTab';
2
2
  import type { DebugFeature } from '../types';
3
3
 
4
+ /**
5
+ * Clipboard feature — all logic lives in ClipboardTab.
6
+ * Data flow is user-driven (TextInput), not event-based.
7
+ */
4
8
  export const createClipboardFeature = (): DebugFeature<void> => ({
5
9
  name: 'clipboard',
6
10
  label: 'Clipboard',
7
11
  renderContent: ClipboardTab,
8
- setup: () => {},
12
+ setup() {},
9
13
  getData: () => [],
10
- cleanup: () => {},
14
+ cleanup() {},
11
15
  });