@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 +19 -0
- package/README.md +74 -7
- package/dist/index.cjs +2 -2
- package/dist/index.d.ts +78 -0
- package/dist/index.es.js +36 -28
- package/package.json +30 -7
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.
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
timeout
|
|
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 = (
|
|
287
|
+
const _onVocalResult = (_event, bestAlternative) => {
|
|
271
288
|
setIsListening(false)
|
|
272
289
|
|
|
273
|
-
setResult(
|
|
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)}})()})),
|
|
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
|
package/dist/index.d.ts
ADDED
|
@@ -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),
|
|
456
|
-
|
|
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
|
|
466
|
-
|
|
467
|
-
let
|
|
468
|
-
|
|
469
|
-
let
|
|
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(() =>
|
|
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
|
-
|
|
483
|
+
B.current.onError?.(e), U.current?.();
|
|
476
484
|
}
|
|
477
485
|
}, [R]), oe = n((e) => {
|
|
478
|
-
K(),
|
|
486
|
+
K(), B.current.onStart?.(e);
|
|
479
487
|
}, [K]), se = n((e) => {
|
|
480
|
-
q(),
|
|
488
|
+
q(), B.current.onSpeechStart?.(e);
|
|
481
489
|
}, [q]), ce = n((e) => {
|
|
482
|
-
K(),
|
|
490
|
+
K(), V.current && (re.current ?? 0) > 0 && ae(), B.current.onSpeechEnd?.(e);
|
|
483
491
|
}, [K, ae]), le = n((e, t) => {
|
|
484
|
-
q(),
|
|
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(),
|
|
494
|
+
Y(), B.current.onError?.(e);
|
|
487
495
|
}, [Y]), ue = n((e) => {
|
|
488
|
-
q(), Y(),
|
|
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(),
|
|
501
|
+
Y(), U.current?.();
|
|
494
502
|
} finally {
|
|
495
|
-
G.current = !1,
|
|
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
|
-
|
|
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
|
-
|
|
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 ?
|
|
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.
|
|
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
|
|
25
|
-
"dist
|
|
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": "
|
|
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
|
}
|