@teardown/dev-client 2.0.44

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/android/build.gradle.kts +34 -0
  2. package/android/react-native.config.js +10 -0
  3. package/android/src/main/AndroidManifest.xml +7 -0
  4. package/android/src/main/java/com/teardown/devclient/DevSettingsModule.kt +130 -0
  5. package/android/src/main/java/com/teardown/devclient/ShakeDetectorModule.kt +118 -0
  6. package/android/src/main/java/com/teardown/devclient/TeardownDevClientPackage.kt +23 -0
  7. package/ios/TeardownDevClient/DevSettingsModule.swift +135 -0
  8. package/ios/TeardownDevClient/ShakeDetector.swift +102 -0
  9. package/ios/TeardownDevClient/TeardownDevClient.h +14 -0
  10. package/ios/TeardownDevClient/TeardownDevClient.mm +42 -0
  11. package/ios/TeardownDevClient.podspec +23 -0
  12. package/package.json +56 -0
  13. package/src/components/dev-menu/dev-menu.tsx +254 -0
  14. package/src/components/dev-menu/index.ts +5 -0
  15. package/src/components/error-overlay/error-overlay.tsx +256 -0
  16. package/src/components/error-overlay/index.ts +5 -0
  17. package/src/components/index.ts +7 -0
  18. package/src/components/splash-screen/index.ts +5 -0
  19. package/src/components/splash-screen/splash-screen.tsx +99 -0
  20. package/src/dev-client-provider.tsx +204 -0
  21. package/src/hooks/index.ts +24 -0
  22. package/src/hooks/use-bundler-status.ts +139 -0
  23. package/src/hooks/use-dev-menu.ts +306 -0
  24. package/src/hooks/use-splash-screen.ts +177 -0
  25. package/src/index.ts +77 -0
  26. package/src/native/dev-settings.ts +132 -0
  27. package/src/native/index.ts +16 -0
  28. package/src/native/shake-detector.ts +105 -0
  29. package/src/types.ts +235 -0
  30. package/src/utils/bundler-url.ts +103 -0
  31. package/src/utils/index.ts +19 -0
  32. package/src/utils/platform.ts +64 -0
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Splash screen control hook
3
+ *
4
+ * Provides state and control methods for the splash screen.
5
+ */
6
+
7
+ import { useCallback, useEffect, useRef, useState } from "react";
8
+ import type { SplashScreenApi, SplashScreenConfig } from "../types";
9
+
10
+ /**
11
+ * Default splash screen configuration
12
+ */
13
+ const DEFAULT_CONFIG: Required<SplashScreenConfig> = {
14
+ backgroundColor: "#ffffff",
15
+ logo: undefined as unknown as Required<SplashScreenConfig>["logo"],
16
+ resizeMode: "contain",
17
+ minDisplayTime: 500,
18
+ fadeOutDuration: 300,
19
+ autoHide: true,
20
+ };
21
+
22
+ /**
23
+ * Options for useSplashScreen hook
24
+ */
25
+ export interface UseSplashScreenOptions {
26
+ /** Splash screen configuration */
27
+ config?: SplashScreenConfig;
28
+
29
+ /** Callback when splash screen is ready to hide */
30
+ onReady?: () => void;
31
+
32
+ /** Callback when splash screen has hidden */
33
+ onHide?: () => void;
34
+ }
35
+
36
+ /**
37
+ * Hook return type
38
+ */
39
+ export interface UseSplashScreenReturn extends SplashScreenApi {
40
+ /** Merged configuration */
41
+ config: Required<SplashScreenConfig>;
42
+
43
+ /** Whether fade animation is in progress */
44
+ isFading: boolean;
45
+
46
+ /** Opacity value for animation (0-1) */
47
+ opacity: number;
48
+ }
49
+
50
+ /**
51
+ * Hook for managing splash screen state
52
+ *
53
+ * @param options - Configuration options
54
+ * @returns Splash screen state and control methods
55
+ *
56
+ * @example
57
+ * ```tsx
58
+ * function App() {
59
+ * const splash = useSplashScreen({
60
+ * config: { minDisplayTime: 1000 },
61
+ * onReady: () => console.log('App ready'),
62
+ * });
63
+ *
64
+ * useEffect(() => {
65
+ * // App initialization logic
66
+ * initializeApp().then(() => {
67
+ * splash.hide();
68
+ * });
69
+ * }, []);
70
+ *
71
+ * return splash.isVisible ? <SplashScreen {...splash} /> : <MainApp />;
72
+ * }
73
+ * ```
74
+ */
75
+ export function useSplashScreen(options: UseSplashScreenOptions = {}): UseSplashScreenReturn {
76
+ const { config: userConfig, onReady, onHide } = options;
77
+
78
+ // Merge config with defaults
79
+ const config: Required<SplashScreenConfig> = {
80
+ ...DEFAULT_CONFIG,
81
+ ...userConfig,
82
+ };
83
+
84
+ const [isVisible, setIsVisible] = useState(true);
85
+ const [isFading, setIsFading] = useState(false);
86
+ const [opacity, setOpacity] = useState(1);
87
+
88
+ const showTimeRef = useRef(Date.now());
89
+ const fadeIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
90
+ const hasCalledOnReady = useRef(false);
91
+
92
+ // Cleanup function
93
+ const cleanup = useCallback(() => {
94
+ if (fadeIntervalRef.current) {
95
+ clearInterval(fadeIntervalRef.current);
96
+ fadeIntervalRef.current = null;
97
+ }
98
+ }, []);
99
+
100
+ // Hide splash screen with optional fade
101
+ const hide = useCallback(
102
+ async (hideOptions?: { fade?: boolean }): Promise<void> => {
103
+ const shouldFade = hideOptions?.fade ?? true;
104
+ const elapsed = Date.now() - showTimeRef.current;
105
+ const remaining = Math.max(0, config.minDisplayTime - elapsed);
106
+
107
+ // Wait for minimum display time
108
+ if (remaining > 0) {
109
+ await new Promise((resolve) => setTimeout(resolve, remaining));
110
+ }
111
+
112
+ if (!shouldFade || config.fadeOutDuration <= 0) {
113
+ // Instant hide
114
+ setIsVisible(false);
115
+ onHide?.();
116
+ return;
117
+ }
118
+
119
+ // Fade animation
120
+ setIsFading(true);
121
+ const steps = 20;
122
+ const stepDuration = config.fadeOutDuration / steps;
123
+ const opacityStep = 1 / steps;
124
+
125
+ return new Promise((resolve) => {
126
+ let currentStep = 0;
127
+ fadeIntervalRef.current = setInterval(() => {
128
+ currentStep++;
129
+ setOpacity(1 - currentStep * opacityStep);
130
+
131
+ if (currentStep >= steps) {
132
+ cleanup();
133
+ setIsVisible(false);
134
+ setIsFading(false);
135
+ onHide?.();
136
+ resolve();
137
+ }
138
+ }, stepDuration);
139
+ });
140
+ },
141
+ [config.minDisplayTime, config.fadeOutDuration, onHide, cleanup]
142
+ );
143
+
144
+ // Show splash screen
145
+ const show = useCallback(async (): Promise<void> => {
146
+ showTimeRef.current = Date.now();
147
+ setOpacity(1);
148
+ setIsFading(false);
149
+ setIsVisible(true);
150
+ }, []);
151
+
152
+ // Auto-hide when component mounts (if enabled)
153
+ useEffect(() => {
154
+ if (!hasCalledOnReady.current) {
155
+ hasCalledOnReady.current = true;
156
+ onReady?.();
157
+
158
+ if (config.autoHide) {
159
+ hide();
160
+ }
161
+ }
162
+ }, [config.autoHide, hide, onReady]);
163
+
164
+ // Cleanup on unmount
165
+ useEffect(() => {
166
+ return cleanup;
167
+ }, [cleanup]);
168
+
169
+ return {
170
+ isVisible,
171
+ isFading,
172
+ opacity,
173
+ config,
174
+ hide,
175
+ show,
176
+ };
177
+ }
package/src/index.ts ADDED
@@ -0,0 +1,77 @@
1
+ /**
2
+ * @teardown/dev-client
3
+ *
4
+ * React Native development client for Teardown applications.
5
+ * Provides dev menu, splash screen, and error overlay functionality.
6
+ */
7
+
8
+ // Components
9
+ export { DevMenu, type DevMenuProps } from "./components/dev-menu";
10
+ export { ErrorOverlay, type ErrorOverlayProps } from "./components/error-overlay";
11
+ export { SplashScreen, type SplashScreenProps } from "./components/splash-screen";
12
+ // Provider
13
+ export {
14
+ DevClientProvider,
15
+ type DevClientProviderProps,
16
+ useDevClient,
17
+ useDevClientMenu,
18
+ useDevClientSplash,
19
+ } from "./dev-client-provider";
20
+ // Hooks
21
+ export {
22
+ createBundlerStatus,
23
+ type UseBundlerStatusOptions,
24
+ useBundlerStatus,
25
+ } from "./hooks/use-bundler-status";
26
+ export {
27
+ createInitialDevMenuState,
28
+ getDefaultMenuItems,
29
+ type UseDevMenuOptions,
30
+ type UseDevMenuReturn,
31
+ useDevMenu,
32
+ } from "./hooks/use-dev-menu";
33
+ export {
34
+ type UseSplashScreenOptions,
35
+ type UseSplashScreenReturn,
36
+ useSplashScreen,
37
+ } from "./hooks/use-splash-screen";
38
+ // Native modules (for advanced use)
39
+ export {
40
+ getDevSettings,
41
+ isNativeModuleAvailable,
42
+ openDebugger,
43
+ openDevMenu,
44
+ reload,
45
+ setFastRefreshEnabled,
46
+ setHotLoadingEnabled,
47
+ toggleElementInspector,
48
+ } from "./native/dev-settings";
49
+ export { ShakeDetector } from "./native/shake-detector";
50
+ // Types
51
+ export type {
52
+ BundlerStatus,
53
+ DevClientConfig,
54
+ DevClientContextValue,
55
+ DevMenuActions,
56
+ DevMenuItem,
57
+ DevMenuState,
58
+ DevSettings,
59
+ GetBundlerUrlOptions,
60
+ SplashScreenApi,
61
+ SplashScreenConfig,
62
+ } from "./types";
63
+ // Utilities
64
+ export {
65
+ DEFAULT_BUNDLER_PORT,
66
+ getBundlerUrl,
67
+ getDefaultBundlerUrl,
68
+ isValidBundlerUrl,
69
+ } from "./utils/bundler-url";
70
+ export {
71
+ currentPlatform,
72
+ isAndroid,
73
+ isDev,
74
+ isEmulator,
75
+ isIOS,
76
+ platformSelect,
77
+ } from "./utils/platform";
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Native dev settings module wrapper
3
+ *
4
+ * Provides a unified interface for interacting with React Native's
5
+ * dev settings across iOS and Android.
6
+ */
7
+
8
+ import { NativeModules } from "react-native";
9
+ import type { DevSettings, DevSettingsNativeModule } from "../types";
10
+
11
+ /**
12
+ * Teardown native module - will be available when native modules are built
13
+ */
14
+ const TeardownDevSettings = NativeModules.TeardownDevSettings as DevSettingsNativeModule | undefined;
15
+
16
+ /**
17
+ * React Native's built-in DevSettings module
18
+ */
19
+ const RNDevSettings = NativeModules.DevSettings as
20
+ | {
21
+ reload?: () => void;
22
+ setHotLoadingEnabled?: (enabled: boolean) => void;
23
+ setProfilingEnabled?: (enabled: boolean) => void;
24
+ }
25
+ | undefined;
26
+
27
+ /**
28
+ * React Native's DevMenu module
29
+ */
30
+ const RNDevMenu = NativeModules.DevMenu as
31
+ | {
32
+ show?: () => void;
33
+ }
34
+ | undefined;
35
+
36
+ /**
37
+ * Reload the JavaScript bundle
38
+ */
39
+ export function reload(): void {
40
+ if (TeardownDevSettings?.reload) {
41
+ TeardownDevSettings.reload();
42
+ } else if (RNDevSettings?.reload) {
43
+ RNDevSettings.reload();
44
+ } else {
45
+ console.warn("[DevClient] Reload not available - native module not found");
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Open the debugger
51
+ */
52
+ export function openDebugger(): void {
53
+ if (TeardownDevSettings?.openDebugger) {
54
+ TeardownDevSettings.openDebugger();
55
+ } else {
56
+ console.warn("[DevClient] OpenDebugger not available - native module not found");
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Toggle element inspector overlay
62
+ */
63
+ export function toggleElementInspector(): void {
64
+ if (TeardownDevSettings?.toggleElementInspector) {
65
+ TeardownDevSettings.toggleElementInspector();
66
+ } else {
67
+ console.warn("[DevClient] ToggleElementInspector not available - native module not found");
68
+ }
69
+ }
70
+
71
+ /**
72
+ * Set hot loading enabled state
73
+ */
74
+ export function setHotLoadingEnabled(enabled: boolean): void {
75
+ if (TeardownDevSettings?.setHotLoadingEnabled) {
76
+ TeardownDevSettings.setHotLoadingEnabled(enabled);
77
+ } else if (RNDevSettings?.setHotLoadingEnabled) {
78
+ RNDevSettings.setHotLoadingEnabled(enabled);
79
+ } else {
80
+ console.warn("[DevClient] SetHotLoadingEnabled not available - native module not found");
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Set fast refresh enabled state
86
+ */
87
+ export function setFastRefreshEnabled(enabled: boolean): void {
88
+ if (TeardownDevSettings?.setFastRefreshEnabled) {
89
+ TeardownDevSettings.setFastRefreshEnabled(enabled);
90
+ } else {
91
+ // Fast refresh uses the same setting as hot loading in RN
92
+ setHotLoadingEnabled(enabled);
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Get current dev settings
98
+ */
99
+ export async function getDevSettings(): Promise<DevSettings> {
100
+ if (TeardownDevSettings?.getDevSettings) {
101
+ return TeardownDevSettings.getDevSettings();
102
+ }
103
+
104
+ // Return default values if native module not available
105
+ return {
106
+ isDebuggingRemotely: false,
107
+ isElementInspectorShown: false,
108
+ isHotLoadingEnabled: true,
109
+ isFastRefreshEnabled: true,
110
+ isPerfMonitorShown: false,
111
+ };
112
+ }
113
+
114
+ /**
115
+ * Open native dev menu
116
+ */
117
+ export function openDevMenu(): void {
118
+ if (TeardownDevSettings?.openDevMenu) {
119
+ TeardownDevSettings.openDevMenu();
120
+ } else if (RNDevMenu?.show) {
121
+ RNDevMenu.show();
122
+ } else {
123
+ console.warn("[DevClient] OpenDevMenu not available - native module not found");
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Check if native module is available
129
+ */
130
+ export function isNativeModuleAvailable(): boolean {
131
+ return TeardownDevSettings !== undefined;
132
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Native module exports
3
+ */
4
+
5
+ export {
6
+ getDevSettings,
7
+ isNativeModuleAvailable,
8
+ openDebugger,
9
+ openDevMenu,
10
+ reload,
11
+ setFastRefreshEnabled,
12
+ setHotLoadingEnabled,
13
+ toggleElementInspector,
14
+ } from "./dev-settings";
15
+
16
+ export { ShakeDetector } from "./shake-detector";
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Shake gesture detection
3
+ *
4
+ * Provides shake-to-open-dev-menu functionality using
5
+ * React Native's DeviceEventEmitter or native module.
6
+ * Uses lazy require so tests can run when react-native is not fully available.
7
+ */
8
+
9
+ import EventEmitter from "eventemitter3";
10
+
11
+ function getRN(): {
12
+ DeviceEventEmitter?: { addListener: (a: string, b: () => void) => { remove: () => void } };
13
+ NativeModules?: { TeardownShakeDetector?: { startListening?: () => void; stopListening?: () => void } };
14
+ } {
15
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
16
+ return require("react-native") as ReturnType<typeof getRN>;
17
+ }
18
+
19
+ /**
20
+ * Native shake detector module (lazy)
21
+ */
22
+ function getTeardownShakeDetector() {
23
+ return getRN().NativeModules?.TeardownShakeDetector;
24
+ }
25
+
26
+ /**
27
+ * Event types emitted by shake detector
28
+ */
29
+ export interface ShakeDetectorEvents {
30
+ shake: () => void;
31
+ }
32
+
33
+ /**
34
+ * ShakeDetector class for managing shake gesture detection
35
+ */
36
+ class ShakeDetectorImpl extends EventEmitter<ShakeDetectorEvents> {
37
+ private isListening = false;
38
+ private subscription: { remove: () => void } | null = null;
39
+
40
+ /**
41
+ * Start listening for shake gestures
42
+ */
43
+ start(): void {
44
+ if (this.isListening) {
45
+ return;
46
+ }
47
+
48
+ const rn = getRN();
49
+ const DeviceEventEmitter = rn.DeviceEventEmitter;
50
+ if (!DeviceEventEmitter) {
51
+ return;
52
+ }
53
+
54
+ this.isListening = true;
55
+ const TeardownShakeDetector = getTeardownShakeDetector();
56
+
57
+ // Try native module first
58
+ if (TeardownShakeDetector?.startListening) {
59
+ TeardownShakeDetector.startListening();
60
+ this.subscription = DeviceEventEmitter.addListener("TeardownShakeEvent", () => {
61
+ this.emit("shake");
62
+ });
63
+ } else {
64
+ // Fallback: Listen for RN's shake event (requires Debug mode)
65
+ this.subscription = DeviceEventEmitter.addListener("RCTDevMenuShown", () => {
66
+ // This fires when the native dev menu is shown via shake
67
+ // We can use this as a proxy for shake detection
68
+ this.emit("shake");
69
+ });
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Stop listening for shake gestures
75
+ */
76
+ stop(): void {
77
+ if (!this.isListening) {
78
+ return;
79
+ }
80
+
81
+ this.isListening = false;
82
+ const TeardownShakeDetector = getTeardownShakeDetector();
83
+
84
+ if (TeardownShakeDetector?.stopListening) {
85
+ TeardownShakeDetector.stopListening();
86
+ }
87
+
88
+ if (this.subscription) {
89
+ this.subscription.remove();
90
+ this.subscription = null;
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Check if currently listening
96
+ */
97
+ getIsListening(): boolean {
98
+ return this.isListening;
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Singleton shake detector instance
104
+ */
105
+ export const ShakeDetector = new ShakeDetectorImpl();