@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 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.