react-hook-toolkit 3.0.3 → 3.0.5

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.
@@ -11,10 +11,30 @@ export declare function useIsMounted(): boolean;
11
11
  export declare function useCss(css: string): {
12
12
  error: Error | null;
13
13
  };
14
- export declare function useSpeak(text: string): {
15
- speak: () => void;
14
+ type VoiceStatus = 'idle' | 'playing' | 'paused' | 'ended' | 'error';
15
+ interface ISpeakOptions {
16
+ rate?: number;
17
+ pitch?: number;
18
+ volume?: number;
19
+ lang?: string;
20
+ voice?: SpeechSynthesisVoice | null;
21
+ onStart?: () => void;
22
+ onEnd?: () => void;
23
+ onPause?: () => void;
24
+ onError?: (error: Error) => void;
25
+ }
26
+ interface ISpeakResult {
27
+ speak: (text: string, options?: ISpeakOptions) => void;
28
+ stop: () => void;
29
+ pause: () => void;
30
+ resume: () => void;
31
+ isPlaying: boolean;
32
+ isPaused: boolean;
33
+ status: VoiceStatus;
34
+ isSupported: boolean;
16
35
  error: Error | null;
17
- };
36
+ }
37
+ export declare const useSpeak: () => ISpeakResult;
18
38
  export declare function useCountUp(target: number, duration: number): {
19
39
  count: number;
20
40
  error: Error | null;
@@ -60,19 +60,143 @@ export function useCss(css) {
60
60
  }, [css]);
61
61
  return { error };
62
62
  }
