solid-drift 0.25.0 → 0.27.0

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/README.md CHANGED
@@ -1534,6 +1534,72 @@ const chat = createChatModel({
1534
1534
  - Honest limitation: api.anthropic.com does not send CORS headers for browser origins, so from a browser Anthropic must go through your own server route or proxy; point `baseUrl` at it. For production with any provider, prefer a server route that holds the key and set `baseUrl` to it so keys never ship to the browser.
1535
1535
  - `createSSE(url, options?)` is a fetch-based event-stream client (`{ status, events, lastEvent, error, connect, disconnect }`): unlike `EventSource` it supports any method and custom headers, parses the full SSE framing (named events, multi-line data, comments, chunk splits), and leaves reconnection manual via `connect()`. SSR-safe: nothing connects until `connect()` (or `autoConnect`) runs on the client.
1536
1536
 
1537
+ ### Voice
1538
+
1539
+ A full voice loop: mic metering, speech-to-text, a voice state machine, canvas waveforms, text-to-speech (browser or cloud), thinking indicators, and a voice-enabled prompt input.
1540
+
1541
+ ```tsx
1542
+ import { createVoiceState, createSpeech, createTTS, createPrompt, createChatModel } from "solid-drift"
1543
+
1544
+ const voice = createVoiceState()
1545
+ const chat = createChatModel({ provider: "openai", apiKey: getKey, model: "gpt-4o-mini" })
1546
+ const tts = createTTS()
1547
+ const prompt = createPrompt({
1548
+ onSubmit: async (text) => {
1549
+ voice.toThinking()
1550
+ await chat.send(text)
1551
+ voice.toSpeaking()
1552
+ const msgs = chat.messages()
1553
+ tts.speak(msgs[msgs.length - 1]?.content ?? "")
1554
+ voice.toIdle()
1555
+ },
1556
+ })
1557
+
1558
+ <input
1559
+ value={prompt.value()}
1560
+ onInput={(e) => prompt.setValue(e.currentTarget.value)}
1561
+ onKeyDown={(e) => e.key === "Enter" && prompt.submit()}
1562
+ />
1563
+ <button onClick={() => { voice.toListening(); prompt.toggleMic(); }}>Mic</button>
1564
+ ```
1565
+
1566
+ - `createVoiceState()` is the turn state machine: `state()` is `idle`, `listening`, `thinking`, or `speaking`, with `toIdle`/`toListening`/`toThinking`/`toSpeaking` transitions.
1567
+ - `createMicLevel(options?)` returns `{ level, active, supported, analyser, error, start, stop }`: a 0..1 smoothed RMS meter from `getUserMedia` plus an `AnalyserNode` (call `start()` from a user gesture). The exposed `analyser` wires straight into `createWaveform`.
1568
+ - `createSpeech(options?)` wraps the Web Speech API (`SpeechRecognition` with `webkitSpeechRecognition` fallback): `{ supported, listening, transcript, interim, error, start, stop, reset }`. Final results accumulate into `transcript()`; `continuous` sessions auto-restart if the browser ends them mid-turn.
1569
+ - `createWaveform(canvas, options)` draws the analyser's time-domain wave (`mode: "line"`) or spectrum (`mode: "bars"`) on a canvas, DPR-aware, on the shared clock; under reduced motion it redraws at most every 250ms.
1570
+ - `createTTS(options?)` speaks via `speechSynthesis` by default (`{ supported, speaking, voices, speak, cancel }`, async voice loading, `speak()` cancels the current utterance first) and upgrades to any cloud voice through `provider: { speak(text, { signal }) }`.
1571
+ - `createThinking(options?)` cycles `"Thinking"`, `"Thinking."`, ... through `phrases` at `interval` ms: `{ text, running, start, stop }`.
1572
+ - `createPrompt(options?)` is the voice-enabled input: `{ value, setValue, listening, interim, supported, toggleMic, submit, clear }`. Mic finals are appended to the value as they arrive; `submit()` fires `onSubmit` and clears by default. Everything is SSR-safe: unsupported primitives report `supported: false` and their actions no-op on the server.
1573
+
1574
+ ### Mobile hardware
1575
+
1576
+ ```tsx
1577
+ import { createBattery, createShare, createScanline } from "solid-drift";
1578
+
1579
+ const battery = createBattery();
1580
+ const share = createShare();
1581
+ const scanline = createScanline({ duration: 1800 });
1582
+
1583
+ scanline.start();
1584
+ // in your scanner viewfinder:
1585
+ // <div class="line" style={{ top: `${scanline.progress() * 100}%` }} />
1586
+
1587
+ <p>Battery: {Math.round(battery.level() * 100)}% {battery.charging() ? "(charging)" : ""}</p>
1588
+ <button onClick={() => share.share({ title: "solid-drift", url: location.href })}>Share</button>
1589
+ ```
1590
+
1591
+ - `createBattery()` wraps `navigator.getBattery()`: `{ supported, charging, level, chargingTime, dischargingTime, error }`. Level is 0..1; times are seconds (`Infinity` when unknown). Listeners detach on cleanup.
1592
+ - `createNetwork()` tracks `navigator.onLine` plus the Network Information API: `{ online, effectiveType, downlink, rtt, saveData, supported }`. Updates on `online`/`offline` events and the connection `change` event.
1593
+ - `createWakeLock()` keeps the screen awake: `{ supported, active, error, request, release }`. Re-acquires automatically when the tab becomes visible again if the lock was still wanted.
1594
+ - `createContactPick()` wraps the Contact Picker API: `{ supported, contacts, error, pick }`. `pick({ multiple })` resolves with normalized `{ name, tel, email }` arrays, or an empty array when the user cancels.
1595
+ - `createOTP()` wraps the WebOTP API: `{ supported, code, error, wait, abort }`. `wait({ transport })` resolves with the SMS code (or `null` when aborted). Requires a secure origin and an origin-bound SMS format.
1596
+ - `createShare()` wraps the Web Share API: `{ supported, canShare, error, share }`. `share({ title, text, url, files })` opens the native sheet; user dismissal is not an error.
1597
+ - `createNFC()` wraps Web NFC (Chrome on Android, secure context, needs a user gesture): `{ supported, scanning, message, error, scan, write, abort }`. Scanned tags land in `message()` with decoded `text`/`url` records plus `serialNumber`; `write()` takes a string or `{ records }`.
1598
+ - `createTorch()` drives the camera flashlight: `{ supported, on, error, attach, set, toggle }`. `attach(trackOrStream)` checks the `torch` capability, then `set(true/false)` applies it via `applyConstraints`.
1599
+ - `createGyro()` wraps `deviceorientation`: `{ supported, needsPermission, alpha, beta, gamma, absolute, listening, error, requestPermission, start, stop }`. On iOS, call `requestPermission()` from a tap handler before `start()`; `start()` also requests it if needed.
1600
+ - `createShake(options?)` detects shake gestures from `devicemotion`: `{ supported, needsPermission, listening, shakes, error, requestPermission, start, stop }`. A shake counts when the acceleration delta exceeds `threshold` (default 15 m/s^2), rate-limited by `cooldown` (default 800ms); `onShake` fires per shake.
1601
+ - `createScanline(options?)` is the animated line of a QR/barcode viewfinder: `{ progress, running, start, stop }`. `progress()` sweeps 0..1 on the shared clock (`direction: "down" | "up" | "alternate"`); bind it to the line's position. Under reduced motion it freezes mid-frame. Every primitive is SSR-safe: server renders get `supported: false` and safe no-op actions.
1602
+
1537
1603
  ### Easings
1538
1604
 
1539
1605
  Named easings: `linear`, `easeInQuad`, `easeOutQuad`, `easeInOutQuad`, `easeInCubic`, `easeOutCubic`, `easeInOutCubic`, `easeInQuart`, `easeOutQuart`, `easeInOutQuart`, `easeOutExpo`, `easeOutBack`, plus the cartoon set: `easeInBack` (anticipation dip before movement), `easeInOutBack` (wind-up, overshoot, settle), `easeOutElastic` (decaying rubber-band oscillation), `easeOutBounce` (shrinking cartoon bounces). Also `cubicBezier(x1, y1, x2, y2)` for CSS-style curves. Pass a name or a custom `(t) => number` function anywhere an easing is accepted.
@@ -0,0 +1,207 @@
1
+ export interface BatteryState {
2
+ supported: boolean;
3
+ charging: () => boolean;
4
+ level: () => number;
5
+ chargingTime: () => number;
6
+ dischargingTime: () => number;
7
+ error: () => Error | null;
8
+ }
9
+ /**
10
+ * createBattery
11
+ *
12
+ * Reactive wrapper around the Battery Status API (`navigator.getBattery`).
13
+ * Tracks charging state, level (0..1), and charge/discharge time in seconds.
14
+ */
15
+ export declare function createBattery(): BatteryState;
16
+ export interface NetworkState {
17
+ online: () => boolean;
18
+ effectiveType: () => string | undefined;
19
+ downlink: () => number | undefined;
20
+ rtt: () => number | undefined;
21
+ saveData: () => boolean;
22
+ supported: boolean;
23
+ }
24
+ /**
25
+ * createNetwork
26
+ *
27
+ * Reactive network status: `navigator.onLine` plus the Network Information
28
+ * API (`navigator.connection`) for effective type, downlink, RTT, and
29
+ * data-saver preference.
30
+ */
31
+ export declare function createNetwork(): NetworkState;
32
+ export interface WakeLockState {
33
+ supported: boolean;
34
+ active: () => boolean;
35
+ error: () => Error | null;
36
+ request: () => Promise<void>;
37
+ release: () => Promise<void>;
38
+ }
39
+ /**
40
+ * createWakeLock
41
+ *
42
+ * Keeps the screen awake with the Screen Wake Lock API. Re-acquires the lock
43
+ * automatically when the tab becomes visible again after a release.
44
+ */
45
+ export declare function createWakeLock(): WakeLockState;
46
+ export interface PickedContact {
47
+ name: string[];
48
+ tel: string[];
49
+ email: string[];
50
+ }
51
+ export interface ContactPickState {
52
+ supported: boolean;
53
+ contacts: () => PickedContact[];
54
+ error: () => Error | null;
55
+ pick: (options?: {
56
+ multiple?: boolean;
57
+ }) => Promise<PickedContact[]>;
58
+ }
59
+ /**
60
+ * createContactPick
61
+ *
62
+ * Contact Picker API (`navigator.contacts.select`). Resolves with the chosen
63
+ * contacts; empty array when the user cancels.
64
+ */
65
+ export declare function createContactPick(): ContactPickState;
66
+ export interface OTPState {
67
+ supported: boolean;
68
+ code: () => string | null;
69
+ error: () => Error | null;
70
+ wait: (options?: {
71
+ transport?: string[];
72
+ }) => Promise<string | null>;
73
+ abort: () => void;
74
+ }
75
+ /**
76
+ * createOTP
77
+ *
78
+ * WebOTP API: reads a one-time code from an incoming SMS without leaving the
79
+ * page. `wait()` resolves with the code (or null when aborted). Works only on
80
+ * secure origins where the SMS matches the site's origin-bound format.
81
+ */
82
+ export declare function createOTP(): OTPState;
83
+ export interface ShareState {
84
+ supported: boolean;
85
+ canShare: (data: ShareData) => boolean;
86
+ error: () => Error | null;
87
+ share: (data: ShareData) => Promise<void>;
88
+ }
89
+ /**
90
+ * createShare
91
+ *
92
+ * Web Share API: opens the native share sheet. `share()` resolves when the
93
+ * user completes or dismisses the sheet (dismissal is not an error).
94
+ */
95
+ export declare function createShare(): ShareState;
96
+ export interface NFCRecord {
97
+ recordType: string;
98
+ mediaType?: string;
99
+ text?: string;
100
+ url?: string;
101
+ }
102
+ export interface NFCMessage {
103
+ records: NFCRecord[];
104
+ serialNumber: string;
105
+ }
106
+ export interface NFCState {
107
+ supported: boolean;
108
+ scanning: () => boolean;
109
+ message: () => NFCMessage | null;
110
+ error: () => Error | null;
111
+ scan: () => Promise<void>;
112
+ write: (content: string | {
113
+ records: Array<Record<string, unknown>>;
114
+ }) => Promise<void>;
115
+ abort: () => void;
116
+ }
117
+ /**
118
+ * createNFC
119
+ *
120
+ * Web NFC (Chrome on Android, secure context): scan tags with `scan()` and
121
+ * read decoded records from `message`; write text or records with `write()`.
122
+ * Scanning needs a user gesture.
123
+ */
124
+ export declare function createNFC(): NFCState;
125
+ export interface TorchState {
126
+ supported: () => boolean;
127
+ on: () => boolean;
128
+ error: () => Error | null;
129
+ attach: (input: MediaStreamTrack | MediaStream) => void;
130
+ set: (value: boolean) => Promise<void>;
131
+ toggle: () => Promise<void>;
132
+ }
133
+ /**
134
+ * createTorch
135
+ *
136
+ * Camera flashlight for devices that expose the `torch` capability.
137
+ * Attach a video track (or stream) from `getUserMedia`, then `set()` or
138
+ * `toggle()` the flashlight.
139
+ */
140
+ export declare function createTorch(): TorchState;
141
+ export interface GyroState {
142
+ supported: boolean;
143
+ needsPermission: boolean;
144
+ alpha: () => number | null;
145
+ beta: () => number | null;
146
+ gamma: () => number | null;
147
+ absolute: () => boolean;
148
+ listening: () => boolean;
149
+ error: () => Error | null;
150
+ requestPermission: () => Promise<"granted" | "denied">;
151
+ start: () => Promise<void>;
152
+ stop: () => void;
153
+ }
154
+ /**
155
+ * createGyro
156
+ *
157
+ * Device orientation (alpha/beta/gamma in degrees). On iOS the motion
158
+ * permission prompt must come from a user gesture: call `requestPermission()`
159
+ * from a tap handler, then `start()`.
160
+ */
161
+ export declare function createGyro(): GyroState;
162
+ export interface ShakeOptions {
163
+ /** Acceleration delta (m/s^2) that counts as a shake. Default 15. */
164
+ threshold?: number;
165
+ /** Minimum milliseconds between shakes. Default 800. */
166
+ cooldown?: number;
167
+ onShake?: () => void;
168
+ }
169
+ export interface ShakeState {
170
+ supported: boolean;
171
+ needsPermission: boolean;
172
+ listening: () => boolean;
173
+ shakes: () => number;
174
+ error: () => Error | null;
175
+ requestPermission: () => Promise<"granted" | "denied">;
176
+ start: () => Promise<void>;
177
+ stop: () => void;
178
+ }
179
+ /**
180
+ * createShake
181
+ *
182
+ * Shake-to-undo style detection from `devicemotion`. Counts a shake when the
183
+ * acceleration delta exceeds `threshold`, rate-limited by `cooldown`.
184
+ */
185
+ export declare function createShake(options?: ShakeOptions): ShakeState;
186
+ export interface ScanlineOptions {
187
+ /** Milliseconds per sweep. Default 1800. */
188
+ duration?: number;
189
+ /** Sweep pattern. Default "alternate" (ping-pong). */
190
+ direction?: "down" | "up" | "alternate";
191
+ }
192
+ export interface ScanlineState {
193
+ /** Line position, 0 at the top edge and 1 at the bottom edge. */
194
+ progress: () => number;
195
+ running: () => boolean;
196
+ start: () => void;
197
+ stop: () => void;
198
+ }
199
+ /**
200
+ * createScanline
201
+ *
202
+ * The animated line of a camera viewfinder (QR/barcode scanning UI). Bind
203
+ * `progress()` to the line position, for example
204
+ * `style={{ top: `${progress() * 100}%` }}`. Runs on the shared clock and
205
+ * freezes mid-frame under reduced motion.
206
+ */
207
+ export declare function createScanline(options?: ScanlineOptions): ScanlineState;