@umituz/react-native-haptics 1.0.6 → 1.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -21,8 +21,12 @@ npm install @umituz/react-native-haptics
21
21
  - ✅ Selection feedback (pickers, sliders)
22
22
  - ✅ Custom haptic patterns
23
23
  - ✅ Convenience methods for common interactions
24
+ - ✅ Type guards for runtime type safety
25
+ - ✅ Throttle mechanism (50ms minimum interval)
26
+ - ✅ Development-mode error logging
24
27
  - ✅ Silent failure (no crashes if unsupported)
25
28
  - ✅ Platform-agnostic (iOS + Android)
29
+ - ✅ Performance optimized (useMemo, useCallback)
26
30
 
27
31
  ## Usage
28
32
 
@@ -91,6 +95,28 @@ await HapticService.buttonPress();
91
95
  await HapticService.success();
92
96
  ```
93
97
 
98
+ ### Type Guards
99
+
100
+ ```typescript
101
+ import { isImpactStyle, isNotificationType, isHapticPattern } from '@umituz/react-native-haptics';
102
+
103
+ // Validate impact style
104
+ if (isImpactStyle(userInput)) {
105
+ // TypeScript knows userInput is ImpactStyle here
106
+ haptics.impact(userInput);
107
+ }
108
+
109
+ // Validate notification type
110
+ if (isNotificationType(type)) {
111
+ haptics.notification(type);
112
+ }
113
+
114
+ // Validate haptic pattern
115
+ if (isHapticPattern(pattern)) {
116
+ haptics.pattern(pattern);
117
+ }
118
+ ```
119
+
94
120
  ## Common Patterns
95
121
 
96
122
  - `buttonPress()` - Light impact
@@ -114,7 +140,17 @@ await HapticService.success();
114
140
 
115
141
  ### Utilities
116
142
 
117
- - `HapticUtils`: Utility functions for haptic pattern management
143
+ - `HAPTIC_CONSTANTS`: Constants for default haptic styles
144
+ - `isImpactStyle()`: Type guard for ImpactStyle validation
145
+ - `isNotificationType()`: Type guard for NotificationType validation
146
+ - `isHapticPattern()`: Type guard for HapticPattern validation
147
+
148
+ ## Performance
149
+
150
+ - **Throttle**: 50ms minimum interval between haptic feedback to prevent spam
151
+ - **Memoization**: Hook return value is memoized with useMemo
152
+ - **Callback optimization**: All callbacks use useCallback to prevent re-renders
153
+ - **Development logging**: Errors are logged only in development mode (__DEV__)
118
154
 
119
155
  ## License
120
156
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-haptics",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "description": "Haptic feedback (vibration) for React Native using expo-haptics with impact, notification, and selection feedback patterns",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -36,3 +36,31 @@ export const HAPTIC_CONSTANTS = {
36
36
  DELETE_IMPACT: 'Medium' as ImpactStyle,
37
37
  ERROR_IMPACT: 'Heavy' as ImpactStyle,
38
38
  } as const;
39
+
40
+ /**
41
+ * Type guards for runtime type safety
42
+ */
43
+
44
+ /**
45
+ * Check if value is a valid ImpactStyle
46
+ */
47
+ export function isImpactStyle(value: unknown): value is ImpactStyle {
48
+ return typeof value === 'string' &&
49
+ ['Light', 'Medium', 'Heavy'].includes(value);
50
+ }
51
+
52
+ /**
53
+ * Check if value is a valid NotificationType
54
+ */
55
+ export function isNotificationType(value: unknown): value is NotificationType {
56
+ return typeof value === 'string' &&
57
+ ['Success', 'Warning', 'Error'].includes(value);
58
+ }
59
+
60
+ /**
61
+ * Check if value is a valid HapticPattern
62
+ */
63
+ export function isHapticPattern(value: unknown): value is HapticPattern {
64
+ return typeof value === 'string' &&
65
+ ['success', 'warning', 'error', 'selection'].includes(value);
66
+ }
package/src/index.ts CHANGED
@@ -163,6 +163,9 @@ export type {
163
163
 
164
164
  export {
165
165
  HAPTIC_CONSTANTS,
166
+ isImpactStyle,
167
+ isNotificationType,
168
+ isHapticPattern,
166
169
  } from './domain/entities/Haptic';
167
170
 
168
171
  // ============================================================================
@@ -10,6 +10,16 @@
10
10
 
11
11
  import * as Haptics from 'expo-haptics';
12
12
  import type { ImpactStyle, NotificationType, HapticPattern } from '../../domain/entities/Haptic';
13
+ import { isImpactStyle, isNotificationType, isHapticPattern } from '../../domain/entities/Haptic';
14
+
15
+ /**
16
+ * Log error in development mode only
17
+ */
18
+ function logError(method: string, error: unknown): void {
19
+ if (process.env.NODE_ENV === 'development') {
20
+ console.error(`[HapticService.${method}]`, error);
21
+ }
22
+ }
13
23
 
14
24
 
15
25
  /**
@@ -27,7 +37,7 @@ export class HapticService {
27
37
  Haptics.ImpactFeedbackStyle.Heavy
28
38
  );
29
39
  } catch (error) {
30
- // Silent fail - haptics not critical
40
+ logError('impact', error);
31
41
  }
32
42
  }
33
43
 
@@ -42,7 +52,7 @@ export class HapticService {
42
52
  Haptics.NotificationFeedbackType.Error
43
53
  );
44
54
  } catch (error) {
45
- // Silent fail
55
+ logError('notification', error);
46
56
  }
47
57
  }
48
58
 
@@ -53,7 +63,7 @@ export class HapticService {
53
63
  try {
54
64
  await Haptics.selectionAsync();
55
65
  } catch (error) {
56
- // Silent fail
66
+ logError('selection', error);
57
67
  }
58
68
  }
59
69
 
@@ -79,7 +89,7 @@ export class HapticService {
79
89
  await HapticService.impact('Light');
80
90
  }
81
91
  } catch (error) {
82
- // Silent fail
92
+ logError('pattern', error);
83
93
  }
84
94
  }
85
95
 
@@ -8,10 +8,16 @@
8
8
  * @layer presentation/hooks
9
9
  */
10
10
 
11
- import { useCallback } from 'react';
11
+ import { useCallback, useRef, useMemo } from 'react';
12
12
  import { HapticService } from '../../infrastructure/services/HapticService';
13
13
  import type { ImpactStyle, NotificationType, HapticPattern } from '../../domain/entities/Haptic';
14
14
 
15
+ /**
16
+ * Minimum interval between haptic feedback (ms)
17
+ * Prevents spam and improves UX
18
+ */
19
+ const THROTTLE_INTERVAL = 50;
20
+
15
21
  /**
16
22
  * useHaptics hook for haptic feedback
17
23
  *
@@ -45,70 +51,96 @@ import type { ImpactStyle, NotificationType, HapticPattern } from '../../domain/
45
51
  * ```
