@untemps/react-vocal 2.0.0-beta.14 → 2.0.0-beta.16

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/CHANGELOG.md CHANGED
@@ -1,3 +1,22 @@
1
+ # [2.0.0-beta.16](https://github.com/untemps/react-vocal/compare/v2.0.0-beta.15...v2.0.0-beta.16) (2026-05-24)
2
+
3
+
4
+ ### Features
5
+
6
+ * Export useCommands and fix README result callback examples ([#158](https://github.com/untemps/react-vocal/issues/158)) ([990636a](https://github.com/untemps/react-vocal/commit/990636a3d7b3a7981629b0172318ae9bc739abf8))
7
+
8
+ # [2.0.0-beta.15](https://github.com/untemps/react-vocal/compare/v2.0.0-beta.14...v2.0.0-beta.15) (2026-05-24)
9
+
10
+
11
+ ### chore
12
+
13
+ * Migrate codebase to TypeScript ([#156](https://github.com/untemps/react-vocal/issues/156)) ([86c6e49](https://github.com/untemps/react-vocal/commit/86c6e49a82f599b60356afb91f34b59f63710a44))
14
+
15
+
16
+ ### BREAKING CHANGES
17
+
18
+ * minimum supported Node.js version bumped from `^20.19.0 || >=22.12.0` to `>=22`, aligning with the underlying @untemps/vocal 2.x direct dependency
19
+
1
20
  # [2.0.0-beta.14](https://github.com/untemps/react-vocal/compare/v2.0.0-beta.13...v2.0.0-beta.14) (2026-05-24)
2
21
 
3
22
 
package/README.md CHANGED
@@ -23,10 +23,12 @@ far (see [caniuse](https://caniuse.com/#search=SpeechRecognition)). If the API i
23
23
  won't display anything.
24
24
 
25
25
  This component intends to catch a speech result as soon as possible. This can be a good fit for vocal commands or search
26
- field filling. For now on it does not support continuous speech (see [Roadmap](#roadmap) below).
27
- That means either a result is caught and returned or timeout is reached and the recognition is discarded.
28
- The `stop` function returned by children-as-function mechanism allows to prematurely discard the recognition before
29
- timeout elapses.
26
+ field filling. It also supports a continuous mode (`continuous` prop) for real-time transcription and always-on voice
27
+ command use cases see the `Vocal` component API table for details.
28
+
29
+ In single-shot mode (the default), either a result is caught and returned or the timeout is reached and the recognition
30
+ is discarded. The `stop` function returned by children-as-function mechanism allows to prematurely discard the
31
+ recognition before the timeout elapses.
30
32
 
31
33
  ### Special cases
32
34
 
@@ -54,6 +56,21 @@ yarn add fuse.js
54
56
 
55
57
  Without fuse.js, phrase commands fall back to case-insensitive exact matching. Single-word commands always use exact matching and never require fuse.js.
56
58
 
59
+ ## TypeScript
60
+
61
+ `@untemps/react-vocal` is written in TypeScript and ships full type declarations. The public surface is typed end-to-end:
62
+
63
+ - `Vocal` component props (`VocalProps`, `OnResultCallback`)
64
+ - `useVocal` hook signature, action tuple (`UseVocalActions`, `UseVocalReturn`)
65
+ - `useCommands` shapes (`CommandCallback`, `CommandsMap`, `TriggerCommand`)
66
+ - `isSupported` function (re-exported from `@untemps/vocal`)
67
+
68
+ ```typescript
69
+ import Vocal, { useVocal, isSupported, type VocalProps, type CommandsMap } from '@untemps/react-vocal'
70
+ ```
71
+
72
+ TypeScript is listed as an optional peer dependency (`>=6.0.0`) — install it only if your project uses TS.
73
+
57
74
  ## Usage
58
75
 
59
76
  ### `Vocal` component
@@ -267,10 +284,10 @@ const App = () => {
267
284
  setResult('')
268
285
  }
269
286
 
270
- const _onVocalResult = (result) => {
287
+ const _onVocalResult = (_event, bestAlternative) => {
271
288
  setIsListening(false)
272
289
 
273
- setResult(result)
290
+ setResult(bestAlternative)
274
291
  }
275
292
 
276
293
  const _onVocalError = (e) => {
@@ -344,6 +361,57 @@ const [, { start }] = useVocal('en-US')
344
361
  start({ signal: controller.signal })
345
362
  ```
346
363
 
364
+ ### `useCommands` hook
365
+
366
+ The `useCommands` hook is the same command-matching primitive used internally by the `Vocal` component. Export it directly when you build a custom UI on top of `useVocal` and want to reuse the matching logic instead of re-implementing it.
367
+
368
+ #### Basic usage
369
+
370
+ ```javascript
371
+ import { useVocal, useCommands } from '@untemps/react-vocal'
372
+
373
+ const App = () => {
374
+ const commands = {
375
+ rouge: () => setBorderColor('red'),
376
+ bleu: () => setBorderColor('blue'),
377
+ }
378
+ const triggerCommand = useCommands(commands)
379
+
380
+ const [, { start, subscribe }] = useVocal('fr-FR')
381
+
382
+ const _onResult = (_event, bestAlternative) => {
383
+ triggerCommand(bestAlternative)
384
+ }
385
+
386
+ const _onClick = () => {
387
+ subscribe('result', _onResult)
388
+ start()
389
+ }
390
+
391
+ return <button onClick={_onClick}>Listen</button>
392
+ }
393
+ ```
394
+
395
+ #### Signature
396
+
397
+ ```
398
+ useCommands(commands, precision)
399
+ ```
400
+
401
+ | Args | Type | Default | Description |
402
+ | --------- | ------ | ------- | -------------------------------------------------------------------------------------------------------- |
403
+ | commands | object | null | A `{ key: callback }` map. Keys are lowercased internally. Callbacks receive `(rawInput, commandKey)`. |
404
+ | precision | number | 0.4 | Fuse.js score threshold for **phrase** command keys only (lower = stricter). Single-word keys use exact lookup. |
405
+
406
+ #### Return value
407
+
408
+ `useCommands` returns a `triggerCommand(rawInput)` function. Passing a transcript runs it against the commands map and invokes the matching callback if any, returning its result. Returns `null` when no command matches.
409
+
410
+ Matching rules:
411
+
412
+ - **Single-word keys** (e.g. `rouge`, `submit`): exact case-insensitive lookup, with each word of the input tried individually so a key fires even when embedded in a phrase (`je veux du rouge` triggers `rouge`).
413
+ - **Phrase keys** (e.g. `change the background color`): Fuse.js fuzzy matching against the joined transcript, gated by `precision`. fuse.js is loaded lazily; if it's not installed, the hook falls back to substring matching.
414
+
347
415
  ### Browser support flag
348
416
 
349
417
  #### Basic usage
@@ -401,4 +469,3 @@ Contributions are warmly welcomed:
401
469
  ## Roadmap
402
470
 
403
471
  - Add a connector management to plug external speech-to-text services in
404
- - Support continuous speech
package/dist/index.cjs CHANGED
@@ -1,7 +1,7 @@
1
- Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:`Module`}});var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,t)=>()=>(t||(e((t={exports:{}}).exports,t),e=null),t.exports),s=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},c=(n,r,a)=>(a=n==null?{}:e(i(n)),s(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));let l=require(`react`);l=c(l,1);var u=()=>!!navigator.permissions,d=()=>!!navigator.mediaDevices,f=async(e,t,{signal:n}={})=>{if(!u()||!d())throw new DOMException(`Navigator API: permissions or Navigator API: mediaDevices not supported`,`NOT_SUPPORTED_ERR`);if(n?.throwIfAborted(),(await navigator.permissions.query({name:e})).state===`denied`)throw new DOMException(`Permission denied`,`NOT_ALLOWED_ERR`);n?.throwIfAborted();let r=navigator.mediaDevices.getUserMedia(t);if(!n)return r;let i,a=new Promise((e,t)=>{i=()=>t(n.reason),n.addEventListener(`abort`,i,{once:!0})});return Promise.race([r,a]).finally(()=>{n.removeEventListener(`abort`,i)})},p={AUDIO_END:`audioend`,AUDIO_START:`audiostart`,END:`end`,ERROR:`error`,NO_MATCH:`nomatch`,RESULT:`result`,SOUND_END:`soundend`,SOUND_START:`soundstart`,SPEECH_END:`speechend`,SPEECH_START:`speechstart`,START:`start`},m=1e3,h=new Set([`not-allowed`,`service-not-allowed`,`audio-capture`]),g={grammars:null,lang:`en-US`,continuous:!1,interimResults:!1,maxAlternatives:1},_=()=>{if(!(typeof window>`u`))return window.SpeechRecognition??window.webkitSpeechRecognition??window.mozSpeechRecognition??window.msSpeechRecognition},v=()=>window.SpeechGrammarList??window.webkitSpeechGrammarList??window.mozSpeechGrammarList??window.msSpeechGrammarList,y=e=>e.reduce((e,t)=>(t.confidence??0)>(e.confidence??0)?t:e),b=e=>{let t=e.slice();return Object.defineProperty(t,`isFinal`,{value:!0}),Object.defineProperty(t,`item`,{value:e=>t[e]}),t},x=e=>{let t=e.slice();return Object.defineProperty(t,`item`,{value:e=>t[e]}),t},S=e=>Object.values(p).includes(e),C=e=>`Unknown event type "${e}". Valid types are: ${Object.values(p).join(`, `)}.`,w=()=>!!_()&&!!u()&&!!d(),T=e=>{let t=_();if(!t)throw new DOMException(`SpeechRecognition not supported`,`NOT_SUPPORTED_ERR`);let n=new t,r={},i=!1,a=!1,o=0,s=null,c=!1,l=[],u={...g,...e??{}};if(n.lang=u.lang,n.continuous=u.continuous,n.interimResults=u.interimResults,n.maxAlternatives=u.maxAlternatives,u.grammars)n.grammars=u.grammars;else{let e=v();n.grammars=e?new e:null}let d=()=>{s!==null&&(clearTimeout(s),s=null),c=!1},w=()=>!!n&&!a&&n.continuous,T=()=>{s=null;try{n.start(),o=Date.now()}catch{c=!1,i=!1}},E=()=>{let e=l;if(l=[],e.length===0||!r[p.RESULT]?.length)return;let t=e.map(e=>y(Array.from(e)).transcript).join(` `).trim(),n=Object.assign(new Event(p.RESULT),{resultIndex:0,results:x(e)});[...r[p.RESULT]].forEach(({callback:e})=>{e(n,t,[t])})},D=[[p.END,e=>{if(w()){let t=Math.max(0,m-(Date.now()-o));c=!0,s=setTimeout(T,t),e.stopImmediatePropagation();return}E(),i=!1}],[p.START,e=>{c&&(e.stopImmediatePropagation(),queueMicrotask(()=>{c=!1}))}],[p.ERROR,e=>{h.has(e.error)&&(a=!0,d(),i=!1)}],[p.RESULT,e=>{if(!u.continuous)return;let t=e,n=t.results?.[t.resultIndex];n?.isFinal&&l.push(b(Array.from(n)))}]];D.forEach(([e,t])=>n.addEventListener(e,t));let O=async({signal:e}={})=>{if(n)try{let t=await f(`microphone`,{audio:!0},{signal:e});if(e?.aborted||!n)return;if(!t)throw Error(`Unable to retrieve the stream from media device`);a=!1,l=[],n.start(),i=!0,o=Date.now()}catch(e){if(e instanceof Error&&e.name===`AbortError`)return;throw e}},k=()=>{n&&(a=!0,d(),n.stop(),i=!1)},A=()=>{n&&(a=!0,d(),l=[],n.abort(),i=!1)},j=(e,t)=>{if(!S(e))throw Error(C(e));if(!n)return;let i=n=>{if(c&&(e===p.END||e===p.START))return;if(e!==p.RESULT){t(n);return}let r=n;if(!(r.results?.length>0)||r.resultIndex>=r.results.length){t(n);return}let i=r.results[r.resultIndex];if(u.continuous&&i.isFinal)return;let a=Array.from(i);t(n,y(a).transcript,a.map(e=>e.transcript))};n.addEventListener(e,i),r[e]||(r[e]=[]),r[e].push({callback:t,handler:i})},M=(e,t)=>{if(!S(e))throw Error(C(e));if(!(!n||!r[e]))if(t!==void 0){let i=r[e].findIndex(e=>e.callback===t);i!==-1&&(n.removeEventListener(e,r[e][i].handler),r[e].splice(i,1),r[e].length===0&&delete r[e])}else r[e].forEach(({handler:t})=>n.removeEventListener(e,t)),delete r[e]};return{get isRecording(){return i},start:O,stop:k,abort:A,on:j,off:M,cleanup:()=>{k(),Object.keys(r).forEach(e=>M(e)),D.forEach(([e,t])=>n?.removeEventListener(e,t)),n=null}}},E=e=>typeof e==`function`,D=(e=`en-US`,t=null,n=1,r=!1,i=null)=>{let a=(0,l.useRef)(null),[o,s]=(0,l.useState)(!1),c=(0,l.useMemo)(()=>w(),[]);return(0,l.useEffect)(()=>{if(c){let o=i||T({lang:e,grammars:t,maxAlternatives:n,continuous:r});a.current=o;let c=()=>s(!0),l=()=>s(!1);return o.on(`start`,c),o.on(`end`,l),o.on(`error`,l),()=>{o.off(`start`,c),o.off(`end`,l),o.off(`error`,l),o.abort(),o.cleanup(),s(!1)}}},[e,t,n,r,i,c]),[a,{start:(0,l.useCallback)(e=>{if(a.current)return s(!0),a.current.start(e)},[]),stop:(0,l.useCallback)(()=>{a.current&&a.current.stop()},[]),abort:(0,l.useCallback)(()=>{a.current&&a.current.abort()},[]),subscribe:(0,l.useCallback)((e,t)=>{a.current&&a.current.on(e,t)},[]),unsubscribe:(0,l.useCallback)((e,t)=>{a.current&&a.current.off(e,t)},[]),clean:(0,l.useCallback)(()=>{a.current&&a.current.cleanup()},[]),isRecording:o}]},O=(e,t=0)=>{let n=(0,l.useRef)(-1),r=(0,l.useCallback)(()=>{clearTimeout(n.current),n.current=-1},[]),i=(0,l.useCallback)(()=>{r(),n.current=setTimeout(e,t)},[e,t,r]);return(0,l.useEffect)(()=>r,[r]),[i,r]},k=(e,t=.4)=>{let n=(0,l.useMemo)(()=>e?Object.entries(e).reduce((e,[t,n])=>({...e,[t.toLowerCase()]:n}),{}):{},[e]),r=(0,l.useMemo)(()=>Object.keys(n),[n]),i=(0,l.useMemo)(()=>r.some(e=>e.includes(` `)),[r]),a=(0,l.useRef)(null);return(0,l.useEffect)(()=>{if(!i){a.current=null;return}let e=!1;return import(`fuse.js`).then(t=>{e||(a.current=new(t.default??t)(r,{includeScore:!0,ignoreLocation:!0}))}).catch(()=>{e||process.env.NODE_ENV!==`production`&&console.warn(`[react-vocal] fuse.js is not installed. Phrase command keys will fall back to exact matching. Install fuse.js to enable fuzzy matching: npm install fuse.js`)}),()=>{e=!0}},[i,r]),e=>{if(!r.length)return null;if(!i){let t=e.trim().split(/\s+/),r=t.length>1?t:[e.trim()];for(let e of r){let t=e.toLowerCase();if(t in n)return n[t]?.(e,t)}return null}let o=a.current;if(o){let r=o.search(e).filter(e=>e.score<t);if(r?.length){let t=r[0].item.toLowerCase();return n[t]?.(e,t)}}else{let t=e.toLowerCase(),i=r.find(e=>t.includes(e)||e.includes(t));if(i)return n[i]?.(e,i)}return null}},A=o((e=>{var t=Symbol.for(`react.transitional.element`),n=Symbol.for(`react.fragment`);function r(e,n,r){var i=null;if(r!==void 0&&(i=``+r),n.key!==void 0&&(i=``+n.key),`key`in n)for(var a in r={},n)a!==`key`&&(r[a]=n[a]);else r=n;return n=r.ref,{$$typeof:t,type:e,key:i,ref:n===void 0?null:n,props:r}}e.Fragment=n,e.jsx=r,e.jsxs=r})),j=o((e=>{process.env.NODE_ENV!==`production`&&(function(){function t(e){if(e==null)return null;if(typeof e==`function`)return e.$$typeof===O?null:e.displayName||e.name||null;if(typeof e==`string`)return e;switch(e){case _:return`Fragment`;case y:return`Profiler`;case v:return`StrictMode`;case C:return`Suspense`;case w:return`SuspenseList`;case D:return`Activity`}if(typeof e==`object`)switch(typeof e.tag==`number`&&console.error(`Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue.`),e.$$typeof){case g:return`Portal`;case x:return e.displayName||`Context`;case b:return(e._context.displayName||`Context`)+`.Consumer`;case S:var n=e.render;return e=e.displayName,e||=(e=n.displayName||n.name||``,e===``?`ForwardRef`:`ForwardRef(`+e+`)`),e;case T:return n=e.displayName||null,n===null?t(e.type)||`Memo`:n;case E:n=e._payload,e=e._init;try{return t(e(n))}catch{}}return null}function n(e){return``+e}function r(e){try{n(e);var t=!1}catch{t=!0}if(t){t=console;var r=t.error,i=typeof Symbol==`function`&&Symbol.toStringTag&&e[Symbol.toStringTag]||e.constructor.name||`Object`;return r.call(t,`The provided key is an unsupported type %s. This value must be coerced to a string before using it here.`,i),n(e)}}function i(e){if(e===_)return`<>`;if(typeof e==`object`&&e&&e.$$typeof===E)return`<...>`;try{var n=t(e);return n?`<`+n+`>`:`<...>`}catch{return`<...>`}}function a(){var e=k.A;return e===null?null:e.getOwner()}function o(){return Error(`react-stack-top-frame`)}function s(e){if(A.call(e,`key`)){var t=Object.getOwnPropertyDescriptor(e,`key`).get;if(t&&t.isReactWarning)return!1}return e.key!==void 0}function c(e,t){function n(){N||(N=!0,console.error("%s: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://react.dev/link/special-props)",t))}n.isReactWarning=!0,Object.defineProperty(e,`key`,{get:n,configurable:!0})}function l(){var e=t(this.type);return P[e]||(P[e]=!0,console.error(`Accessing element.ref was removed in React 19. ref is now a regular prop. It will be removed from the JSX Element type in a future release.`)),e=this.props.ref,e===void 0?null:e}function u(e,t,n,r,i,a){var o=n.ref;return e={$$typeof:h,type:e,key:t,props:n,_owner:r},(o===void 0?null:o)===null?Object.defineProperty(e,`ref`,{enumerable:!1,value:null}):Object.defineProperty(e,`ref`,{enumerable:!1,get:l}),e._store={},Object.defineProperty(e._store,`validated`,{configurable:!1,enumerable:!1,writable:!0,value:0}),Object.defineProperty(e,`_debugInfo`,{configurable:!1,enumerable:!1,writable:!0,value:null}),Object.defineProperty(e,`_debugStack`,{configurable:!1,enumerable:!1,writable:!0,value:i}),Object.defineProperty(e,`_debugTask`,{configurable:!1,enumerable:!1,writable:!0,value:a}),Object.freeze&&(Object.freeze(e.props),Object.freeze(e)),e}function d(e,n,i,o,l,d){var p=n.children;if(p!==void 0)if(o)if(j(p)){for(o=0;o<p.length;o++)f(p[o]);Object.freeze&&Object.freeze(p)}else console.error(`React.jsx: Static children should always be an array. You are likely explicitly calling React.jsxs or React.jsxDEV. Use the Babel transform instead.`);else f(p);if(A.call(n,`key`)){p=t(e);var m=Object.keys(n).filter(function(e){return e!==`key`});o=0<m.length?`{key: someKey, `+m.join(`: ..., `)+`: ...}`:`{key: someKey}`,L[p+o]||(m=0<m.length?`{`+m.join(`: ..., `)+`: ...}`:`{}`,console.error(`A props object containing a "key" prop is being spread into JSX:
1
+ Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:`Module`}});var e=(e,t)=>()=>(t||(e((t={exports:{}}).exports,t),e=null),t.exports);let t=require(`react`);var n=()=>!!navigator.permissions,r=()=>!!navigator.mediaDevices,i=async(e,t,{signal:i}={})=>{if(!n()||!r())throw new DOMException(`Navigator API: permissions or Navigator API: mediaDevices not supported`,`NOT_SUPPORTED_ERR`);if(i?.throwIfAborted(),(await navigator.permissions.query({name:e})).state===`denied`)throw new DOMException(`Permission denied`,`NOT_ALLOWED_ERR`);i?.throwIfAborted();let a=navigator.mediaDevices.getUserMedia(t);if(!i)return a;let o,s=new Promise((e,t)=>{o=()=>t(i.reason),i.addEventListener(`abort`,o,{once:!0})});return Promise.race([a,s]).finally(()=>{i.removeEventListener(`abort`,o)})},a={AUDIO_END:`audioend`,AUDIO_START:`audiostart`,END:`end`,ERROR:`error`,NO_MATCH:`nomatch`,RESULT:`result`,SOUND_END:`soundend`,SOUND_START:`soundstart`,SPEECH_END:`speechend`,SPEECH_START:`speechstart`,START:`start`},o=1e3,s=new Set([`not-allowed`,`service-not-allowed`,`audio-capture`]),c={grammars:null,lang:`en-US`,continuous:!1,interimResults:!1,maxAlternatives:1},l=()=>{if(!(typeof window>`u`))return window.SpeechRecognition??window.webkitSpeechRecognition??window.mozSpeechRecognition??window.msSpeechRecognition},u=()=>window.SpeechGrammarList??window.webkitSpeechGrammarList??window.mozSpeechGrammarList??window.msSpeechGrammarList,d=e=>e.reduce((e,t)=>(t.confidence??0)>(e.confidence??0)?t:e),f=e=>{let t=e.slice();return Object.defineProperty(t,`isFinal`,{value:!0}),Object.defineProperty(t,`item`,{value:e=>t[e]}),t},p=e=>{let t=e.slice();return Object.defineProperty(t,`item`,{value:e=>t[e]}),t},m=e=>Object.values(a).includes(e),h=e=>`Unknown event type "${e}". Valid types are: ${Object.values(a).join(`, `)}.`,g=()=>!!l()&&!!n()&&!!r(),_=e=>{let t=l();if(!t)throw new DOMException(`SpeechRecognition not supported`,`NOT_SUPPORTED_ERR`);let n=new t,r={},g=!1,_=!1,v=0,y=null,b=!1,x=[],S={...c,...e??{}};if(n.lang=S.lang,n.continuous=S.continuous,n.interimResults=S.interimResults,n.maxAlternatives=S.maxAlternatives,S.grammars)n.grammars=S.grammars;else{let e=u();n.grammars=e?new e:null}let C=()=>{y!==null&&(clearTimeout(y),y=null),b=!1},w=()=>!!n&&!_&&n.continuous,T=()=>{y=null;try{n.start(),v=Date.now()}catch{b=!1,g=!1}},E=()=>{let e=x;if(x=[],e.length===0||!r[a.RESULT]?.length)return;let t=e.map(e=>d(Array.from(e)).transcript).join(` `).trim(),n=Object.assign(new Event(a.RESULT),{resultIndex:0,results:p(e)});[...r[a.RESULT]].forEach(({callback:e})=>{e(n,t,[t])})},D=[[a.END,e=>{if(w()){let t=Math.max(0,o-(Date.now()-v));b=!0,y=setTimeout(T,t),e.stopImmediatePropagation();return}E(),g=!1}],[a.START,e=>{b&&(e.stopImmediatePropagation(),queueMicrotask(()=>{b=!1}))}],[a.ERROR,e=>{s.has(e.error)&&(_=!0,C(),g=!1)}],[a.RESULT,e=>{if(!S.continuous)return;let t=e,n=t.results?.[t.resultIndex];n?.isFinal&&x.push(f(Array.from(n)))}]];D.forEach(([e,t])=>n.addEventListener(e,t));let O=async({signal:e}={})=>{if(n)try{let t=await i(`microphone`,{audio:!0},{signal:e});if(e?.aborted||!n)return;if(!t)throw Error(`Unable to retrieve the stream from media device`);_=!1,x=[],n.start(),g=!0,v=Date.now()}catch(e){if(e instanceof Error&&e.name===`AbortError`)return;throw e}},k=()=>{n&&(_=!0,C(),n.stop(),g=!1)},A=()=>{n&&(_=!0,C(),x=[],n.abort(),g=!1)},j=(e,t)=>{if(!m(e))throw Error(h(e));if(!n)return;let i=n=>{if(b&&(e===a.END||e===a.START))return;if(e!==a.RESULT){t(n);return}let r=n;if(!(r.results?.length>0)||r.resultIndex>=r.results.length){t(n);return}let i=r.results[r.resultIndex];if(S.continuous&&i.isFinal)return;let o=Array.from(i);t(n,d(o).transcript,o.map(e=>e.transcript))};n.addEventListener(e,i),r[e]||(r[e]=[]),r[e].push({callback:t,handler:i})},M=(e,t)=>{if(!m(e))throw Error(h(e));if(!(!n||!r[e]))if(t!==void 0){let i=r[e].findIndex(e=>e.callback===t);i!==-1&&(n.removeEventListener(e,r[e][i].handler),r[e].splice(i,1),r[e].length===0&&delete r[e])}else r[e].forEach(({handler:t})=>n.removeEventListener(e,t)),delete r[e]};return{get isRecording(){return g},start:O,stop:k,abort:A,on:j,off:M,cleanup:()=>{k(),Object.keys(r).forEach(e=>M(e)),D.forEach(([e,t])=>n?.removeEventListener(e,t)),n=null}}},v=e=>typeof e==`function`,y=(e=`en-US`,n=null,r=1,i=!1,a=null)=>{let o=(0,t.useRef)(null),[s,c]=(0,t.useState)(!1),l=(0,t.useMemo)(()=>g(),[]);return(0,t.useEffect)(()=>{if(l){let t=a||_({lang:e,grammars:n,maxAlternatives:r,continuous:i});o.current=t;let s=()=>c(!0),l=()=>c(!1);return t.on(`start`,s),t.on(`end`,l),t.on(`error`,l),()=>{t.off(`start`,s),t.off(`end`,l),t.off(`error`,l),t.abort(),t.cleanup(),c(!1)}}},[e,n,r,i,a,l]),[o,{start:(0,t.useCallback)(e=>{if(o.current)return c(!0),o.current.start(e)},[]),stop:(0,t.useCallback)(()=>{o.current&&o.current.stop()},[]),abort:(0,t.useCallback)(()=>{o.current&&o.current.abort()},[]),subscribe:(0,t.useCallback)((e,t)=>{o.current&&o.current.on(e,t)},[]),unsubscribe:(0,t.useCallback)((e,t)=>{o.current&&o.current.off(e,t)},[]),clean:(0,t.useCallback)(()=>{o.current&&o.current.cleanup()},[]),isRecording:s}]},b=(e,n=0)=>{let r=(0,t.useRef)(-1),i=(0,t.useCallback)(()=>{clearTimeout(r.current),r.current=-1},[]),a=(0,t.useCallback)(()=>{i(),r.current=setTimeout(e,n)},[e,n,i]);return(0,t.useEffect)(()=>i,[i]),[a,i]},x=(e,n=.4)=>{let r=(0,t.useMemo)(()=>e?Object.entries(e).reduce((e,[t,n])=>({...e,[t.toLowerCase()]:n}),{}):{},[e]),i=(0,t.useMemo)(()=>Object.keys(r),[r]),a=(0,t.useMemo)(()=>i.some(e=>e.includes(` `)),[i]),o=(0,t.useRef)(null);return(0,t.useEffect)(()=>{if(!a){o.current=null;return}let e=!1;return import(`fuse.js`).then(t=>{e||(o.current=new(t.default??t)(i,{includeScore:!0,ignoreLocation:!0}))}).catch(()=>{e||process.env.NODE_ENV!==`production`&&console.warn(`[react-vocal] fuse.js is not installed. Phrase command keys will fall back to exact matching. Install fuse.js to enable fuzzy matching: npm install fuse.js`)}),()=>{e=!0}},[a,i]),e=>{if(!i.length)return null;if(!a){let t=e.trim().split(/\s+/),n=t.length>1?t:[e.trim()];for(let e of n){let t=e.toLowerCase();if(t in r)return r[t]?.(e,t)}return null}let t=o.current;if(t){let i=t.search(e).filter(e=>(e.score??1)<n);if(i?.length){let t=i[0].item.toLowerCase();return r[t]?.(e,t)}}else{let t=e.toLowerCase(),n=i.find(e=>t.includes(e)||e.includes(t));if(n)return r[n]?.(e,n)}return null}},S=e((e=>{var t=Symbol.for(`react.transitional.element`),n=Symbol.for(`react.fragment`);function r(e,n,r){var i=null;if(r!==void 0&&(i=``+r),n.key!==void 0&&(i=``+n.key),`key`in n)for(var a in r={},n)a!==`key`&&(r[a]=n[a]);else r=n;return n=r.ref,{$$typeof:t,type:e,key:i,ref:n===void 0?null:n,props:r}}e.Fragment=n,e.jsx=r,e.jsxs=r})),C=e((e=>{process.env.NODE_ENV!==`production`&&(function(){function t(e){if(e==null)return null;if(typeof e==`function`)return e.$$typeof===O?null:e.displayName||e.name||null;if(typeof e==`string`)return e;switch(e){case _:return`Fragment`;case y:return`Profiler`;case v:return`StrictMode`;case C:return`Suspense`;case w:return`SuspenseList`;case D:return`Activity`}if(typeof e==`object`)switch(typeof e.tag==`number`&&console.error(`Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue.`),e.$$typeof){case g:return`Portal`;case x:return e.displayName||`Context`;case b:return(e._context.displayName||`Context`)+`.Consumer`;case S:var n=e.render;return e=e.displayName,e||=(e=n.displayName||n.name||``,e===``?`ForwardRef`:`ForwardRef(`+e+`)`),e;case T:return n=e.displayName||null,n===null?t(e.type)||`Memo`:n;case E:n=e._payload,e=e._init;try{return t(e(n))}catch{}}return null}function n(e){return``+e}function r(e){try{n(e);var t=!1}catch{t=!0}if(t){t=console;var r=t.error,i=typeof Symbol==`function`&&Symbol.toStringTag&&e[Symbol.toStringTag]||e.constructor.name||`Object`;return r.call(t,`The provided key is an unsupported type %s. This value must be coerced to a string before using it here.`,i),n(e)}}function i(e){if(e===_)return`<>`;if(typeof e==`object`&&e&&e.$$typeof===E)return`<...>`;try{var n=t(e);return n?`<`+n+`>`:`<...>`}catch{return`<...>`}}function a(){var e=k.A;return e===null?null:e.getOwner()}function o(){return Error(`react-stack-top-frame`)}function s(e){if(A.call(e,`key`)){var t=Object.getOwnPropertyDescriptor(e,`key`).get;if(t&&t.isReactWarning)return!1}return e.key!==void 0}function c(e,t){function n(){N||(N=!0,console.error("%s: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://react.dev/link/special-props)",t))}n.isReactWarning=!0,Object.defineProperty(e,`key`,{get:n,configurable:!0})}function l(){var e=t(this.type);return P[e]||(P[e]=!0,console.error(`Accessing element.ref was removed in React 19. ref is now a regular prop. It will be removed from the JSX Element type in a future release.`)),e=this.props.ref,e===void 0?null:e}function u(e,t,n,r,i,a){var o=n.ref;return e={$$typeof:h,type:e,key:t,props:n,_owner:r},(o===void 0?null:o)===null?Object.defineProperty(e,`ref`,{enumerable:!1,value:null}):Object.defineProperty(e,`ref`,{enumerable:!1,get:l}),e._store={},Object.defineProperty(e._store,`validated`,{configurable:!1,enumerable:!1,writable:!0,value:0}),Object.defineProperty(e,`_debugInfo`,{configurable:!1,enumerable:!1,writable:!0,value:null}),Object.defineProperty(e,`_debugStack`,{configurable:!1,enumerable:!1,writable:!0,value:i}),Object.defineProperty(e,`_debugTask`,{configurable:!1,enumerable:!1,writable:!0,value:a}),Object.freeze&&(Object.freeze(e.props),Object.freeze(e)),e}function d(e,n,i,o,l,d){var p=n.children;if(p!==void 0)if(o)if(j(p)){for(o=0;o<p.length;o++)f(p[o]);Object.freeze&&Object.freeze(p)}else console.error(`React.jsx: Static children should always be an array. You are likely explicitly calling React.jsxs or React.jsxDEV. Use the Babel transform instead.`);else f(p);if(A.call(n,`key`)){p=t(e);var m=Object.keys(n).filter(function(e){return e!==`key`});o=0<m.length?`{key: someKey, `+m.join(`: ..., `)+`: ...}`:`{key: someKey}`,L[p+o]||(m=0<m.length?`{`+m.join(`: ..., `)+`: ...}`:`{}`,console.error(`A props object containing a "key" prop is being spread into JSX:
2
2
  let props = %s;
3
3
  <%s {...props} />
4
4
  React keys must be passed directly to JSX without using spread:
5
5
  let props = %s;
6
- <%s key={someKey} {...props} />`,o,p,m,p),L[p+o]=!0)}if(p=null,i!==void 0&&(r(i),p=``+i),s(n)&&(r(n.key),p=``+n.key),`key`in n)for(var h in i={},n)h!==`key`&&(i[h]=n[h]);else i=n;return p&&c(i,typeof e==`function`?e.displayName||e.name||`Unknown`:e),u(e,p,i,a(),l,d)}function f(e){p(e)?e._store&&(e._store.validated=1):typeof e==`object`&&e&&e.$$typeof===E&&(e._payload.status===`fulfilled`?p(e._payload.value)&&e._payload.value._store&&(e._payload.value._store.validated=1):e._store&&(e._store.validated=1))}function p(e){return typeof e==`object`&&!!e&&e.$$typeof===h}var m=require(`react`),h=Symbol.for(`react.transitional.element`),g=Symbol.for(`react.portal`),_=Symbol.for(`react.fragment`),v=Symbol.for(`react.strict_mode`),y=Symbol.for(`react.profiler`),b=Symbol.for(`react.consumer`),x=Symbol.for(`react.context`),S=Symbol.for(`react.forward_ref`),C=Symbol.for(`react.suspense`),w=Symbol.for(`react.suspense_list`),T=Symbol.for(`react.memo`),E=Symbol.for(`react.lazy`),D=Symbol.for(`react.activity`),O=Symbol.for(`react.client.reference`),k=m.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,A=Object.prototype.hasOwnProperty,j=Array.isArray,M=console.createTask?console.createTask:function(){return null};m={react_stack_bottom_frame:function(e){return e()}};var N,P={},F=m.react_stack_bottom_frame.bind(m,o)(),I=M(i(o)),L={};e.Fragment=_,e.jsx=function(e,t,n){var r=1e4>k.recentlyCreatedOwnerStacks++;return d(e,t,n,!1,r?Error(`react-stack-top-frame`):F,r?M(i(e)):I)},e.jsxs=function(e,t,n){var r=1e4>k.recentlyCreatedOwnerStacks++;return d(e,t,n,!0,r?Error(`react-stack-top-frame`):F,r?M(i(e)):I)}})()})),M=o(((e,t)=>{process.env.NODE_ENV===`production`?t.exports=A():t.exports=j()}))(),N=({color:e=`black`,activeColor:t=`red`,isActive:n=!1})=>(0,M.jsx)(`svg`,{"data-testid":`__icon-root__`,xmlns:`http://www.w3.org/2000/svg`,width:`100%`,height:`100%`,viewBox:`0 0 24 24`,"aria-hidden":`true`,children:(0,M.jsxs)(`g`,{children:[(0,M.jsx)(`path`,{"data-testid":`__icon-path__`,fill:e,d:`M12 14c1.66 0 2.99-1.34 2.99-3L15 5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3zm5.3-3c0 3-2.54 5.1-5.3 5.1S6.7 14 6.7 11H5c0 3.41 2.72 6.23 6 6.72V21h2v-3.28c3.28-.48 6-3.3 6-6.72h-1.7z`}),n&&(0,M.jsx)(`circle`,{"data-testid":`__icon-active__`,fill:t,cx:`16`,cy:`4`,r:`4`})]})}),P=(e,t)=>{for(let n of e)for(let e of n)if(t(e.transcript??``)!==null)return},F=({children:e,commands:t=null,lang:n=`en-US`,grammars:r=null,timeout:i=3e3,silenceTimeout:a=null,precision:o=.4,maxAlternatives:s=1,continuous:c=!1,ariaLabel:u=`start recognition`,style:d=null,className:f=null,outlineStyle:p=`2px solid`,onStart:m=null,onEnd:h=null,onSpeechStart:g=null,onSpeechEnd:_=null,onResult:v=null,onError:y=null,onNoMatch:b=null,signal:x=null,__rsInstance:S})=>{let C=(0,l.useRef)(null),T=(0,l.useMemo)(()=>w(),[]),[,{start:A,stop:j,subscribe:F,unsubscribe:I,isRecording:L}]=D(n,r,s,c,S),R=k(t,o),z=(0,l.useRef)({});z.current={onStart:m,onEnd:h,onSpeechStart:g,onSpeechEnd:_,onResult:v,onError:y,onNoMatch:b};let B=(0,l.useRef)(c);B.current=c;let V=(0,l.useRef)(R);V.current=R;let H=(0,l.useRef)(null),U=(0,l.useRef)(null),W=(0,l.useRef)(!1),G=(0,l.useRef)(a);G.current=a;let ee=(0,l.useCallback)(()=>U.current?.(),[]),[K,q]=O(ee,i),[te,J]=O(ee,a??0),Y=(0,l.useCallback)(()=>{try{j()}catch(e){z.current.onError?.(e),H.current?.()}},[j]),ne=(0,l.useCallback)(e=>{K(),z.current.onStart?.(e)},[K]),re=(0,l.useCallback)(e=>{q(),z.current.onSpeechStart?.(e)},[q]),ie=(0,l.useCallback)(e=>{K(),B.current&&G.current>0&&te(),z.current.onSpeechEnd?.(e)},[K,te]),ae=(0,l.useCallback)((e,t)=>{q(),B.current||(P(e?.results??[],V.current),Y()),z.current.onResult?.(t,e)},[q,Y]),X=(0,l.useCallback)(e=>{Y(),z.current.onError?.(e)},[Y]),oe=(0,l.useCallback)(e=>{q(),Y(),z.current.onNoMatch?.(e)},[q,Y]),Z=(0,l.useCallback)(e=>{if(!W.current){W.current=!0,q(),J();try{Y(),H.current?.()}finally{W.current=!1,z.current.onEnd?.(e)}}},[q,J,Y]);U.current=Z;let Q=(0,l.useMemo)(()=>({start:ne,end:Z,speechstart:re,speechend:ie,result:ae,error:X,nomatch:oe}),[ne,Z,re,ie,ae,X,oe]);H.current=()=>Object.entries(Q).forEach(([e,t])=>I?.(e,t));let $=(0,l.useCallback)(()=>{try{J(),Object.entries(Q).forEach(([e,t])=>F(e,t)),A({signal:x})?.catch?.(X)}catch(e){X(e)}},[Q,F,A,J,X,x]),se=(0,l.useCallback)(()=>{!f&&p&&(C.current.style.outline=p)},[f,p]),ce=(0,l.useCallback)(()=>{!f&&p&&(C.current.style.outline=`none`)},[f,p]);return T?E(e)?e($,Y,L):(0,l.isValidElement)(e)?(0,l.cloneElement)(e,{...!L&&{onClick:$}}):(0,M.jsx)(`button`,{"data-testid":`__vocal-root__`,ref:C,"aria-label":u,"aria-pressed":L,style:f?null:{width:24,height:24,backgroundColor:`transparent`,border:`none`,padding:0,cursor:!c&&L?`default`:`pointer`,...d},className:f,onFocus:se,onBlur:ce,onClick:L?Y:$,children:(0,M.jsx)(N,{isActive:L,color:`#aaa`})}):null};exports.default=F,exports.isSupported=w,exports.useVocal=D;
6
+ <%s key={someKey} {...props} />`,o,p,m,p),L[p+o]=!0)}if(p=null,i!==void 0&&(r(i),p=``+i),s(n)&&(r(n.key),p=``+n.key),`key`in n)for(var h in i={},n)h!==`key`&&(i[h]=n[h]);else i=n;return p&&c(i,typeof e==`function`?e.displayName||e.name||`Unknown`:e),u(e,p,i,a(),l,d)}function f(e){p(e)?e._store&&(e._store.validated=1):typeof e==`object`&&e&&e.$$typeof===E&&(e._payload.status===`fulfilled`?p(e._payload.value)&&e._payload.value._store&&(e._payload.value._store.validated=1):e._store&&(e._store.validated=1))}function p(e){return typeof e==`object`&&!!e&&e.$$typeof===h}var m=require(`react`),h=Symbol.for(`react.transitional.element`),g=Symbol.for(`react.portal`),_=Symbol.for(`react.fragment`),v=Symbol.for(`react.strict_mode`),y=Symbol.for(`react.profiler`),b=Symbol.for(`react.consumer`),x=Symbol.for(`react.context`),S=Symbol.for(`react.forward_ref`),C=Symbol.for(`react.suspense`),w=Symbol.for(`react.suspense_list`),T=Symbol.for(`react.memo`),E=Symbol.for(`react.lazy`),D=Symbol.for(`react.activity`),O=Symbol.for(`react.client.reference`),k=m.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,A=Object.prototype.hasOwnProperty,j=Array.isArray,M=console.createTask?console.createTask:function(){return null};m={react_stack_bottom_frame:function(e){return e()}};var N,P={},F=m.react_stack_bottom_frame.bind(m,o)(),I=M(i(o)),L={};e.Fragment=_,e.jsx=function(e,t,n){var r=1e4>k.recentlyCreatedOwnerStacks++;return d(e,t,n,!1,r?Error(`react-stack-top-frame`):F,r?M(i(e)):I)},e.jsxs=function(e,t,n){var r=1e4>k.recentlyCreatedOwnerStacks++;return d(e,t,n,!0,r?Error(`react-stack-top-frame`):F,r?M(i(e)):I)}})()})),w=e(((e,t)=>{process.env.NODE_ENV===`production`?t.exports=S():t.exports=C()}))(),T=({color:e=`black`,activeColor:t=`red`,isActive:n=!1})=>(0,w.jsx)(`svg`,{"data-testid":`__icon-root__`,xmlns:`http://www.w3.org/2000/svg`,width:`100%`,height:`100%`,viewBox:`0 0 24 24`,"aria-hidden":`true`,children:(0,w.jsxs)(`g`,{children:[(0,w.jsx)(`path`,{"data-testid":`__icon-path__`,fill:e,d:`M12 14c1.66 0 2.99-1.34 2.99-3L15 5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3zm5.3-3c0 3-2.54 5.1-5.3 5.1S6.7 14 6.7 11H5c0 3.41 2.72 6.23 6 6.72V21h2v-3.28c3.28-.48 6-3.3 6-6.72h-1.7z`}),n&&(0,w.jsx)(`circle`,{"data-testid":`__icon-active__`,fill:t,cx:`16`,cy:`4`,r:`4`})]})}),E=(e,t)=>{for(let n of e)for(let e of n)if(t(e.transcript??``)!==null)return},D=({children:e,commands:n=null,lang:r=`en-US`,grammars:i=null,timeout:a=3e3,silenceTimeout:o=null,precision:s=.4,maxAlternatives:c=1,continuous:l=!1,ariaLabel:u=`start recognition`,style:d=null,className:f=null,outlineStyle:p=`2px solid`,onStart:m=null,onEnd:h=null,onSpeechStart:_=null,onSpeechEnd:S=null,onResult:C=null,onError:D=null,onNoMatch:O=null,signal:k=null,__rsInstance:A=null})=>{let j=(0,t.useRef)(null),M=(0,t.useMemo)(()=>g(),[]),[,{start:N,stop:P,subscribe:F,unsubscribe:I,isRecording:L}]=y(r,i,c,l,A),ee=x(n,s),R=(0,t.useRef)({onStart:null,onEnd:null,onSpeechStart:null,onSpeechEnd:null,onResult:null,onError:null,onNoMatch:null});R.current={onStart:m,onEnd:h,onSpeechStart:_,onSpeechEnd:S,onResult:C,onError:D,onNoMatch:O};let z=(0,t.useRef)(l);z.current=l;let B=(0,t.useRef)(ee);B.current=ee;let V=(0,t.useRef)(null),H=(0,t.useRef)(null),U=(0,t.useRef)(!1),W=(0,t.useRef)(o);W.current=o;let G=(0,t.useCallback)(()=>H.current?.(),[]),[K,q]=b(G,a),[te,J]=b(G,o??0),Y=(0,t.useCallback)(()=>{try{P()}catch(e){R.current.onError?.(e),V.current?.()}},[P]),ne=(0,t.useCallback)(e=>{K(),R.current.onStart?.(e)},[K]),re=(0,t.useCallback)(e=>{q(),R.current.onSpeechStart?.(e)},[q]),ie=(0,t.useCallback)(e=>{K(),z.current&&(W.current??0)>0&&te(),R.current.onSpeechEnd?.(e)},[K,te]),ae=(0,t.useCallback)((e,t)=>{q(),z.current||(E(e.results===void 0?[]:Array.from(e.results,e=>Array.from(e)),B.current),Y()),R.current.onResult?.(t,e)},[q,Y]),X=(0,t.useCallback)(e=>{Y(),R.current.onError?.(e)},[Y]),oe=(0,t.useCallback)(e=>{q(),Y(),R.current.onNoMatch?.(e)},[q,Y]),Z=(0,t.useCallback)(e=>{if(!U.current){U.current=!0,q(),J();try{Y(),V.current?.()}finally{U.current=!1,R.current.onEnd?.(e)}}},[q,J,Y]);H.current=Z;let Q=(0,t.useMemo)(()=>({start:ne,end:Z,speechstart:re,speechend:ie,result:ae,error:X,nomatch:oe}),[ne,Z,re,ie,ae,X,oe]);V.current=()=>Object.entries(Q).forEach(([e,t])=>I?.(e,t));let $=(0,t.useCallback)(()=>{try{J(),Object.entries(Q).forEach(([e,t])=>F(e,t)),N({signal:k??void 0})?.catch?.(X)}catch(e){X(e)}},[Q,F,N,J,X,k]),se=(0,t.useCallback)(()=>{!f&&p&&j.current&&(j.current.style.outline=p)},[f,p]),ce=(0,t.useCallback)(()=>{!f&&p&&j.current&&(j.current.style.outline=`none`)},[f,p]);return M?v(e)?e($,Y,L):(0,t.isValidElement)(e)?(0,t.cloneElement)(e,{...!L&&{onClick:$}}):(0,w.jsx)(`button`,{"data-testid":`__vocal-root__`,ref:j,"aria-label":u,"aria-pressed":L,style:f?void 0:{width:24,height:24,backgroundColor:`transparent`,border:`none`,padding:0,cursor:!l&&L?`default`:`pointer`,...d??{}},className:f??void 0,onFocus:se,onBlur:ce,onClick:L?Y:$,children:(0,w.jsx)(T,{isActive:L,color:`#aaa`})}):null};exports.default=D,exports.isSupported=g,exports.useCommands=x,exports.useVocal=y;
7
7
  //# sourceMappingURL=index.cjs.map
@@ -0,0 +1,78 @@
1
+ import { CSSProperties } from 'react';
2
+ import { EventHandlerFor } from '@untemps/vocal';
3
+ import { EventType } from '@untemps/vocal';
4
+ import { GenericEventHandler } from '@untemps/vocal';
5
+ import { isSupported } from '@untemps/vocal';
6
+ import { JSX } from 'react/jsx-runtime';
7
+ import { ReactElement } from 'react';
8
+ import { ReactNode } from 'react';
9
+ import { RefObject } from 'react';
10
+ import { VocalInstance } from '@untemps/vocal';
11
+
12
+ export declare type CommandCallback = (rawInput: string, commandKey: string) => unknown;
13
+
14
+ export declare type CommandsMap = Record<string, CommandCallback>;
15
+
16
+ export { isSupported }
17
+
18
+ export declare type OnResultCallback = (bestAlternative: string, event: SpeechRecognitionEvent | Event) => void;
19
+
20
+ export declare type TriggerCommand = (rawInput: string) => unknown;
21
+
22
+ export declare const useCommands: (commands?: CommandsMap | null, precision?: number) => TriggerCommand;
23
+
24
+ export declare const useVocal: (lang?: string, grammars?: SpeechGrammarList | null, maxAlternatives?: number, continuous?: boolean, __rsInstance?: VocalInstance | null) => UseVocalReturn;
25
+
26
+ export declare interface UseVocalActions {
27
+ start: (options?: {
28
+ signal?: AbortSignal;
29
+ }) => Promise<void> | undefined;
30
+ stop: () => void;
31
+ abort: () => void;
32
+ subscribe: {
33
+ <T extends EventType>(eventType: T, handler: EventHandlerFor<T>): void;
34
+ (eventType: string, handler: GenericEventHandler): void;
35
+ };
36
+ unsubscribe: {
37
+ <T extends EventType>(eventType: T, handler?: EventHandlerFor<T>): void;
38
+ (eventType: string, handler?: GenericEventHandler): void;
39
+ };
40
+ clean: () => void;
41
+ isRecording: boolean;
42
+ }
43
+
44
+ export declare type UseVocalReturn = [RefObject<VocalInstance | null>, UseVocalActions];
45
+
46
+ declare const Vocal: ({ children, commands, lang, grammars, timeout, silenceTimeout, precision, maxAlternatives, continuous, ariaLabel, style, className, outlineStyle, onStart, onEnd, onSpeechStart, onSpeechEnd, onResult, onError, onNoMatch, signal, __rsInstance, }: VocalProps) => JSX.Element | null;
47
+ export default Vocal;
48
+
49
+ export declare interface VocalProps {
50
+ children?: ReactNode | ((start: () => void, stop: () => void, isStarted: boolean) => ReactElement | null);
51
+ commands?: CommandsMap | null;
52
+ lang?: string;
53
+ grammars?: SpeechGrammarList | null;
54
+ timeout?: number;
55
+ silenceTimeout?: number | null;
56
+ precision?: number;
57
+ maxAlternatives?: number;
58
+ continuous?: boolean;
59
+ ariaLabel?: string;
60
+ style?: CSSProperties | null;
61
+ className?: string | null;
62
+ outlineStyle?: string | null;
63
+ onStart?: ((event: Event) => void) | null;
64
+ onEnd?: ((event?: Event) => void) | null;
65
+ onSpeechStart?: ((event: Event) => void) | null;
66
+ onSpeechEnd?: ((event: Event) => void) | null;
67
+ onResult?: OnResultCallback | null;
68
+ onError?: ((error: unknown) => void) | null;
69
+ onNoMatch?: ((event: Event) => void) | null;
70
+ signal?: AbortSignal | null;
71
+ /**
72
+ * Internal/testing escape hatch. Injects a custom vocal instance. Not part of the
73
+ * stable public API — see issue #136 for the redesign of this surface.
74
+ */
75
+ __rsInstance?: VocalInstance | null;
76
+ }
77
+
78
+ export { }
package/dist/index.es.js CHANGED
@@ -245,7 +245,7 @@ var s = (e, t) => () => (t || (e((t = { exports: {} }).exports, t), e = null), t
245
245
  }
246
246
  let r = c.current;
247
247
  if (r) {
248
- let i = r.search(e).filter((e) => e.score < t);
248
+ let i = r.search(e).filter((e) => (e.score ?? 1) < t);
249
249
  if (i?.length) {
250
250
  let t = i[0].item.toLowerCase();
251
251
  return n[t]?.(e, t);
@@ -451,9 +451,17 @@ var s = (e, t) => () => (t || (e((t = { exports: {} }).exports, t), e = null), t
451
451
  })] })
452
452
  }), N = (e, t) => {
453
453
  for (let n of e) for (let e of n) if (t(e.transcript ?? "") !== null) return;
454
- }, P = ({ children: r, commands: o = null, lang: s = "en-US", grammars: c = null, timeout: l = 3e3, silenceTimeout: u = null, precision: d = .4, maxAlternatives: f = 1, continuous: p = !1, ariaLabel: m = "start recognition", style: h = null, className: g = null, outlineStyle: _ = "2px solid", onStart: v = null, onEnd: y = null, onSpeechStart: b = null, onSpeechEnd: x = null, onResult: S = null, onError: w = null, onNoMatch: k = null, signal: A = null, __rsInstance: P }) => {
455
- let F = a(null), I = i(() => C(), []), [, { start: L, stop: R, subscribe: ee, unsubscribe: te, isRecording: z }] = E(s, c, f, p, P), B = O(o, d), V = a({});
456
- V.current = {
454
+ }, P = ({ children: r, commands: o = null, lang: s = "en-US", grammars: c = null, timeout: l = 3e3, silenceTimeout: u = null, precision: d = .4, maxAlternatives: f = 1, continuous: p = !1, ariaLabel: m = "start recognition", style: h = null, className: g = null, outlineStyle: _ = "2px solid", onStart: v = null, onEnd: y = null, onSpeechStart: b = null, onSpeechEnd: x = null, onResult: S = null, onError: w = null, onNoMatch: k = null, signal: A = null, __rsInstance: P = null }) => {
455
+ let F = a(null), I = i(() => C(), []), [, { start: L, stop: R, subscribe: ee, unsubscribe: te, isRecording: z }] = E(s, c, f, p, P), ne = O(o, d), B = a({
456
+ onStart: null,
457
+ onEnd: null,
458
+ onSpeechStart: null,
459
+ onSpeechEnd: null,
460
+ onResult: null,
461
+ onError: null,
462
+ onNoMatch: null
463
+ });
464
+ B.current = {
457
465
  onStart: v,
458
466
  onEnd: y,
459
467
  onSpeechStart: b,
@@ -462,37 +470,37 @@ var s = (e, t) => () => (t || (e((t = { exports: {} }).exports, t), e = null), t
462
470
  onError: w,
463
471
  onNoMatch: k
464
472
  };
465
- let H = a(p);
466
- H.current = p;
467
- let U = a(B);
468
- U.current = B;
469
- let W = a(null), ne = a(null), G = a(!1), re = a(u);
473
+ let V = a(p);
474
+ V.current = p;
475
+ let H = a(ne);
476
+ H.current = ne;
477
+ let U = a(null), W = a(null), G = a(!1), re = a(u);
470
478
  re.current = u;
471
- let ie = n(() => ne.current?.(), []), [K, q] = D(ie, l), [ae, J] = D(ie, u ?? 0), Y = n(() => {
479
+ let ie = n(() => W.current?.(), []), [K, q] = D(ie, l), [ae, J] = D(ie, u ?? 0), Y = n(() => {
472
480
  try {
473
481
  R();
474
482
  } catch (e) {
475
- V.current.onError?.(e), W.current?.();
483
+ B.current.onError?.(e), U.current?.();
476
484
  }
477
485
  }, [R]), oe = n((e) => {
478
- K(), V.current.onStart?.(e);
486
+ K(), B.current.onStart?.(e);
479
487
  }, [K]), se = n((e) => {
480
- q(), V.current.onSpeechStart?.(e);
488
+ q(), B.current.onSpeechStart?.(e);
481
489
  }, [q]), ce = n((e) => {
482
- K(), H.current && re.current > 0 && ae(), V.current.onSpeechEnd?.(e);
490
+ K(), V.current && (re.current ?? 0) > 0 && ae(), B.current.onSpeechEnd?.(e);
483
491
  }, [K, ae]), le = n((e, t) => {
484
- q(), H.current || (N(e?.results ?? [], U.current), Y()), V.current.onResult?.(t, e);
492
+ q(), V.current || (N(e.results === void 0 ? [] : Array.from(e.results, (e) => Array.from(e)), H.current), Y()), B.current.onResult?.(t, e);
485
493
  }, [q, Y]), X = n((e) => {
486
- Y(), V.current.onError?.(e);
494
+ Y(), B.current.onError?.(e);
487
495
  }, [Y]), ue = n((e) => {
488
- q(), Y(), V.current.onNoMatch?.(e);
496
+ q(), Y(), B.current.onNoMatch?.(e);
489
497
  }, [q, Y]), Z = n((e) => {
490
498
  if (!G.current) {
491
499
  G.current = !0, q(), J();
492
500
  try {
493
- Y(), W.current?.();
501
+ Y(), U.current?.();
494
502
  } finally {
495
- G.current = !1, V.current.onEnd?.(e);
503
+ G.current = !1, B.current.onEnd?.(e);
496
504
  }
497
505
  }
498
506
  }, [
@@ -500,7 +508,7 @@ var s = (e, t) => () => (t || (e((t = { exports: {} }).exports, t), e = null), t
500
508
  J,
501
509
  Y
502
510
  ]);
503
- ne.current = Z;
511
+ W.current = Z;
504
512
  let Q = i(() => ({
505
513
  start: oe,
506
514
  end: Z,
@@ -518,10 +526,10 @@ var s = (e, t) => () => (t || (e((t = { exports: {} }).exports, t), e = null), t
518
526
  X,
519
527
  ue
520
528
  ]);
521
- W.current = () => Object.entries(Q).forEach(([e, t]) => te?.(e, t));
529
+ U.current = () => Object.entries(Q).forEach(([e, t]) => te?.(e, t));
522
530
  let $ = n(() => {
523
531
  try {
524
- J(), Object.entries(Q).forEach(([e, t]) => ee(e, t)), L({ signal: A })?.catch?.(X);
532
+ J(), Object.entries(Q).forEach(([e, t]) => ee(e, t)), L({ signal: A ?? void 0 })?.catch?.(X);
525
533
  } catch (e) {
526
534
  X(e);
527
535
  }
@@ -533,25 +541,25 @@ var s = (e, t) => () => (t || (e((t = { exports: {} }).exports, t), e = null), t
533
541
  X,
534
542
  A
535
543
  ]), de = n(() => {
536
- !g && _ && (F.current.style.outline = _);
544
+ !g && _ && F.current && (F.current.style.outline = _);
537
545
  }, [g, _]), fe = n(() => {
538
- !g && _ && (F.current.style.outline = "none");
546
+ !g && _ && F.current && (F.current.style.outline = "none");
539
547
  }, [g, _]);
540
548
  return I ? T(r) ? r($, Y, z) : t(r) ? e(r, { ...!z && { onClick: $ } }) : /* @__PURE__ */ (0, j.jsx)("button", {
541
549
  "data-testid": "__vocal-root__",
542
550
  ref: F,
543
551
  "aria-label": m,
544
552
  "aria-pressed": z,
545
- style: g ? null : {
553
+ style: g ? void 0 : {
546
554
  width: 24,
547
555
  height: 24,
548
556
  backgroundColor: "transparent",
549
557
  border: "none",
550
558
  padding: 0,
551
559
  cursor: !p && z ? "default" : "pointer",
552
- ...h
560
+ ...h ?? {}
553
561
  },
554
- className: g,
562
+ className: g ?? void 0,
555
563
  onFocus: de,
556
564
  onBlur: fe,
557
565
  onClick: z ? Y : $,
@@ -562,6 +570,6 @@ var s = (e, t) => () => (t || (e((t = { exports: {} }).exports, t), e = null), t
562
570
  }) : null;
563
571
  };
564
572
  //#endregion
565
- export { P as default, C as isSupported, E as useVocal };
573
+ export { P as default, C as isSupported, O as useCommands, E as useVocal };
566
574
 
567
575
  //# sourceMappingURL=index.es.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@untemps/react-vocal",
3
- "version": "2.0.0-beta.14",
3
+ "version": "2.0.0-beta.16",
4
4
  "author": "Vincent Le Badezet <v.lebadezet@untemps.net>",
5
5
  "repository": "git@github.com:untemps/react-vocal.git",
6
6
  "license": "MIT",
@@ -21,54 +21,71 @@
21
21
  "access": "public"
22
22
  },
23
23
  "files": [
24
- "dist/index.cjs",
25
- "dist/index.es.js",
24
+ "dist/*.cjs",
25
+ "dist/*.es.js",
26
+ "dist/*.d.ts",
26
27
  "CHANGELOG.md"
27
28
  ],
28
29
  "type": "module",
29
30
  "main": "dist/index.cjs",
30
31
  "module": "dist/index.es.js",
32
+ "types": "dist/index.d.ts",
31
33
  "sideEffects": false,
32
34
  "exports": {
33
35
  ".": {
36
+ "types": "./dist/index.d.ts",
34
37
  "import": "./dist/index.es.js",
35
38
  "require": "./dist/index.cjs",
36
39
  "default": "./dist/index.es.js"
37
40
  }
38
41
  },
39
42
  "engines": {
40
- "node": "^20.19.0 || >=22.12.0"
43
+ "node": ">=22"
41
44
  },
42
45
  "devDependencies": {
43
46
  "@commitlint/cli": "^20.5.3",
44
- "fuse.js": "^7.3.0",
45
47
  "@commitlint/config-conventional": "^20.5.3",
48
+ "@eslint/js": "^10.0.1",
49
+ "@microsoft/api-extractor": "^7.58.7",
46
50
  "@semantic-release/changelog": "^6.0.3",
47
51
  "@semantic-release/git": "^10.0.1",
48
52
  "@semantic-release/github": "^12.0.6",
49
53
  "@testing-library/dom": "^10.4.1",
50
54
  "@testing-library/jest-dom": "^6.9.1",
51
55
  "@testing-library/react": "^16.3.2",
56
+ "@types/react": "^19.2.15",
57
+ "@types/react-dom": "^19.2.3",
52
58
  "@untemps/utils": "^3.2.0",
53
59
  "@vitejs/plugin-react": "^6.0.1",
54
60
  "@vitest/coverage-v8": "^4.1.5",
61
+ "eslint": "^10.4.0",
62
+ "eslint-config-prettier": "^10.1.8",
63
+ "fuse.js": "^7.3.0",
64
+ "globals": "^17.6.0",
55
65
  "husky": "^9.1.7",
56
66
  "jsdom": "^29.1.1",
57
67
  "prettier": "^3.8.3",
58
68
  "react": "^19.2.5",
59
69
  "react-dom": "^19.2.5",
60
70
  "semantic-release": "^25.0.3",
71
+ "typescript": "^6.0.3",
72
+ "typescript-eslint": "^8.59.4",
61
73
  "vite": "^8.0.10",
74
+ "vite-plugin-dts": "^5.0.1",
62
75
  "vitest": "^4.1.5"
63
76
  },
64
77
  "peerDependencies": {
65
78
  "fuse.js": ">=6.0.0",
66
79
  "react": ">=16.13.1",
67
- "react-dom": ">=16.13.1"
80
+ "react-dom": ">=16.13.1",
81
+ "typescript": ">=6.0.0"
68
82
  },
69
83
  "peerDependenciesMeta": {
70
84
  "fuse.js": {
71
85
  "optional": true
86
+ },
87
+ "typescript": {
88
+ "optional": true
72
89
  }
73
90
  },
74
91
  "dependencies": {
@@ -109,6 +126,10 @@
109
126
  {
110
127
  "path": "dist/index.es.js",
111
128
  "label": "ES distribution"
129
+ },
130
+ {
131
+ "path": "dist/index.d.ts",
132
+ "label": "TypeScript declarations"
112
133
  }
113
134
  ]
114
135
  }
@@ -119,8 +140,10 @@
119
140
  "dev": "cd dev && yarn && yarn dev",
120
141
  "test": "vitest",
121
142
  "test:ci": "vitest run --coverage",
143
+ "typecheck": "tsc --noEmit",
144
+ "lint": "eslint src vitest.setup.ts",
122
145
  "build": "vite build",
123
146
  "prepare": "husky",
124
- "prettier": "prettier \"*/**/*.js\" --ignore-path ./.prettierignore --write && git add -u && git status"
147
+ "prettier": "prettier \"*/**/*.{js,ts,tsx}\" --ignore-path ./.prettierignore --write && git add -u && git status"
125
148
  }
126
149
  }