@untemps/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,23 @@
1
+ # [2.0.0-beta.16](https://github.com/untemps/vocal/compare/v2.0.0-beta.15...v2.0.0-beta.16) (2026-05-16)
2
+
3
+
4
+ ### Features
5
+
6
+ * Add once() method for one-shot event listener registration ([#76](https://github.com/untemps/vocal/issues/76)) ([8179643](https://github.com/untemps/vocal/commit/8179643f159e3ccf690f7ac2d8bd101568c6e5b3))
7
+
8
+ # [2.0.0-beta.15](https://github.com/untemps/vocal/compare/v2.0.0-beta.14...v2.0.0-beta.15) (2026-05-16)
9
+
10
+
11
+ ### Features
12
+
13
+ * Support multiple listeners per event type in addEventListener ([#75](https://github.com/untemps/vocal/issues/75)) ([97a435d](https://github.com/untemps/vocal/commit/97a435dc09105fadb9d8c22052ddaa75cbb6ee26))
14
+
15
+
16
+ ### BREAKING CHANGES
17
+
18
+ * addEventListener now stacks listeners instead of replacing. removeEventListener(eventType) removes all handlers for the type
19
+ removeEventListener(eventType, callback) removes only the specific callback.
20
+
1
21
  # [2.0.0-beta.14](https://github.com/untemps/vocal/compare/v2.0.0-beta.13...v2.0.0-beta.14) (2026-05-16)
2
22
 
3
23
  # [2.0.0-beta.13](https://github.com/untemps/vocal/compare/v2.0.0-beta.12...v2.0.0-beta.13) (2026-05-16)
package/README.md CHANGED
@@ -65,6 +65,7 @@ Please refer to [this section](https://developer.mozilla.org/en-US/docs/Web/API/
65
65
  | continuous | boolean | false | Whether continuous results are returned for each recognition, or only a single result |
66
66
  | interimResults | boolean | false | Whether interim results should be returned or not. Interim results are results that are not yet final |
67
67
  | maxAlternatives | number | 1 | Maximum number of SpeechRecognitionAlternatives provided per result |
68
+
68
69
  ## Events
69
70
 
70
71
  Events described below are those from the `SpeechRecognition` Web API.
@@ -108,3 +109,19 @@ vocal.start({ signal: controller.signal })
108
109
  controller.abort()
109
110
  ```
110
111
 
112
+ ### `once(eventType, callback)`
113
+
114
+ Registers a one-shot listener that automatically unregisters itself after firing once.
115
+
116
+ | Parameter | Type | Description |
117
+ | --------- | ------------------------------------------------- | ------------------------------------------ |
118
+ | eventType | `EventType` | One of the valid event type strings |
119
+ | callback | `ResultEventHandler \| ErrorEventHandler \| GenericEventHandler` | Callback invoked once when the event fires |
120
+
121
+ ```js
122
+ vocal.once('result', (event, bestAlternative, alternatives) => {
123
+ console.log(bestAlternative)
124
+ vocal.stop()
125
+ })
126
+ ```
127
+
package/dist/index.cjs CHANGED
@@ -1,2 +1,2 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require(`@untemps/user-permissions-utils`);var t=class t{static defaultOptions={grammars:null,lang:`en-US`,continuous:!1,interimResults:!1,maxAlternatives:1};static eventTypes={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`};static get isSupported(){return!!t._resolveSpeechRecognition()&&!!(0,e.isNavigatorPermissionsSupported)()&&!!(0,e.isNavigatorMediaDevicesSupported)()}static set isSupported(e){throw Error(`You cannot set isSupported directly.`)}_instance=null;_listeners=null;_isRecording=!1;_onEnd=()=>{this._isRecording=!1};constructor(e){let n=t._resolveSpeechRecognition();if(!n)throw new DOMException(`SpeechRecognition not supported`,`NOT_SUPPORTED_ERR`);this._instance=new n,this._listeners={};let{grammars:r,...i}={...t.defaultOptions,...e??{}},a=this._instance;if(Object.assign(a,i),r)a.grammars=r;else{let e=t._resolveSpeechGrammarList();a.grammars=e?new e:null}this._instance.addEventListener(`end`,this._onEnd)}get instance(){return this._instance}set instance(e){throw Error(`You cannot set instance directly.`)}get isRecording(){return this._isRecording}set isRecording(e){throw Error(`You cannot set isRecording directly.`)}async start({signal:t}={}){if(this._instance)try{if(!await(0,e.getUserMediaStream)(`microphone`,{audio:!0},{signal:t}))throw Error(`Unable to retrieve the stream from media device`);this._instance.start(),this._isRecording=!0}catch(e){if(e instanceof Error&&e.name===`AbortError`)return this;throw e}return this}stop(){return this._instance&&(this._instance.stop(),this._isRecording=!1),this}abort(){return this._instance&&(this._instance.abort(),this._isRecording=!1),this}addEventListener(e,n){if(!this._includesEventType(e))throw Error(this._unknownEventTypeMessage(e));if(this._instance&&this._listeners){this._listeners[e]&&this.removeEventListener(e);let r=r=>{let i=[];if(e===t.eventTypes.RESULT){let e=r;if(e.results?.length>0&&e.resultIndex<e.results.length){let t=Array.from(e.results[e.resultIndex]),n=t.reduce((e,t)=>(t.confidence??0)>(e.confidence??0)?t:e);i.push(n.transcript,t.map(e=>e.transcript))}}n.call(this,r,...i)};this._instance.addEventListener(e,r),this._listeners[e]=r}return this}removeEventListener(e){if(!this._includesEventType(e))throw Error(this._unknownEventTypeMessage(e));if(this._instance&&this._listeners){let t=this._listeners[e];this._instance.removeEventListener(e,t),delete this._listeners[e]}return this}cleanup(){return this.stop(),Object.keys(this._listeners).forEach(e=>this.removeEventListener(e)),this._instance?.removeEventListener(`end`,this._onEnd),this._instance=null,this}_includesEventType(e){return Object.values(t.eventTypes).includes(e)}_unknownEventTypeMessage(e){return`Unknown event type "${e}". Valid types are: ${Object.values(t.eventTypes).join(`, `)}.`}static _resolveSpeechRecognition(){if(!(typeof window>`u`))return window.SpeechRecognition??window.webkitSpeechRecognition??window.mozSpeechRecognition??window.msSpeechRecognition}static _resolveSpeechGrammarList(){return window.SpeechGrammarList??window.webkitSpeechGrammarList??window.mozSpeechGrammarList??window.msSpeechGrammarList}};exports.Vocal=t;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require(`@untemps/user-permissions-utils`);var t=class t{static defaultOptions={grammars:null,lang:`en-US`,continuous:!1,interimResults:!1,maxAlternatives:1};static eventTypes={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`};static get isSupported(){return!!t._resolveSpeechRecognition()&&!!(0,e.isNavigatorPermissionsSupported)()&&!!(0,e.isNavigatorMediaDevicesSupported)()}static set isSupported(e){throw Error(`You cannot set isSupported directly.`)}_instance=null;_listeners=null;_isRecording=!1;_onEnd=()=>{this._isRecording=!1};constructor(e){let n=t._resolveSpeechRecognition();if(!n)throw new DOMException(`SpeechRecognition not supported`,`NOT_SUPPORTED_ERR`);this._instance=new n,this._listeners={};let{grammars:r,...i}={...t.defaultOptions,...e??{}},a=this._instance;if(Object.assign(a,i),r)a.grammars=r;else{let e=t._resolveSpeechGrammarList();a.grammars=e?new e:null}this._instance.addEventListener(`end`,this._onEnd)}get instance(){return this._instance}set instance(e){throw Error(`You cannot set instance directly.`)}get isRecording(){return this._isRecording}set isRecording(e){throw Error(`You cannot set isRecording directly.`)}async start({signal:t}={}){if(this._instance)try{if(!await(0,e.getUserMediaStream)(`microphone`,{audio:!0},{signal:t}))throw Error(`Unable to retrieve the stream from media device`);this._instance.start(),this._isRecording=!0}catch(e){if(e instanceof Error&&e.name===`AbortError`)return this;throw e}return this}stop(){return this._instance&&(this._instance.stop(),this._isRecording=!1),this}abort(){return this._instance&&(this._instance.abort(),this._isRecording=!1),this}addEventListener(e,n){if(!this._includesEventType(e))throw Error(this._unknownEventTypeMessage(e));if(this._instance&&this._listeners){let r=r=>{let i=[];if(e===t.eventTypes.RESULT){let e=r;if(e.results?.length>0&&e.resultIndex<e.results.length){let t=Array.from(e.results[e.resultIndex]),n=t.reduce((e,t)=>(t.confidence??0)>(e.confidence??0)?t:e);i.push(n.transcript,t.map(e=>e.transcript))}}n.call(this,r,...i)};this._instance.addEventListener(e,r),this._listeners[e]||(this._listeners[e]=[]),this._listeners[e].push({callback:n,handler:r})}return this}removeEventListener(e,t){if(!this._includesEventType(e))throw Error(this._unknownEventTypeMessage(e));let n=this._instance;if(n&&this._listeners&&this._listeners[e])if(t!==void 0){let r=this._listeners[e].findIndex(e=>e.callback===t);r!==-1&&(n.removeEventListener(e,this._listeners[e][r].handler),this._listeners[e].splice(r,1),this._listeners[e].length===0&&delete this._listeners[e])}else this._listeners[e].forEach(({handler:t})=>n.removeEventListener(e,t)),delete this._listeners[e];return this}once(e,t){let n=(...r)=>{t.call(this,...r),this.removeEventListener(e,n)};return this.addEventListener(e,n)}cleanup(){return this.stop(),Object.keys(this._listeners).forEach(e=>this.removeEventListener(e)),this._instance?.removeEventListener(`end`,this._onEnd),this._instance=null,this}_includesEventType(e){return Object.values(t.eventTypes).includes(e)}_unknownEventTypeMessage(e){return`Unknown event type "${e}". Valid types are: ${Object.values(t.eventTypes).join(`, `)}.`}static _resolveSpeechRecognition(){if(!(typeof window>`u`))return window.SpeechRecognition??window.webkitSpeechRecognition??window.mozSpeechRecognition??window.msSpeechRecognition}static _resolveSpeechGrammarList(){return window.SpeechGrammarList??window.webkitSpeechGrammarList??window.mozSpeechGrammarList??window.msSpeechGrammarList}};exports.Vocal=t;
2
2
  //# sourceMappingURL=index.cjs.map
package/dist/index.es.js CHANGED
@@ -79,7 +79,6 @@ var r = class r {
79
79
  addEventListener(e, t) {
80
80
  if (!this._includesEventType(e)) throw Error(this._unknownEventTypeMessage(e));
81
81
  if (this._instance && this._listeners) {
82
- this._listeners[e] && this.removeEventListener(e);
83
82
  let n = (n) => {
84
83
  let i = [];
85
84
  if (e === r.eventTypes.RESULT) {
@@ -91,18 +90,28 @@ var r = class r {
91
90
  }
92
91
  t.call(this, n, ...i);
93
92
  };
94
- this._instance.addEventListener(e, n), this._listeners[e] = n;
93
+ this._instance.addEventListener(e, n), this._listeners[e] || (this._listeners[e] = []), this._listeners[e].push({
94
+ callback: t,
95
+ handler: n
96
+ });
95
97
  }
96
98
  return this;
97
99
  }
98
- removeEventListener(e) {
100
+ removeEventListener(e, t) {
99
101
  if (!this._includesEventType(e)) throw Error(this._unknownEventTypeMessage(e));
100
- if (this._instance && this._listeners) {
101
- let t = this._listeners[e];
102
- this._instance.removeEventListener(e, t), delete this._listeners[e];
103
- }
102
+ let n = this._instance;
103
+ if (n && this._listeners && this._listeners[e]) if (t !== void 0) {
104
+ let r = this._listeners[e].findIndex((e) => e.callback === t);
105
+ r !== -1 && (n.removeEventListener(e, this._listeners[e][r].handler), this._listeners[e].splice(r, 1), this._listeners[e].length === 0 && delete this._listeners[e]);
106
+ } else this._listeners[e].forEach(({ handler: t }) => n.removeEventListener(e, t)), delete this._listeners[e];
104
107
  return this;
105
108
  }
109
+ once(e, t) {
110
+ let n = (...r) => {
111
+ t.call(this, ...r), this.removeEventListener(e, n);
112
+ };
113
+ return this.addEventListener(e, n);
114
+ }
106
115
  cleanup() {
107
116
  return this.stop(), Object.keys(this._listeners).forEach((e) => this.removeEventListener(e)), this._instance?.removeEventListener("end", this._onEnd), this._instance = null, this;
108
117
  }
package/dist/index.umd.js CHANGED
@@ -1,2 +1,2 @@
1
- (function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports,require(`@untemps/user-permissions-utils`)):typeof define==`function`&&define.amd?define([`exports`,`@untemps/user-permissions-utils`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.Vocal={},e.UserPermissionsUtils))})(this,function(e,t){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`}),e.Vocal=class e{static defaultOptions={grammars:null,lang:`en-US`,continuous:!1,interimResults:!1,maxAlternatives:1};static eventTypes={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`};static get isSupported(){return!!e._resolveSpeechRecognition()&&!!(0,t.isNavigatorPermissionsSupported)()&&!!(0,t.isNavigatorMediaDevicesSupported)()}static set isSupported(e){throw Error(`You cannot set isSupported directly.`)}_instance=null;_listeners=null;_isRecording=!1;_onEnd=()=>{this._isRecording=!1};constructor(t){let n=e._resolveSpeechRecognition();if(!n)throw new DOMException(`SpeechRecognition not supported`,`NOT_SUPPORTED_ERR`);this._instance=new n,this._listeners={};let{grammars:r,...i}={...e.defaultOptions,...t??{}},a=this._instance;if(Object.assign(a,i),r)a.grammars=r;else{let t=e._resolveSpeechGrammarList();a.grammars=t?new t:null}this._instance.addEventListener(`end`,this._onEnd)}get instance(){return this._instance}set instance(e){throw Error(`You cannot set instance directly.`)}get isRecording(){return this._isRecording}set isRecording(e){throw Error(`You cannot set isRecording directly.`)}async start({signal:e}={}){if(this._instance)try{if(!await(0,t.getUserMediaStream)(`microphone`,{audio:!0},{signal:e}))throw Error(`Unable to retrieve the stream from media device`);this._instance.start(),this._isRecording=!0}catch(e){if(e instanceof Error&&e.name===`AbortError`)return this;throw e}return this}stop(){return this._instance&&(this._instance.stop(),this._isRecording=!1),this}abort(){return this._instance&&(this._instance.abort(),this._isRecording=!1),this}addEventListener(t,n){if(!this._includesEventType(t))throw Error(this._unknownEventTypeMessage(t));if(this._instance&&this._listeners){this._listeners[t]&&this.removeEventListener(t);let r=r=>{let i=[];if(t===e.eventTypes.RESULT){let e=r;if(e.results?.length>0&&e.resultIndex<e.results.length){let t=Array.from(e.results[e.resultIndex]),n=t.reduce((e,t)=>(t.confidence??0)>(e.confidence??0)?t:e);i.push(n.transcript,t.map(e=>e.transcript))}}n.call(this,r,...i)};this._instance.addEventListener(t,r),this._listeners[t]=r}return this}removeEventListener(e){if(!this._includesEventType(e))throw Error(this._unknownEventTypeMessage(e));if(this._instance&&this._listeners){let t=this._listeners[e];this._instance.removeEventListener(e,t),delete this._listeners[e]}return this}cleanup(){return this.stop(),Object.keys(this._listeners).forEach(e=>this.removeEventListener(e)),this._instance?.removeEventListener(`end`,this._onEnd),this._instance=null,this}_includesEventType(t){return Object.values(e.eventTypes).includes(t)}_unknownEventTypeMessage(t){return`Unknown event type "${t}". Valid types are: ${Object.values(e.eventTypes).join(`, `)}.`}static _resolveSpeechRecognition(){if(!(typeof window>`u`))return window.SpeechRecognition??window.webkitSpeechRecognition??window.mozSpeechRecognition??window.msSpeechRecognition}static _resolveSpeechGrammarList(){return window.SpeechGrammarList??window.webkitSpeechGrammarList??window.mozSpeechGrammarList??window.msSpeechGrammarList}}});
1
+ (function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports,require(`@untemps/user-permissions-utils`)):typeof define==`function`&&define.amd?define([`exports`,`@untemps/user-permissions-utils`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.Vocal={},e.UserPermissionsUtils))})(this,function(e,t){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`}),e.Vocal=class e{static defaultOptions={grammars:null,lang:`en-US`,continuous:!1,interimResults:!1,maxAlternatives:1};static eventTypes={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`};static get isSupported(){return!!e._resolveSpeechRecognition()&&!!(0,t.isNavigatorPermissionsSupported)()&&!!(0,t.isNavigatorMediaDevicesSupported)()}static set isSupported(e){throw Error(`You cannot set isSupported directly.`)}_instance=null;_listeners=null;_isRecording=!1;_onEnd=()=>{this._isRecording=!1};constructor(t){let n=e._resolveSpeechRecognition();if(!n)throw new DOMException(`SpeechRecognition not supported`,`NOT_SUPPORTED_ERR`);this._instance=new n,this._listeners={};let{grammars:r,...i}={...e.defaultOptions,...t??{}},a=this._instance;if(Object.assign(a,i),r)a.grammars=r;else{let t=e._resolveSpeechGrammarList();a.grammars=t?new t:null}this._instance.addEventListener(`end`,this._onEnd)}get instance(){return this._instance}set instance(e){throw Error(`You cannot set instance directly.`)}get isRecording(){return this._isRecording}set isRecording(e){throw Error(`You cannot set isRecording directly.`)}async start({signal:e}={}){if(this._instance)try{if(!await(0,t.getUserMediaStream)(`microphone`,{audio:!0},{signal:e}))throw Error(`Unable to retrieve the stream from media device`);this._instance.start(),this._isRecording=!0}catch(e){if(e instanceof Error&&e.name===`AbortError`)return this;throw e}return this}stop(){return this._instance&&(this._instance.stop(),this._isRecording=!1),this}abort(){return this._instance&&(this._instance.abort(),this._isRecording=!1),this}addEventListener(t,n){if(!this._includesEventType(t))throw Error(this._unknownEventTypeMessage(t));if(this._instance&&this._listeners){let r=r=>{let i=[];if(t===e.eventTypes.RESULT){let e=r;if(e.results?.length>0&&e.resultIndex<e.results.length){let t=Array.from(e.results[e.resultIndex]),n=t.reduce((e,t)=>(t.confidence??0)>(e.confidence??0)?t:e);i.push(n.transcript,t.map(e=>e.transcript))}}n.call(this,r,...i)};this._instance.addEventListener(t,r),this._listeners[t]||(this._listeners[t]=[]),this._listeners[t].push({callback:n,handler:r})}return this}removeEventListener(e,t){if(!this._includesEventType(e))throw Error(this._unknownEventTypeMessage(e));let n=this._instance;if(n&&this._listeners&&this._listeners[e])if(t!==void 0){let r=this._listeners[e].findIndex(e=>e.callback===t);r!==-1&&(n.removeEventListener(e,this._listeners[e][r].handler),this._listeners[e].splice(r,1),this._listeners[e].length===0&&delete this._listeners[e])}else this._listeners[e].forEach(({handler:t})=>n.removeEventListener(e,t)),delete this._listeners[e];return this}once(e,t){let n=(...r)=>{t.call(this,...r),this.removeEventListener(e,n)};return this.addEventListener(e,n)}cleanup(){return this.stop(),Object.keys(this._listeners).forEach(e=>this.removeEventListener(e)),this._instance?.removeEventListener(`end`,this._onEnd),this._instance=null,this}_includesEventType(t){return Object.values(e.eventTypes).includes(t)}_unknownEventTypeMessage(t){return`Unknown event type "${t}". Valid types are: ${Object.values(e.eventTypes).join(`, `)}.`}static _resolveSpeechRecognition(){if(!(typeof window>`u`))return window.SpeechRecognition??window.webkitSpeechRecognition??window.mozSpeechRecognition??window.msSpeechRecognition}static _resolveSpeechGrammarList(){return window.SpeechGrammarList??window.webkitSpeechGrammarList??window.mozSpeechGrammarList??window.msSpeechGrammarList}}});
2
2
  //# sourceMappingURL=index.umd.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@untemps/vocal",
3
- "version": "2.0.0-beta.14",
3
+ "version": "2.0.0-beta.16",
4
4
  "description": "Class wrapped around the SpeechRecognition Web API",
5
5
  "repository": "git@github.com:untemps/vocal.git",
6
6
  "keywords": [