46
52
  */
47
53
  export const useHaptics = () => {
54
+ const lastExecutionRef = useRef<number>(0);
55
+
56
+ /**
57
+ * Check if enough time has passed since last haptic
58
+ */
59
+ const canExecute = useCallback((): boolean => {
60
+ const now = Date.now();
61
+ if (now - lastExecutionRef.current < THROTTLE_INTERVAL) {
62
+ return false;
63
+ }
64
+ lastExecutionRef.current = now;
65
+ return true;
66
+ }, []);
67
+
48
68
  /**
49
69
  * Trigger impact feedback (light, medium, heavy)
50
70
  */
51
71
  const impact = useCallback(async (style: ImpactStyle = 'Light') => {
72
+ if (!canExecute()) return;
52
73
  await HapticService.impact(style);
53
- }, []);
74
+ }, [canExecute]);
54
75
 
55
76
  /**
56
77
  * Trigger notification feedback (success, warning, error)
57
78
  */
58
79
  const notification = useCallback(async (type: NotificationType) => {
80
+ if (!canExecute()) return;
59
81
  await HapticService.notification(type);
60
- }, []);
82
+ }, [canExecute]);
61
83
 
62
84
  /**
63
85
  * Trigger selection feedback (for pickers, sliders)
64
86
  */
65
87
  const selection = useCallback(async () => {
88
+ if (!canExecute()) return;
66
89
  await HapticService.selection();
67
- }, []);
90
+ }, [canExecute]);
68
91
 
69
92
  /**
70
93
  * Trigger custom haptic pattern
71
94
  */
72
95
  const pattern = useCallback(async (patternType: HapticPattern) => {
96
+ if (!canExecute()) return;
73
97
  await HapticService.pattern(patternType);
74
- }, []);
98
+ }, [canExecute]);
75
99
 
76
100
  /**
77
101
  * Common haptic patterns (convenience methods)
78
102
  */
79
103
  const buttonPress = useCallback(async () => {
104
+ if (!canExecute()) return;
80
105
  await HapticService.buttonPress();
81
- }, []);
106
+ }, [canExecute]);
82
107
 
83
108
  const success = useCallback(async () => {
109
+ if (!canExecute()) return;
84
110
  await HapticService.success();
85
- }, []);
111
+ }, [canExecute]);
86
112
 
87
113
  const error = useCallback(async () => {
114
+ if (!canExecute()) return;
88
115
  await HapticService.error();
89
- }, []);
116
+ }, [canExecute]);
90
117
 
91
118
  const warning = useCallback(async () => {
119
+ if (!canExecute()) return;
92
120
  await HapticService.warning();
93
- }, []);
121
+ }, [canExecute]);
94
122
 
95
123
  const deleteItem = useCallback(async () => {
124
+ if (!canExecute()) return;
96
125
  await HapticService.delete();
97
- }, []);
126
+ }, [canExecute]);
98
127
 
99
128
  const refresh = useCallback(async () => {
129
+ if (!canExecute()) return;
100
130
  await HapticService.refresh();
101
- }, []);
131
+ }, [canExecute]);
102
132
 
103
133
  const selectionChange = useCallback(async () => {
134
+ if (!canExecute()) return;
104
135
  await HapticService.selectionChange();
105
- }, []);
136
+ }, [canExecute]);
106
137
 
107
138
  const longPress = useCallback(async () => {
139
+ if (!canExecute()) return;
108
140
  await HapticService.longPress();
109
- }, []);
141
+ }, [canExecute]);
110
142
 
111
- return {
143
+ return useMemo(() => ({
112
144
  // Generic methods
113
145
  impact,
114
146
  notification,
@@ -124,5 +156,18 @@ export const useHaptics = () => {
124
156
  refresh,
125
157
  selectionChange,
126
158
  longPress,
127
- };
159
+ }), [
160
+ impact,
161
+ notification,
162
+ selection,
163
+ pattern,
164
+ buttonPress,
165
+ success,
166
+ error,
167
+ warning,
168
+ deleteItem,
169
+ refresh,
170
+ selectionChange,
171
+ longPress,
172
+ ]);
128
173
  };