@untemps/react-vocal 2.0.0-beta.5 → 2.0.0-beta.6
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 +7 -0
- package/README.md +4 -1
- package/dev/src/index.jsx +11 -3
- package/dist/index.es.js +99 -78
- package/dist/index.es.js.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/index.umd.js +2 -2
- package/dist/index.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/components/Vocal.jsx +38 -10
- package/src/components/__tests__/Vocal.test.jsx +152 -15
- package/src/components/__tests__/__snapshots__/Vocal.test.jsx.snap +1 -1
- package/src/hooks/__tests__/useVocal.test.js +7 -2
- package/src/hooks/useCommands.js +11 -11
- package/src/hooks/useVocal.js +3 -3
- package/vitest.setup.js +8 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
# [2.0.0-beta.6](https://github.com/untemps/react-vocal/compare/v2.0.0-beta.5...v2.0.0-beta.6) (2026-05-12)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* Add continuous session support ([#118](https://github.com/untemps/react-vocal/issues/118)) ([690ba61](https://github.com/untemps/react-vocal/commit/690ba617746aa28499d7ea2d48751453a652ff5e))
|
|
7
|
+
|
|
1
8
|
# [2.0.0-beta.5](https://github.com/untemps/react-vocal/compare/v2.0.0-beta.4...v2.0.0-beta.5) (2026-05-11)
|
|
2
9
|
|
|
3
10
|
|
package/README.md
CHANGED
|
@@ -224,6 +224,8 @@ fuse.js is an optional peer dependency — install it separately to enable fuzzy
|
|
|
224
224
|
| timeout | number | 3000 | Time in ms to wait before discarding the recognition |
|
|
225
225
|
| precision | number | 0.4 | Fuse.js score threshold for **phrase** command keys only (lower = stricter). Single-word commands always use exact lookup. |
|
|
226
226
|
| maxAlternatives | number | 1 | Maximum number of recognition alternatives per segment. Setting this to 3–5 lets the engine surface the correct word as a secondary transcript, which is useful for handling homophones (e.g. _vert_ / _verre_ in French). |
|
|
227
|
+
| continuous | boolean | false | Keep the recognition session open after each result. The session accumulates transcript across segments and stops when the button is clicked again or `silenceTimeout` expires. Commands are not evaluated in continuous mode. |
|
|
228
|
+
| silenceTimeout | number | null | When `continuous` is true, automatically stop the session after this many ms of inactivity following the last recognized result. `null` or `0` disables auto-stop (button click required). |
|
|
227
229
|
| style | object | null | Styles of the root element if className is not specified |
|
|
228
230
|
| className | string | null | Class of the root element |
|
|
229
231
|
| ariaLabel | string | 'start recognition' | Accessible label for the default button |
|
|
@@ -298,7 +300,7 @@ const App = () => {
|
|
|
298
300
|
#### Signature
|
|
299
301
|
|
|
300
302
|
```
|
|
301
|
-
useVocal(lang, grammars, maxAlternatives)
|
|
303
|
+
useVocal(lang, grammars, maxAlternatives, continuous)
|
|
302
304
|
```
|
|
303
305
|
|
|
304
306
|
| Args | Type | Default | Description |
|
|
@@ -306,6 +308,7 @@ useVocal(lang, grammars, maxAlternatives)
|
|
|
306
308
|
| lang | string | 'en-US' | Language understood by the recognition [BCP 47 language tag](https://tools.ietf.org/html/bcp47) |
|
|
307
309
|
| grammars | SpeechGrammarList | null | Grammars understood by the recognition [JSpeech Grammar Format](https://www.w3.org/TR/jsgf/) |
|
|
308
310
|
| maxAlternatives | number | 1 | Maximum number of recognition alternatives per segment |
|
|
311
|
+
| continuous | boolean | false | Keep the recognition session open after each result |
|
|
309
312
|
|
|
310
313
|
---
|
|
311
314
|
|
package/dev/src/index.jsx
CHANGED
|
@@ -13,6 +13,7 @@ const COMMANDS = {
|
|
|
13
13
|
const App = () => {
|
|
14
14
|
const [logs, setLogs] = useState('')
|
|
15
15
|
const [borderColor, setBorderColor] = useState()
|
|
16
|
+
const [continuous, setContinuous] = useState(false)
|
|
16
17
|
|
|
17
18
|
const _log = (value) => setLogs((prev) => `${prev}${prev.length > 0 ? '\n' : ''} ----- ${value}`)
|
|
18
19
|
|
|
@@ -22,8 +23,8 @@ const App = () => {
|
|
|
22
23
|
Object.fromEntries(
|
|
23
24
|
Object.entries(COMMANDS).map(([key, color]) => [
|
|
24
25
|
key,
|
|
25
|
-
(
|
|
26
|
-
_log(`command matched: "${
|
|
26
|
+
(rawInput, commandKey) => {
|
|
27
|
+
_log(`command matched: "${commandKey}" → ${color}`)
|
|
27
28
|
setBorderColor(color)
|
|
28
29
|
},
|
|
29
30
|
])
|
|
@@ -37,12 +38,19 @@ const App = () => {
|
|
|
37
38
|
<Vocal
|
|
38
39
|
lang="fr"
|
|
39
40
|
commands={commands}
|
|
41
|
+
continuous={continuous}
|
|
40
42
|
onStart={() => _log('start')}
|
|
41
43
|
onEnd={() => _log('end')}
|
|
42
|
-
onResult={(result) => _log(`
|
|
44
|
+
onResult={(result) => _log(`transcript: "${result}"`)}
|
|
43
45
|
onError={(e) => _log(`error: ${e.message}`)}
|
|
44
46
|
maxAlternatives={3}
|
|
45
47
|
/>
|
|
48
|
+
<p style={{ fontSize: 12, color: '#666', margin: '8px 0' }}>
|
|
49
|
+
<label style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
|
50
|
+
<input type="checkbox" checked={continuous} onChange={(e) => setContinuous(e.target.checked)} />
|
|
51
|
+
Mode continu
|
|
52
|
+
</label>
|
|
53
|
+
</p>
|
|
46
54
|
<p style={{ fontSize: 12, color: '#666', margin: '8px 0' }}>
|
|
47
55
|
Commandes :{' '}
|
|
48
56
|
{Object.keys(COMMANDS).map((k, i) => (
|
package/dist/index.es.js
CHANGED
|
@@ -672,39 +672,41 @@ x(N, "defaultOptions", {
|
|
|
672
672
|
});
|
|
673
673
|
//#endregion
|
|
674
674
|
//#region node_modules/@untemps/utils/dist/function/isFunction.js
|
|
675
|
-
var P = (e) => typeof e == "function", F = (e = "en-US", t = null, i = 1, o = null) => {
|
|
676
|
-
let
|
|
675
|
+
var P = (e) => typeof e == "function", F = (e = "en-US", t = null, i = 1, o = !1, s = null) => {
|
|
676
|
+
let c = a(null);
|
|
677
677
|
return r(() => {
|
|
678
|
-
if (N.isSupported) return
|
|
678
|
+
if (N.isSupported) return c.current = s || new N({
|
|
679
679
|
lang: e,
|
|
680
680
|
grammars: t,
|
|
681
|
-
maxAlternatives: i
|
|
681
|
+
maxAlternatives: i,
|
|
682
|
+
continuous: o
|
|
682
683
|
}), () => {
|
|
683
|
-
|
|
684
|
+
c.current.abort(), c.current.cleanup();
|
|
684
685
|
};
|
|
685
686
|
}, [
|
|
686
687
|
e,
|
|
687
688
|
t,
|
|
688
689
|
i,
|
|
689
|
-
o
|
|
690
|
-
|
|
690
|
+
o,
|
|
691
|
+
s
|
|
692
|
+
]), [c, {
|
|
691
693
|
start: n(() => {
|
|
692
|
-
|
|
694
|
+
c.current && c.current.start();
|
|
693
695
|
}, []),
|
|
694
696
|
stop: n(() => {
|
|
695
|
-
|
|
697
|
+
c.current && c.current.stop();
|
|
696
698
|
}, []),
|
|
697
699
|
abort: n(() => {
|
|
698
|
-
|
|
700
|
+
c.current && c.current.abort();
|
|
699
701
|
}, []),
|
|
700
702
|
subscribe: n((e, t) => {
|
|
701
|
-
|
|
703
|
+
c.current && c.current.addEventListener(e, t);
|
|
702
704
|
}, []),
|
|
703
705
|
unsubscribe: n((e, t) => {
|
|
704
|
-
|
|
706
|
+
c.current && c.current.removeEventListener(e, t);
|
|
705
707
|
}, []),
|
|
706
708
|
clean: n(() => {
|
|
707
|
-
|
|
709
|
+
c.current && c.current.cleanup();
|
|
708
710
|
}, [])
|
|
709
711
|
}];
|
|
710
712
|
}, I = (e, t = 0) => {
|
|
@@ -742,17 +744,20 @@ var P = (e) => typeof e == "function", F = (e = "en-US", t = null, i = 1, o = nu
|
|
|
742
744
|
let t = e.trim().split(/\s+/), r = t.length > 1 ? t : [e.trim()];
|
|
743
745
|
for (let e of r) {
|
|
744
746
|
let t = e.toLowerCase();
|
|
745
|
-
if (t in n) return n[t]?.(e);
|
|
747
|
+
if (t in n) return n[t]?.(e, t);
|
|
746
748
|
}
|
|
747
749
|
return null;
|
|
748
750
|
}
|
|
749
751
|
let r = c.current;
|
|
750
752
|
if (r) {
|
|
751
753
|
let i = r.search(e).filter((e) => e.score < t);
|
|
752
|
-
if (i?.length)
|
|
754
|
+
if (i?.length) {
|
|
755
|
+
let t = i[0].item.toLowerCase();
|
|
756
|
+
return n[t]?.(e, t);
|
|
757
|
+
}
|
|
753
758
|
} else {
|
|
754
759
|
let t = e.toLowerCase(), r = o.find((e) => t.includes(e) || e.includes(t));
|
|
755
|
-
if (r) return n[r]?.(e);
|
|
760
|
+
if (r) return n[r]?.(e, r);
|
|
756
761
|
}
|
|
757
762
|
return null;
|
|
758
763
|
};
|
|
@@ -950,32 +955,39 @@ var P = (e) => typeof e == "function", F = (e = "en-US", t = null, i = 1, o = nu
|
|
|
950
955
|
})] })
|
|
951
956
|
}), te = (e, t) => {
|
|
952
957
|
for (let { alternatives: n } of e) for (let e of n) if (t(e) !== null) return;
|
|
953
|
-
}, V = ({ children: r, commands: s = null, lang: c = "en-US", grammars: l = null, timeout: u = 3e3,
|
|
954
|
-
let
|
|
955
|
-
|
|
956
|
-
onStart:
|
|
957
|
-
onEnd:
|
|
958
|
-
onSpeechStart:
|
|
959
|
-
onSpeechEnd:
|
|
960
|
-
onResult:
|
|
961
|
-
onError:
|
|
962
|
-
onNoMatch:
|
|
958
|
+
}, V = ({ children: r, commands: s = null, lang: c = "en-US", grammars: l = null, timeout: u = 3e3, silenceTimeout: d = null, precision: f = .4, maxAlternatives: p = 1, continuous: m = !1, ariaLabel: h = "start recognition", style: g = null, className: _ = null, outlineStyle: v = "2px solid", onStart: y = null, onEnd: b = null, onSpeechStart: x = null, onSpeechEnd: S = null, onResult: C = null, onError: w = null, onNoMatch: T = null, __rsInstance: E }) => {
|
|
959
|
+
let D = a(null), [O, k] = o(!1), [, { start: A, stop: j, subscribe: M, unsubscribe: R }] = F(c, l, p, m, E), z = L(s, f), V = a({});
|
|
960
|
+
V.current = {
|
|
961
|
+
onStart: y,
|
|
962
|
+
onEnd: b,
|
|
963
|
+
onSpeechStart: x,
|
|
964
|
+
onSpeechEnd: S,
|
|
965
|
+
onResult: C,
|
|
966
|
+
onError: w,
|
|
967
|
+
onNoMatch: T
|
|
963
968
|
};
|
|
964
|
-
let
|
|
965
|
-
|
|
966
|
-
let
|
|
969
|
+
let H = a(m);
|
|
970
|
+
H.current = m;
|
|
971
|
+
let U = a({
|
|
972
|
+
transcript: "",
|
|
973
|
+
event: null
|
|
974
|
+
}), W = a(z);
|
|
975
|
+
W.current = z;
|
|
976
|
+
let G = a(null), ne = a(null), re = a(d);
|
|
977
|
+
re.current = d;
|
|
978
|
+
let ie = n(() => ne.current?.(), []), [K, q] = I(ie, u), [ae, J] = I(ie, d ?? 0), Y = n(() => {
|
|
967
979
|
try {
|
|
968
|
-
|
|
980
|
+
k(!1), j();
|
|
969
981
|
} catch (e) {
|
|
970
|
-
|
|
982
|
+
V.current.onError?.(e), G.current?.();
|
|
971
983
|
}
|
|
972
|
-
}, [
|
|
973
|
-
|
|
974
|
-
}, [
|
|
975
|
-
|
|
976
|
-
}, [
|
|
977
|
-
|
|
978
|
-
}, [
|
|
984
|
+
}, [j]), oe = n((e) => {
|
|
985
|
+
K(), V.current.onStart?.(e);
|
|
986
|
+
}, [K]), se = n((e) => {
|
|
987
|
+
q(), V.current.onSpeechStart?.(e);
|
|
988
|
+
}, [q]), ce = n((e) => {
|
|
989
|
+
K(), V.current.onSpeechEnd?.(e);
|
|
990
|
+
}, [K]), le = n((e) => {
|
|
979
991
|
let t = Array.from(e?.results ?? [], (e) => {
|
|
980
992
|
let t = {
|
|
981
993
|
confidence: -Infinity,
|
|
@@ -990,74 +1002,83 @@ var P = (e) => typeof e == "function", F = (e = "en-US", t = null, i = 1, o = nu
|
|
|
990
1002
|
alternatives: n
|
|
991
1003
|
};
|
|
992
1004
|
}), n = t.map((e) => e.best).join("");
|
|
993
|
-
|
|
994
|
-
}, [
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1005
|
+
q(), H.current ? (U.current.transcript = n, U.current.event = e, re.current > 0 && ae()) : (te(t, W.current), Y(), V.current.onResult?.(n, e));
|
|
1006
|
+
}, [
|
|
1007
|
+
q,
|
|
1008
|
+
ae,
|
|
1009
|
+
Y
|
|
1010
|
+
]), X = n((e) => {
|
|
1011
|
+
Y(), V.current.onError?.(e);
|
|
1012
|
+
}, [Y]), ue = n((e) => {
|
|
1013
|
+
q(), Y(), V.current.onNoMatch?.(e);
|
|
1014
|
+
}, [q, Y]), Z = n((e) => {
|
|
1015
|
+
q(), J();
|
|
1000
1016
|
try {
|
|
1001
|
-
G(), V.current?.();
|
|
1017
|
+
Y(), G.current?.(), H.current && U.current.transcript && (V.current.onResult?.(U.current.transcript, U.current.event), U.current.transcript = "", U.current.event = null);
|
|
1002
1018
|
} finally {
|
|
1003
|
-
|
|
1019
|
+
V.current.onEnd?.(e);
|
|
1004
1020
|
}
|
|
1005
|
-
}, [
|
|
1006
|
-
|
|
1021
|
+
}, [
|
|
1022
|
+
q,
|
|
1023
|
+
J,
|
|
1024
|
+
Y
|
|
1025
|
+
]);
|
|
1026
|
+
ne.current = Z;
|
|
1007
1027
|
let Q = i(() => ({
|
|
1008
|
-
start:
|
|
1028
|
+
start: oe,
|
|
1009
1029
|
end: Z,
|
|
1010
|
-
speechstart:
|
|
1011
|
-
speechend:
|
|
1012
|
-
result:
|
|
1030
|
+
speechstart: se,
|
|
1031
|
+
speechend: ce,
|
|
1032
|
+
result: le,
|
|
1013
1033
|
error: X,
|
|
1014
|
-
nomatch:
|
|
1034
|
+
nomatch: ue
|
|
1015
1035
|
}), [
|
|
1016
|
-
|
|
1036
|
+
oe,
|
|
1017
1037
|
Z,
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1038
|
+
se,
|
|
1039
|
+
ce,
|
|
1040
|
+
le,
|
|
1021
1041
|
X,
|
|
1022
|
-
|
|
1042
|
+
ue
|
|
1023
1043
|
]);
|
|
1024
|
-
|
|
1044
|
+
G.current = () => Object.entries(Q).forEach(([e, t]) => R?.(e, t));
|
|
1025
1045
|
let $ = n(() => {
|
|
1026
1046
|
try {
|
|
1027
|
-
|
|
1047
|
+
U.current.transcript = "", U.current.event = null, J(), k(!0), Object.entries(Q).forEach(([e, t]) => M(e, t)), A();
|
|
1028
1048
|
} catch (e) {
|
|
1029
1049
|
X(e);
|
|
1030
1050
|
}
|
|
1031
1051
|
}, [
|
|
1032
1052
|
Q,
|
|
1053
|
+
M,
|
|
1033
1054
|
A,
|
|
1034
|
-
|
|
1055
|
+
J,
|
|
1035
1056
|
X
|
|
1036
|
-
]),
|
|
1037
|
-
!
|
|
1038
|
-
},
|
|
1039
|
-
!
|
|
1057
|
+
]), de = () => {
|
|
1058
|
+
!_ && v && (D.current.style.outline = v);
|
|
1059
|
+
}, fe = () => {
|
|
1060
|
+
!_ && v && (D.current.style.outline = "none");
|
|
1040
1061
|
};
|
|
1041
|
-
return N.isSupported ? P(r) ? r($,
|
|
1062
|
+
return N.isSupported ? P(r) ? r($, Y, O) : t(r) ? e(r, { ...!O && { onClick: $ } }) : /* @__PURE__ */ (0, B.jsx)("button", {
|
|
1042
1063
|
"data-testid": "__vocal-root__",
|
|
1043
|
-
ref:
|
|
1044
|
-
|
|
1045
|
-
"aria-
|
|
1046
|
-
style:
|
|
1064
|
+
ref: D,
|
|
1065
|
+
"aria-label": h,
|
|
1066
|
+
"aria-pressed": O,
|
|
1067
|
+
style: _ ? null : {
|
|
1047
1068
|
width: 24,
|
|
1048
1069
|
height: 24,
|
|
1049
1070
|
backgroundColor: "transparent",
|
|
1050
1071
|
border: "none",
|
|
1051
1072
|
padding: 0,
|
|
1052
|
-
cursor:
|
|
1053
|
-
...
|
|
1073
|
+
cursor: !m && O ? "default" : "pointer",
|
|
1074
|
+
...g
|
|
1054
1075
|
},
|
|
1055
|
-
className:
|
|
1056
|
-
onFocus:
|
|
1057
|
-
onBlur:
|
|
1058
|
-
onClick: $,
|
|
1076
|
+
className: _,
|
|
1077
|
+
onFocus: de,
|
|
1078
|
+
onBlur: fe,
|
|
1079
|
+
onClick: O ? Y : $,
|
|
1059
1080
|
children: /* @__PURE__ */ (0, B.jsx)(ee, {
|
|
1060
|
-
isActive:
|
|
1081
|
+
isActive: O,
|
|
1061
1082
|
color: "#aaa"
|
|
1062
1083
|
})
|
|
1063
1084
|
}) : null;
|