63
- export function useSpeak(text) {
63
+ const IS_SUPPORTED = typeof window !== 'undefined' && 'speechSynthesis' in window;
64
+ export const useSpeak = () => {
65
+ const [status, setStatus] = useState('idle');
64
66
  const [error, setError] = useState(null);
65
- const speak = () => {
66
- try {
67
- const utterance = new SpeechSynthesisUtterance(text);
68
- speechSynthesis.speak(utterance);
67
+ const isMounted = useRef(true);
68
+ const utteranceRef = useRef(null);
69
+ const callbacksRef = useRef({});
70
+ // ── Derived state (no extra useState) ──────────────────────
71
+ const isPlaying = status === 'playing';
72
+ const isPaused = status === 'paused';
73
+ // ── Mount / Unmount ────────────────────────────────────────
74
+ useEffect(() => {
75
+ isMounted.current = true;
76
+ return () => {
77
+ var _a;
78
+ isMounted.current = false;
79
+ (_a = window.speechSynthesis) === null || _a === void 0 ? void 0 : _a.cancel();
80
+ utteranceRef.current = null;
81
+ };
82
+ }, []);
83
+ // ── Stable status setter ───────────────────────────────────
84
+ const safeSetStatus = useCallback((next) => {
85
+ if (isMounted.current)
86
+ setStatus(next);
87
+ }, []);
88
+ const safeSetError = useCallback((err) => {
89
+ if (isMounted.current)
90
+ setError(err);
91
+ }, []);
92
+ // ── Controls ───────────────────────────────────────────────
93
+ const stop = useCallback(() => {
94
+ var _a;
95
+ (_a = window.speechSynthesis) === null || _a === void 0 ? void 0 : _a.cancel();
96
+ safeSetStatus('idle');
97
+ }, [safeSetStatus]);
98
+ const pause = useCallback(() => {
99
+ var _a;
100
+ if (!isPlaying)
101
+ return;
102
+ (_a = window.speechSynthesis) === null || _a === void 0 ? void 0 : _a.pause();
103
+ safeSetStatus('paused');
104
+ }, [isPlaying, safeSetStatus]);
105
+ const resume = useCallback(() => {
106
+ var _a;
107
+ if (!isPaused)
108
+ return;
109
+ (_a = window.speechSynthesis) === null || _a === void 0 ? void 0 : _a.resume();
110
+ safeSetStatus('playing');
111
+ }, [isPaused, safeSetStatus]);
112
+ // ── Core speak ─────────────────────────────────────────────
113
+ const speak = useCallback((text, options = {}) => {
114
+ var _a, _b, _c, _d, _e;
115
+ // Guards
116
+ if (!IS_SUPPORTED) {
117
+ const err = new Error('SpeechSynthesis is not supported in this browser');
118
+ safeSetError(err);
119
+ (_a = options.onError) === null || _a === void 0 ? void 0 : _a.call(options, err);
120
+ return;
69
121
  }
70
- catch (err) {
71
- setError(err instanceof Error ? err : new Error('Failed to speak'));
122
+ const trimmed = text === null || text === void 0 ? void 0 : text.trim();
123
+ if (!trimmed) {
124
+ const err = new Error('Text cannot be empty');
125
+ safeSetError(err);
126
+ (_b = options.onError) === null || _b === void 0 ? void 0 : _b.call(options, err);
127
+ return;
72
128
  }
129
+ // Store latest callbacks in ref — avoids stale closure in handlers
130
+ callbacksRef.current = {
131
+ onStart: options.onStart,
132
+ onEnd: options.onEnd,
133
+ onPause: options.onPause,
134
+ onError: options.onError,
135
+ };
136
+ // Cancel any ongoing speech
137
+ window.speechSynthesis.cancel();
138
+ if (isMounted.current)
139
+ setError(null);
140
+ // Build utterance
141
+ const utterance = new SpeechSynthesisUtterance(trimmed);
142
+ utterance.rate = (_c = options.rate) !== null && _c !== void 0 ? _c : 1;
143
+ utterance.pitch = (_d = options.pitch) !== null && _d !== void 0 ? _d : 1;
144
+ utterance.volume = (_e = options.volume) !== null && _e !== void 0 ? _e : 1;
145
+ if (options.lang)
146
+ utterance.lang = options.lang;
147
+ if (options.voice)
148
+ utterance.voice = options.voice;
149
+ // ── Event handlers (read callbacks from ref — always fresh) ──
150
+ utterance.onstart = () => {
151
+ var _a, _b;
152
+ safeSetStatus('playing');
153
+ (_b = (_a = callbacksRef.current).onStart) === null || _b === void 0 ? void 0 : _b.call(_a);
154
+ };
155
+ utterance.onpause = () => {
156
+ var _a, _b;
157
+ safeSetStatus('paused');
158
+ (_b = (_a = callbacksRef.current).onPause) === null || _b === void 0 ? void 0 : _b.call(_a);
159
+ };
160
+ utterance.onresume = () => {
161
+ safeSetStatus('playing');
162
+ };
163
+ utterance.onend = () => {
164
+ var _a, _b;
165
+ safeSetStatus('ended');
166
+ (_b = (_a = callbacksRef.current).onEnd) === null || _b === void 0 ? void 0 : _b.call(_a);
167
+ utteranceRef.current = null;
168
+ };
169
+ utterance.onerror = (e) => {
170
+ var _a, _b;
171
+ // 'interrupted' fires on manual stop() — not a real error
172
+ if (e.error === 'interrupted' || e.error === 'canceled')
173
+ return;
174
+ const err = new Error(`Speech error: ${e.error}`);
175
+ safeSetStatus('error');
176
+ safeSetError(err);
177
+ (_b = (_a = callbacksRef.current).onError) === null || _b === void 0 ? void 0 : _b.call(_a, err);
178
+ utteranceRef.current = null;
179
+ };
180
+ // Assign ref BEFORE timeout — prevents Chrome GC dropping utterance mid-speech
181
+ utteranceRef.current = utterance;
182
+ // Chrome gets stuck in paused state after cancel() — delay lets it settle
183
+ setTimeout(() => {
184
+ if (isMounted.current)
185
+ window.speechSynthesis.speak(utterance);
186
+ }, 100);
187
+ }, [safeSetStatus, safeSetError]); // minimal deps — text passed at call time
188
+ return {
189
+ speak,
190
+ stop,
191
+ pause,
192
+ resume,
193
+ isPlaying,
194
+ isPaused,
195
+ status,
196
+ isSupported: IS_SUPPORTED,
197
+ error,
73
198
  };
74
- return { speak, error };
75
- }
199
+ };
76
200
  export function useCountUp(target, duration) {
77
201
  const [count, setCount] = useState(0);
78
202
  const [error, setError] = useState(null);
package/dist/index.d.ts CHANGED
@@ -1,4 +1,3 @@
1
- import 'nprogress/nprogress.css';
2
1
  import { ReactHooksWrapper, getHook, setHook } from './chunk1415/chunk143';
3
2
  import { useHistoryState, useIdle, useIsFirstRender, useList, useLockBodyScroll, useLongPress, useRecentSearch, useSpeech, usePermission, usePageLeave, useMotion, useHoverDirty, useBeforeUnload, useClickAway, useResponsive, useUnmountedRef } from './chunk1516/chunk726433';
4
3
  import { useBrowser, useMenuNavigation, useNavigationState, useRouter } from './chunk1516/chunk0022';
package/dist/index.js CHANGED
@@ -1,4 +1,3 @@
1
- import 'nprogress/nprogress.css';
2
1
  import { ReactHooksWrapper, getHook, setHook } from './chunk1415/chunk143';
3
2
  import { useHistoryState, useIdle, useIsFirstRender, useList, useLockBodyScroll, useLongPress, useRecentSearch, useSpeech, usePermission, usePageLeave, useMotion, useHoverDirty, useBeforeUnload, useClickAway, useResponsive, useUnmountedRef } from './chunk1516/chunk726433';
4
3
  import { useBrowser, useMenuNavigation, useNavigationState, useRouter } from './chunk1516/chunk0022';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-hook-toolkit",
3
- "version": "3.0.3",
3
+ "version": "3.0.5",
4
4
  "description": "Ultimate package for React developers, offering a powerful collection of hooks and components to enhance their development experience.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",