@voiceinput/react 0.1.0-beta.1
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/LICENSE +21 -0
- package/README.md +267 -0
- package/dist/index.cjs +701 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +110 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.ts +110 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +690 -0
- package/dist/index.js.map +1 -0
- package/package.json +77 -0
- package/styles.css +157 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Hirad Arshadiyarahmadi
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
# `@voiceinput/react`
|
|
2
|
+
|
|
3
|
+
Headless React voice input plus optional accessible controls. The package wraps
|
|
4
|
+
`@voiceinput/core`; it does not implement a separate React transcription path.
|
|
5
|
+
|
|
6
|
+
React 18.2+ and React 19 are supported.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
Install this package with one adapter:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install @voiceinput/react@next @voiceinput/openai@next
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Shared provider configuration
|
|
17
|
+
|
|
18
|
+
```tsx
|
|
19
|
+
"use client";
|
|
20
|
+
|
|
21
|
+
import { openai } from "@voiceinput/openai";
|
|
22
|
+
import { VoiceInputProvider } from "@voiceinput/react";
|
|
23
|
+
|
|
24
|
+
const provider = openai({ tokenEndpoint: "/api/voice-token" });
|
|
25
|
+
|
|
26
|
+
export function Providers({ children }: { children: React.ReactNode }) {
|
|
27
|
+
return (
|
|
28
|
+
<VoiceInputProvider provider={provider}>{children}</VoiceInputProvider>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Create provider objects once at module scope, as above, or memoize them.
|
|
34
|
+
Provider and recording options are sampled at recording start. Changing provider
|
|
35
|
+
identity during a render does not interrupt a running session; the next start
|
|
36
|
+
uses the new configuration.
|
|
37
|
+
|
|
38
|
+
`VoiceInputProvider` accepts `provider`, an optional custom `audioSource`, and
|
|
39
|
+
`children`. It coordinates descendants so only one microphone session is active
|
|
40
|
+
in that context. Context is optional: pass `provider` and optionally
|
|
41
|
+
`audioSource` directly to `useVoiceInput` or a component's `voice` prop.
|
|
42
|
+
|
|
43
|
+
## Headless hook
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
import { useVoiceInput } from "@voiceinput/react";
|
|
47
|
+
import { useState } from "react";
|
|
48
|
+
|
|
49
|
+
export function Composer() {
|
|
50
|
+
const [value, setValue] = useState("");
|
|
51
|
+
const voice = useVoiceInput({
|
|
52
|
+
value,
|
|
53
|
+
onValueChange: setValue,
|
|
54
|
+
language: "en-CA",
|
|
55
|
+
vocabulary: ["VoiceInput"],
|
|
56
|
+
activationMode: "toggle",
|
|
57
|
+
interimBehavior: "inline",
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<>
|
|
62
|
+
<textarea
|
|
63
|
+
ref={voice.targetRef}
|
|
64
|
+
value={value}
|
|
65
|
+
onChange={(event) => setValue(event.currentTarget.value)}
|
|
66
|
+
/>
|
|
67
|
+
<button {...voice.getTriggerProps()}>Speak</button>
|
|
68
|
+
</>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
`getTriggerProps()` supplies click, pointer, keyboard, blur, disabled, type, and
|
|
74
|
+
`aria-pressed` behavior while capturing selection before focus changes. Pass
|
|
75
|
+
application button props to it instead of relying on object-spread order:
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
<button
|
|
79
|
+
{...voice.getTriggerProps({
|
|
80
|
+
onClick(event) {
|
|
81
|
+
if (!formIsReady) event.preventDefault();
|
|
82
|
+
},
|
|
83
|
+
})}
|
|
84
|
+
>
|
|
85
|
+
Speak
|
|
86
|
+
</button>
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Application handlers run first. Calling `preventDefault()` skips VoiceInput's
|
|
90
|
+
handler. The lower-level `triggerProps` object remains available when no event
|
|
91
|
+
handlers need to be composed.
|
|
92
|
+
|
|
93
|
+
For an uncontrolled target, omit both `value` and `onValueChange`. Supplying
|
|
94
|
+
only one is an invalid configuration. VoiceInput updates the DOM value and
|
|
95
|
+
dispatches a bubbling native `input` event.
|
|
96
|
+
|
|
97
|
+
### `UseVoiceInputOptions`
|
|
98
|
+
|
|
99
|
+
| Option | Purpose |
|
|
100
|
+
| ------------------------- | ----------------------------------------------------------------- |
|
|
101
|
+
| `provider`, `audioSource` | Override context configuration |
|
|
102
|
+
| `value`, `onValueChange` | Controlled text binding; supply both or neither |
|
|
103
|
+
| `language` | BCP 47 language hint |
|
|
104
|
+
| `vocabulary` | Domain terms mapped by the selected adapter |
|
|
105
|
+
| `endpointing` | Provider default, `false`, or `{ silenceMs }` |
|
|
106
|
+
| `connectionTimeoutMs` | Provider connection deadline after audio acquisition; default 15s |
|
|
107
|
+
| `maxDurationMs` | Positive finite duration; default five minutes |
|
|
108
|
+
| `interimBehavior` | `"inline"` (default) or `"expose"` |
|
|
109
|
+
| `transformTranscript` | Sync or async post-stop transform for unedited voice-owned spans |
|
|
110
|
+
| `transformTimeoutMs` | Transform deadline; default 10 seconds |
|
|
111
|
+
| `activationMode` | `"toggle"` (default) or `"hold"` |
|
|
112
|
+
| `disabled` | Prevent activation and stop active recording |
|
|
113
|
+
| `onEvent` | Receive every normalized session event |
|
|
114
|
+
| `onStatusChange` | Receive current and previous status |
|
|
115
|
+
| `onInterimTranscript` | Current raw interim provider part |
|
|
116
|
+
| `onFinalTranscriptPart` | Each raw provider-final part |
|
|
117
|
+
| `onFinalTranscript` | Cumulative normalized provider-final transcript |
|
|
118
|
+
| `onTranscriptChange` | Cumulative normalized transcript, including interim text |
|
|
119
|
+
| `onDurationWarning` | Called before maximum-duration cutoff |
|
|
120
|
+
| `onStop`, `onError` | Terminal callbacks |
|
|
121
|
+
|
|
122
|
+
### `UseVoiceInputResult`
|
|
123
|
+
|
|
124
|
+
The hook returns:
|
|
125
|
+
|
|
126
|
+
- `targetRef`, `triggerProps`, and `isSupported`
|
|
127
|
+
- `getTriggerProps(buttonProps?)` for safe application-handler composition
|
|
128
|
+
- `undo()` and `redo()` restore field-local editing transactions
|
|
129
|
+
- `status`, `transcript`, `interimTranscript`, `finalTranscript`, and `error`
|
|
130
|
+
- `start()`, `stop(reason?)`, `cancel()`, and `toggle()`
|
|
131
|
+
- `getTextSnapshot()` for the current selection and voice-owned spans
|
|
132
|
+
|
|
133
|
+
Status values are `idle`, `requesting-permission`, `connecting`, `listening`,
|
|
134
|
+
`stopping`, `processing`, and `error`.
|
|
135
|
+
|
|
136
|
+
Transcript names are intentionally distinct: `onInterimTranscript` and
|
|
137
|
+
`onFinalTranscriptPart` receive raw provider parts, while `transcript`,
|
|
138
|
+
`finalTranscript`, `onFinalTranscript`, and `onTranscriptChange` expose
|
|
139
|
+
cumulative normalized state.
|
|
140
|
+
|
|
141
|
+
## Optional controls
|
|
142
|
+
|
|
143
|
+
### `VoiceButton`
|
|
144
|
+
|
|
145
|
+
```tsx
|
|
146
|
+
<VoiceButton voice={{ activationMode: "toggle" }} className="my-button">
|
|
147
|
+
{(voice) => (voice.status === "listening" ? "Stop" : "Speak")}
|
|
148
|
+
</VoiceButton>
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
`VoiceButton` forwards native button props and its ref. Hook options live under
|
|
152
|
+
`voice` to avoid collisions with native props. `children` can be a React node or
|
|
153
|
+
a render function receiving the full hook result. `announce={false}` disables
|
|
154
|
+
the built-in live region; `getAnnouncement` customizes its text.
|
|
155
|
+
|
|
156
|
+
### `VoiceInput` and `VoiceTextarea`
|
|
157
|
+
|
|
158
|
+
```tsx
|
|
159
|
+
<VoiceInput
|
|
160
|
+
type="search"
|
|
161
|
+
defaultValue="Search notes"
|
|
162
|
+
voiceButtonProps={{ "aria-label": "Dictate search" }}
|
|
163
|
+
/>
|
|
164
|
+
|
|
165
|
+
<VoiceTextarea
|
|
166
|
+
value={message}
|
|
167
|
+
onValueChange={setMessage}
|
|
168
|
+
voice={{ vocabulary: ["VoiceInput"] }}
|
|
169
|
+
/>
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Both controls forward native field props and refs. Their VoiceInput additions
|
|
173
|
+
are:
|
|
174
|
+
|
|
175
|
+
- `voice`: hook options except `value` and `onValueChange`
|
|
176
|
+
- `value` and `onValueChange`: controlled voice binding
|
|
177
|
+
- `containerClassName`: class on the field/button wrapper
|
|
178
|
+
- `voiceButtonProps`: native button props plus render children and announcement
|
|
179
|
+
options
|
|
180
|
+
|
|
181
|
+
`VoiceInput` intentionally accepts only selection-capable types: `text`,
|
|
182
|
+
`search`, `tel`, and `url`.
|
|
183
|
+
|
|
184
|
+
Controls expose these stable attributes on stateful roots and triggers:
|
|
185
|
+
|
|
186
|
+
- `data-voiceinput-active="true|false"`
|
|
187
|
+
- `data-voiceinput-error="<code>"`
|
|
188
|
+
- `data-voiceinput-status="<status>"`
|
|
189
|
+
- `data-voiceinput-supported="true|false"`
|
|
190
|
+
|
|
191
|
+
The controls remain fully functional without a stylesheet.
|
|
192
|
+
|
|
193
|
+
## Optional CSS
|
|
194
|
+
|
|
195
|
+
Import styles explicitly:
|
|
196
|
+
|
|
197
|
+
```ts
|
|
198
|
+
import "@voiceinput/react/styles.css";
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
No code path imports CSS automatically. The theme uses these custom properties:
|
|
202
|
+
|
|
203
|
+
- `--voiceinput-accent`, `--voiceinput-accent-strong`
|
|
204
|
+
- `--voiceinput-surface`, `--voiceinput-surface-active`
|
|
205
|
+
- `--voiceinput-text`, `--voiceinput-muted`, `--voiceinput-danger`
|
|
206
|
+
- `--voiceinput-radius`, `--voiceinput-focus`
|
|
207
|
+
- `--voiceinput-shadow`, `--voiceinput-shadow-hover`
|
|
208
|
+
|
|
209
|
+
Override them at `:root` or a containing element. There is no Tailwind runtime
|
|
210
|
+
dependency.
|
|
211
|
+
|
|
212
|
+
## Accessibility and interaction
|
|
213
|
+
|
|
214
|
+
- Toggle mode works with native button click, Enter, and Space.
|
|
215
|
+
- Hold mode starts on primary-pointer/key press and stops on release,
|
|
216
|
+
cancellation, lost capture, blur, disable, or window blur.
|
|
217
|
+
- Pointer activation preserves the target selection instead of moving focus.
|
|
218
|
+
- Triggers expose `aria-pressed`; controls announce status and errors.
|
|
219
|
+
- The optional CSS provides visible focus and reduced-motion handling.
|
|
220
|
+
|
|
221
|
+
If your application already owns a live region, pass `announce={false}` and
|
|
222
|
+
render `status`/`error` in your existing accessibility system.
|
|
223
|
+
|
|
224
|
+
## Public API
|
|
225
|
+
|
|
226
|
+
Runtime exports:
|
|
227
|
+
|
|
228
|
+
- `VoiceInputProvider`
|
|
229
|
+
- `useVoiceInput`
|
|
230
|
+
- `VoiceButton`
|
|
231
|
+
- `VoiceInput`
|
|
232
|
+
- `VoiceTextarea`
|
|
233
|
+
|
|
234
|
+
Type exports:
|
|
235
|
+
|
|
236
|
+
- `VoiceInputProviderProps`
|
|
237
|
+
- `UseVoiceInputOptions`, `UseVoiceInputResult`
|
|
238
|
+
- `VoiceInputActivationMode`, `VoiceInputTriggerProps`
|
|
239
|
+
- `VoiceButtonChildren`, `VoiceButtonProps`, `VoiceFieldButtonProps`
|
|
240
|
+
- `VoiceInputProps`, `VoiceTextareaProps`
|
|
241
|
+
- `VoiceInputError`, `VoiceInputStatus`, `VoiceInputStopReason`
|
|
242
|
+
- `VoiceInputSessionEvent`, `VoiceInputSnapshot`
|
|
243
|
+
- `VoiceInputProviderV1`, `VoiceEndpointingOptions`
|
|
244
|
+
|
|
245
|
+
## Security
|
|
246
|
+
|
|
247
|
+
This package runs in the browser. Give adapters a same-origin token endpoint;
|
|
248
|
+
never pass long-lived provider credentials to React props, client environment
|
|
249
|
+
variables, or browser bundles. Official server handlers live under each provider
|
|
250
|
+
package's `/server` export.
|
|
251
|
+
|
|
252
|
+
## Editing guarantees and limit notifications
|
|
253
|
+
|
|
254
|
+
Controlled wrappers require only `value` and `onValueChange`; the callback
|
|
255
|
+
covers typing, dictation, undo and redo once per edit. Uncontrolled wrappers
|
|
256
|
+
dispatch native input events that React `onChange` and form registration can
|
|
257
|
+
observe. `disabled` and `readOnly` are safe mounting states.
|
|
258
|
+
|
|
259
|
+
`onTextLimit` receives the `text-limit` event: `maxLength`, attempted `text`,
|
|
260
|
+
`insertedText`, and `source` (`interim`, `final`, `transform`). Stop reasons
|
|
261
|
+
include `max-length`, `target-unavailable`, and `backgrounded` in addition to
|
|
262
|
+
the original reasons. Full recognized text remains in transcript callbacks even
|
|
263
|
+
when it cannot be inserted.
|
|
264
|
+
|
|
265
|
+
See [editing behavior and history limits](../../docs/editing-contract.md) and
|
|
266
|
+
[form integration](../../docs/form-integration.md). Mobile microphones and
|
|
267
|
+
manual screen-reader compatibility remain unverified for this desktop beta.
|