@designcrowd/fe-shared-lib 1.6.10-voiceText-3 → 1.6.10-voiceText-4

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.
@@ -0,0 +1,68 @@
1
+ - generic [ref=e3]:
2
+ - navigation "Global" [ref=e6]:
3
+ - generic [ref=e10]:
4
+ - generic [ref=e11]:
5
+ - link "Skip to canvas" [ref=e12] [cursor=pointer]:
6
+ - /url: "#storybook-preview-wrapper"
7
+ - link "Storybook" [ref=e14] [cursor=pointer]:
8
+ - /url: ./
9
+ - img "Storybook" [ref=e15]
10
+ - button "Shortcuts" [ref=e21] [cursor=pointer]:
11
+ - img [ref=e22]
12
+ - generic [ref=e25]: Search for components
13
+ - combobox "Search for components" [ref=e27]:
14
+ - generic:
15
+ - img
16
+ - searchbox "Search for components" [ref=e28]
17
+ - code: ⌃ K
18
+ - main [ref=e52]:
19
+ - generic [ref=e54]:
20
+ - generic [ref=e55]:
21
+ - button "Remount component" [ref=e56] [cursor=pointer]:
22
+ - img [ref=e57]
23
+ - button "Zoom in" [ref=e59] [cursor=pointer]:
24
+ - img [ref=e60]
25
+ - button "Zoom out" [ref=e63] [cursor=pointer]:
26
+ - img [ref=e64]
27
+ - button "Reset zoom" [ref=e67] [cursor=pointer]:
28
+ - img [ref=e68]
29
+ - button "Enable measure" [ref=e71] [cursor=pointer]:
30
+ - img [ref=e72]
31
+ - button "Apply outlines to the preview" [ref=e75] [cursor=pointer]:
32
+ - img [ref=e76]
33
+ - button "Change the size of the preview" [ref=e79] [cursor=pointer]:
34
+ - img [ref=e80]
35
+ - button "Vision simulator" [ref=e85] [cursor=pointer]:
36
+ - img [ref=e86]
37
+ - generic:
38
+ - img
39
+ - generic [ref=e90]:
40
+ - button "Go full screen" [ref=e91] [cursor=pointer]:
41
+ - img [ref=e92]
42
+ - link "Open canvas in new tab" [ref=e94] [cursor=pointer]:
43
+ - /url: iframe.html?id=components-voicetotextbutton--side-by-side
44
+ - img [ref=e95]
45
+ - button "Copy canvas link" [ref=e98] [cursor=pointer]:
46
+ - img [ref=e99]
47
+ - generic [ref=e103]:
48
+ - progressbar "Content is loading..." [ref=e105]
49
+ - generic [ref=e106]:
50
+ - link "Skip to sidebar" [ref=e107] [cursor=pointer]:
51
+ - /url: "#components-voicetotextbutton--side-by-side"
52
+ - iframe [ref=e108]:
53
+
54
+ - generic [ref=e113]:
55
+ - tablist [ref=e115]:
56
+ - tab "Controls" [ref=e116] [cursor=pointer]:
57
+ - generic [ref=e118]: Controls
58
+ - tab "Actions" [ref=e119] [cursor=pointer]:
59
+ - generic [ref=e121]: Actions
60
+ - tab "Interactions" [ref=e122] [cursor=pointer]:
61
+ - generic [ref=e124]: Interactions
62
+ - tab "Accessibility" [ref=e125] [cursor=pointer]:
63
+ - generic [ref=e127]: Accessibility
64
+ - generic [ref=e130]:
65
+ - button "Change addon orientation [alt D]" [ref=e131] [cursor=pointer]:
66
+ - img [ref=e132]
67
+ - button "Hide addons [alt A]" [ref=e135] [cursor=pointer]:
68
+ - img [ref=e136]
@@ -0,0 +1,68 @@
1
+ - generic [ref=e3]:
2
+ - navigation "Global" [ref=e6]:
3
+ - generic [ref=e10]:
4
+ - generic [ref=e11]:
5
+ - link "Skip to canvas" [ref=e12] [cursor=pointer]:
6
+ - /url: "#storybook-preview-wrapper"
7
+ - link "Storybook" [ref=e14] [cursor=pointer]:
8
+ - /url: ./
9
+ - img "Storybook" [ref=e15]
10
+ - button "Shortcuts" [ref=e21] [cursor=pointer]:
11
+ - img [ref=e22]
12
+ - generic [ref=e25]: Search for components
13
+ - combobox "Search for components" [ref=e27]:
14
+ - generic:
15
+ - img
16
+ - searchbox "Search for components" [ref=e28]
17
+ - code: ⌃ K
18
+ - main [ref=e52]:
19
+ - generic [ref=e54]:
20
+ - generic [ref=e55]:
21
+ - button "Remount component" [ref=e56] [cursor=pointer]:
22
+ - img [ref=e57]
23
+ - button "Zoom in" [ref=e59] [cursor=pointer]:
24
+ - img [ref=e60]
25
+ - button "Zoom out" [ref=e63] [cursor=pointer]:
26
+ - img [ref=e64]
27
+ - button "Reset zoom" [ref=e67] [cursor=pointer]:
28
+ - img [ref=e68]
29
+ - button "Enable measure" [ref=e71] [cursor=pointer]:
30
+ - img [ref=e72]
31
+ - button "Apply outlines to the preview" [ref=e75] [cursor=pointer]:
32
+ - img [ref=e76]
33
+ - button "Change the size of the preview" [ref=e79] [cursor=pointer]:
34
+ - img [ref=e80]
35
+ - button "Vision simulator" [ref=e85] [cursor=pointer]:
36
+ - img [ref=e86]
37
+ - generic:
38
+ - img
39
+ - generic [ref=e90]:
40
+ - button "Go full screen" [ref=e91] [cursor=pointer]:
41
+ - img [ref=e92]
42
+ - link "Open canvas in new tab" [ref=e94] [cursor=pointer]:
43
+ - /url: iframe.html?id=components-voicetotextbutton--prompt-input-demo
44
+ - img [ref=e95]
45
+ - button "Copy canvas link" [ref=e98] [cursor=pointer]:
46
+ - img [ref=e99]
47
+ - generic [ref=e103]:
48
+ - progressbar "Content is loading..." [ref=e105]
49
+ - generic [ref=e106]:
50
+ - link "Skip to sidebar" [ref=e107] [cursor=pointer]:
51
+ - /url: "#components-voicetotextbutton--prompt-input-demo"
52
+ - iframe [ref=e108]:
53
+
54
+ - generic [ref=e113]:
55
+ - tablist [ref=e115]:
56
+ - tab "Controls" [ref=e116] [cursor=pointer]:
57
+ - generic [ref=e118]: Controls
58
+ - tab "Actions" [ref=e119] [cursor=pointer]:
59
+ - generic [ref=e121]: Actions
60
+ - tab "Interactions" [ref=e122] [cursor=pointer]:
61
+ - generic [ref=e124]: Interactions
62
+ - tab "Accessibility" [ref=e125] [cursor=pointer]:
63
+ - generic [ref=e127]: Accessibility
64
+ - generic [ref=e130]:
65
+ - button "Change addon orientation [alt D]" [ref=e131] [cursor=pointer]:
66
+ - img [ref=e132]
67
+ - button "Hide addons [alt A]" [ref=e135] [cursor=pointer]:
68
+ - img [ref=e136]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@designcrowd/fe-shared-lib",
3
- "version": "1.6.10-voiceText-3",
3
+ "version": "1.6.10-voiceText-4",
4
4
  "scripts": {
5
5
  "start": "run-p storybook watch:translation",
6
6
  "build": "npm run build:css --production",
@@ -1438,10 +1438,6 @@ video {
1438
1438
  --tw-bg-opacity: 1;
1439
1439
  background-color: rgb(208 208 208 / var(--tw-bg-opacity));
1440
1440
  }
1441
- .theme-brandCrowd .tw-bg-grayscale-700 {
1442
- --tw-bg-opacity: 1;
1443
- background-color: rgb(43 43 43 / var(--tw-bg-opacity));
1444
- }
1445
1441
  .theme-brandCrowd .tw-bg-info-100 {
1446
1442
  --tw-bg-opacity: 1;
1447
1443
  background-color: rgb(204 234 247 / var(--tw-bg-opacity));
@@ -2257,16 +2253,16 @@ video {
2257
2253
  outline: 2px solid transparent;
2258
2254
  outline-offset: 2px;
2259
2255
  }
2260
- .theme-brandCrowd .focus\:tw-ring-2:focus {
2256
+ .theme-brandCrowd .focus-visible\:tw-ring-2:focus-visible {
2261
2257
  --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
2262
2258
  --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);
2263
2259
  box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
2264
2260
  }
2265
- .theme-brandCrowd .focus\:tw-ring-primary-500:focus {
2261
+ .theme-brandCrowd .focus-visible\:tw-ring-primary-500:focus-visible {
2266
2262
  --tw-ring-opacity: 1;
2267
2263
  --tw-ring-color: rgb(242 27 63 / var(--tw-ring-opacity));
2268
2264
  }
2269
- .theme-brandCrowd .focus\:tw-ring-offset-2:focus {
2265
+ .theme-brandCrowd .focus-visible\:tw-ring-offset-2:focus-visible {
2270
2266
  --tw-ring-offset-width: 2px;
2271
2267
  }
2272
2268
  .theme-brandCrowd .tw-group:hover .group-hover\:tw-text-info-500 {
@@ -1362,10 +1362,6 @@ video {
1362
1362
  --tw-bg-opacity: 1;
1363
1363
  background-color: rgb(208 208 208 / var(--tw-bg-opacity));
1364
1364
  }
1365
- .theme-brandPage .tw-bg-grayscale-700 {
1366
- --tw-bg-opacity: 1;
1367
- background-color: rgb(43 43 43 / var(--tw-bg-opacity));
1368
- }
1369
1365
  .theme-brandPage .tw-bg-transparent {
1370
1366
  background-color: transparent;
1371
1367
  }
@@ -1933,12 +1929,12 @@ video {
1933
1929
  outline: 2px solid transparent;
1934
1930
  outline-offset: 2px;
1935
1931
  }
1936
- .theme-brandPage .focus\:tw-ring-2:focus {
1932
+ .theme-brandPage .focus-visible\:tw-ring-2:focus-visible {
1937
1933
  --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
1938
1934
  --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);
1939
1935
  box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
1940
1936
  }
1941
- .theme-brandPage .focus\:tw-ring-offset-2:focus {
1937
+ .theme-brandPage .focus-visible\:tw-ring-offset-2:focus-visible {
1942
1938
  --tw-ring-offset-width: 2px;
1943
1939
  }
1944
1940
  @media (min-width: 640px) {
@@ -1438,10 +1438,6 @@ video {
1438
1438
  --tw-bg-opacity: 1;
1439
1439
  background-color: rgb(199 204 207 / var(--tw-bg-opacity));
1440
1440
  }
1441
- .theme-crazyDomains .tw-bg-grayscale-700 {
1442
- --tw-bg-opacity: 1;
1443
- background-color: rgb(87 97 99 / var(--tw-bg-opacity));
1444
- }
1445
1441
  .theme-crazyDomains .tw-bg-info-100 {
1446
1442
  --tw-bg-opacity: 1;
1447
1443
  background-color: rgb(230 246 253 / var(--tw-bg-opacity));
@@ -2257,16 +2253,16 @@ video {
2257
2253
  outline: 2px solid transparent;
2258
2254
  outline-offset: 2px;
2259
2255
  }
2260
- .theme-crazyDomains .focus\:tw-ring-2:focus {
2256
+ .theme-crazyDomains .focus-visible\:tw-ring-2:focus-visible {
2261
2257
  --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
2262
2258
  --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);
2263
2259
  box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
2264
2260
  }
2265
- .theme-crazyDomains .focus\:tw-ring-primary-500:focus {
2261
+ .theme-crazyDomains .focus-visible\:tw-ring-primary-500:focus-visible {
2266
2262
  --tw-ring-opacity: 1;
2267
2263
  --tw-ring-color: rgb(111 172 47 / var(--tw-ring-opacity));
2268
2264
  }
2269
- .theme-crazyDomains .focus\:tw-ring-offset-2:focus {
2265
+ .theme-crazyDomains .focus-visible\:tw-ring-offset-2:focus-visible {
2270
2266
  --tw-ring-offset-width: 2px;
2271
2267
  }
2272
2268
  .theme-crazyDomains .tw-group:hover .group-hover\:tw-text-info-500 {
@@ -1438,10 +1438,6 @@ video {
1438
1438
  --tw-bg-opacity: 1;
1439
1439
  background-color: rgb(209 209 209 / var(--tw-bg-opacity));
1440
1440
  }
1441
- .theme-designCom .tw-bg-grayscale-700 {
1442
- --tw-bg-opacity: 1;
1443
- background-color: rgb(38 38 38 / var(--tw-bg-opacity));
1444
- }
1445
1441
  .theme-designCom .tw-bg-info-100 {
1446
1442
  --tw-bg-opacity: 1;
1447
1443
  background-color: rgb(236 238 254 / var(--tw-bg-opacity));
@@ -2257,16 +2253,16 @@ video {
2257
2253
  outline: 2px solid transparent;
2258
2254
  outline-offset: 2px;
2259
2255
  }
2260
- .theme-designCom .focus\:tw-ring-2:focus {
2256
+ .theme-designCom .focus-visible\:tw-ring-2:focus-visible {
2261
2257
  --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
2262
2258
  --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);
2263
2259
  box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
2264
2260
  }
2265
- .theme-designCom .focus\:tw-ring-primary-500:focus {
2261
+ .theme-designCom .focus-visible\:tw-ring-primary-500:focus-visible {
2266
2262
  --tw-ring-opacity: 1;
2267
2263
  --tw-ring-color: rgb(63 89 246 / var(--tw-ring-opacity));
2268
2264
  }
2269
- .theme-designCom .focus\:tw-ring-offset-2:focus {
2265
+ .theme-designCom .focus-visible\:tw-ring-offset-2:focus-visible {
2270
2266
  --tw-ring-offset-width: 2px;
2271
2267
  }
2272
2268
  .theme-designCom .tw-group:hover .group-hover\:tw-text-info-500 {
@@ -1438,10 +1438,6 @@ video {
1438
1438
  --tw-bg-opacity: 1;
1439
1439
  background-color: rgb(204 204 204 / var(--tw-bg-opacity));
1440
1440
  }
1441
- .theme-designCrowd .tw-bg-grayscale-700 {
1442
- --tw-bg-opacity: 1;
1443
- background-color: rgb(82 93 96 / var(--tw-bg-opacity));
1444
- }
1445
1441
  .theme-designCrowd .tw-bg-info-100 {
1446
1442
  --tw-bg-opacity: 1;
1447
1443
  background-color: rgb(207 234 251 / var(--tw-bg-opacity));
@@ -2257,16 +2253,16 @@ video {
2257
2253
  outline: 2px solid transparent;
2258
2254
  outline-offset: 2px;
2259
2255
  }
2260
- .theme-designCrowd .focus\:tw-ring-2:focus {
2256
+ .theme-designCrowd .focus-visible\:tw-ring-2:focus-visible {
2261
2257
  --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
2262
2258
  --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);
2263
2259
  box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
2264
2260
  }
2265
- .theme-designCrowd .focus\:tw-ring-primary-500:focus {
2261
+ .theme-designCrowd .focus-visible\:tw-ring-primary-500:focus-visible {
2266
2262
  --tw-ring-opacity: 1;
2267
2263
  --tw-ring-color: rgb(17 151 235 / var(--tw-ring-opacity));
2268
2264
  }
2269
- .theme-designCrowd .focus\:tw-ring-offset-2:focus {
2265
+ .theme-designCrowd .focus-visible\:tw-ring-offset-2:focus-visible {
2270
2266
  --tw-ring-offset-width: 2px;
2271
2267
  }
2272
2268
  .theme-designCrowd .tw-group:hover .group-hover\:tw-text-info-500 {
@@ -24,34 +24,30 @@
24
24
  </button>
25
25
  </template>
26
26
 
27
- <script setup>
27
+ <script setup lang="ts">
28
28
  import { watch, toRef, computed } from 'vue';
29
29
  import Icon from '../Icon/Icon.vue';
30
30
  import { useVoiceToText } from '../../../useVoiceToText';
31
31
 
32
- const props = defineProps({
33
- lang: {
34
- type: String,
35
- default: 'en-US',
36
- },
37
- disabled: {
38
- type: Boolean,
39
- default: false,
40
- },
41
- size: {
42
- type: String,
43
- default: 'md',
44
- validator: (value) => ['sm', 'md', 'lg'].includes(value),
45
- },
46
- variant: {
47
- type: String,
48
- default: 'dark',
49
- validator: (value) => ['light', 'dark'].includes(value),
50
- },
32
+ type ButtonSize = 'sm' | 'md' | 'lg';
33
+ type ButtonVariant = 'light' | 'dark';
34
+
35
+ interface VoiceToTextButtonProps {
36
+ lang?: string;
37
+ disabled?: boolean;
38
+ size?: ButtonSize;
39
+ variant?: ButtonVariant;
40
+ }
41
+
42
+ const props = withDefaults(defineProps<VoiceToTextButtonProps>(), {
43
+ lang: 'en-US',
44
+ disabled: false,
45
+ size: 'md',
46
+ variant: 'dark',
51
47
  });
52
48
 
53
49
  const sizeClasses = computed(() => {
54
- const sizes = {
50
+ const sizes: Record<ButtonSize, string> = {
55
51
  sm: 'tw-w-8 tw-h-8',
56
52
  md: 'tw-w-10 tw-h-10',
57
53
  lg: 'tw-w-12 tw-h-12',
@@ -60,7 +56,7 @@ const sizeClasses = computed(() => {
60
56
  });
61
57
 
62
58
  const iconSize = computed(() => {
63
- const sizes = {
59
+ const sizes: Record<ButtonSize, string> = {
64
60
  sm: 'sm',
65
61
  md: 'md',
66
62
  lg: 'md',
@@ -68,7 +64,13 @@ const iconSize = computed(() => {
68
64
  return sizes[props.size];
69
65
  });
70
66
 
71
- const emit = defineEmits(['on-transcript', 'on-interim-transcript', 'on-start', 'on-stop', 'on-error']);
67
+ const emit = defineEmits<{
68
+ 'on-transcript': [transcript: string];
69
+ 'on-interim-transcript': [transcript: string];
70
+ 'on-start': [];
71
+ 'on-stop': [];
72
+ 'on-error': [error: string];
73
+ }>();
72
74
 
73
75
  const { isSupported, isListening, transcript, isFinal, error, toggle, setLang } = useVoiceToText({
74
76
  lang: props.lang,
@@ -0,0 +1,8 @@
1
+ /* eslint-disable no-undef, no-unused-vars */
2
+ export {};
3
+
4
+ declare global {
5
+ interface Window {
6
+ webkitSpeechRecognition: typeof SpeechRecognition;
7
+ }
8
+ }
@@ -1,13 +1,38 @@
1
- import { ref, computed, readonly } from 'vue';
1
+ /* eslint-disable no-undef */
2
+ import { ref, computed, readonly, type Ref, type ComputedRef, type DeepReadonly } from 'vue';
3
+
4
+ export interface UseVoiceToTextOptions {
5
+ lang?: string;
6
+ }
7
+
8
+ export interface UseVoiceToTextReturn {
9
+ isSupported: ComputedRef<boolean>;
10
+ isListening: DeepReadonly<Ref<boolean>>;
11
+ transcript: DeepReadonly<Ref<string>>;
12
+ isFinal: DeepReadonly<Ref<boolean>>;
13
+ error: DeepReadonly<Ref<string | null>>;
14
+ start: () => void;
15
+ stop: () => void;
16
+ toggle: () => void;
17
+ // eslint-disable-next-line no-unused-vars
18
+ setLang: (lang: string) => void;
19
+ }
20
+
21
+ interface VoiceToTextState {
22
+ isListening: Ref<boolean>;
23
+ transcript: Ref<string>;
24
+ isFinal: Ref<boolean>;
25
+ error: Ref<string | null>;
26
+ }
2
27
 
3
28
  // Singleton instance and state (lazily initialized)
4
- let recognition = null;
29
+ let recognition: SpeechRecognition | null = null;
5
30
  let isInitialized = false;
6
- let errorClearTimeout = null;
7
- let state = null;
31
+ let errorClearTimeout: ReturnType<typeof setTimeout> | null = null;
32
+ let state: VoiceToTextState | null = null;
8
33
 
9
34
  // Error message mapping per spec
10
- const ERROR_MESSAGES = {
35
+ const ERROR_MESSAGES: Record<string, string> = {
11
36
  'not-allowed': 'Microphone permission was denied. Please allow access.',
12
37
  'language-not-supported': 'This language is not supported.',
13
38
  network: 'A network error occurred. Please check your connection.',
@@ -16,13 +41,13 @@ const ERROR_MESSAGES = {
16
41
 
17
42
  const ERROR_CLEAR_DELAY = 5000;
18
43
 
19
- function getState() {
44
+ function getState(): VoiceToTextState {
20
45
  if (!state) {
21
46
  state = {
22
47
  isListening: ref(false),
23
48
  transcript: ref(''),
24
49
  isFinal: ref(false),
25
- error: ref(null),
50
+ error: ref<string | null>(null),
26
51
  };
27
52
  }
28
53
  return state;
@@ -31,30 +56,26 @@ function getState() {
31
56
  /**
32
57
  * Singleton composable that wraps the Web Speech API (SpeechRecognition).
33
58
  * All calls to useVoiceToText() return the same shared instance.
34
- *
35
- * @param {Object} options
36
- * @param {string} options.lang - BCP 47 language tag (default: 'en-US')
37
- * @returns {Object} Voice-to-text state and controls
38
59
  */
39
- export function useVoiceToText(options = {}) {
60
+ export function useVoiceToText(options: UseVoiceToTextOptions = {}): UseVoiceToTextReturn {
40
61
  const { lang = 'en-US' } = options;
41
62
 
42
63
  // Get or create shared state
43
64
  const { isListening, transcript, isFinal, error } = getState();
44
65
 
45
66
  // Check for browser support
46
- const SpeechRecognition =
67
+ const SpeechRecognitionCtor: typeof SpeechRecognition | null =
47
68
  typeof window !== 'undefined' ? window.SpeechRecognition || window.webkitSpeechRecognition : null;
48
69
 
49
- const isSupported = computed(() => !!SpeechRecognition);
70
+ const isSupported = computed(() => !!SpeechRecognitionCtor);
50
71
 
51
72
  // Initialize singleton once
52
- if (!isInitialized && SpeechRecognition) {
53
- recognition = new SpeechRecognition();
73
+ if (!isInitialized && SpeechRecognitionCtor) {
74
+ recognition = new SpeechRecognitionCtor();
54
75
  recognition.continuous = true;
55
76
  recognition.interimResults = true;
56
77
 
57
- recognition.onresult = (event) => {
78
+ recognition.onresult = (event: SpeechRecognitionEvent) => {
58
79
  let interimTranscript = '';
59
80
  let finalTranscript = '';
60
81
 
@@ -76,7 +97,7 @@ export function useVoiceToText(options = {}) {
76
97
  }
77
98
  };
78
99
 
79
- recognition.onerror = (event) => {
100
+ recognition.onerror = (event: SpeechRecognitionErrorEvent) => {
80
101
  // Suppress no-speech and aborted errors per spec
81
102
  if (event.error === 'no-speech' || event.error === 'aborted') {
82
103
  return;
@@ -124,10 +145,10 @@ export function useVoiceToText(options = {}) {
124
145
  try {
125
146
  recognition.start();
126
147
  isListening.value = true;
127
- } catch (e) {
148
+ } catch (e: unknown) {
128
149
  // Handle case where recognition is already started
129
150
  // eslint-disable-next-line no-console
130
- console.warn('[useVoiceToText] Failed to start:', e.message);
151
+ console.warn('[useVoiceToText] Failed to start:', (e as Error).message);
131
152
  }
132
153
  };
133
154
 
@@ -137,10 +158,10 @@ export function useVoiceToText(options = {}) {
137
158
  isListening.value = false;
138
159
  try {
139
160
  recognition.stop();
140
- } catch (e) {
161
+ } catch (e: unknown) {
141
162
  // Handle case where recognition is already stopped
142
163
  // eslint-disable-next-line no-console
143
- console.warn('[useVoiceToText] Failed to stop:', e.message);
164
+ console.warn('[useVoiceToText] Failed to stop:', (e as Error).message);
144
165
  }
145
166
  };
146
167
 
@@ -152,7 +173,7 @@ export function useVoiceToText(options = {}) {
152
173
  }
153
174
  };
154
175
 
155
- const setLang = (newLang) => {
176
+ const setLang = (newLang: string) => {
156
177
  if (recognition) {
157
178
  recognition.lang = newLang;
158
179
  }