@burdenoff/website-sdk 2026.828.7 → 2026.829.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/dist/components/explore.js +401 -189
- package/dist/components/explore.js.map +1 -1
- package/dist/components/explore.mjs +403 -191
- package/dist/components/explore.mjs.map +1 -1
- package/dist/hooks/index.js +110 -68
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/index.mjs +110 -68
- package/dist/hooks/index.mjs.map +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +401 -189
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +401 -189
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/hooks/index.js
CHANGED
|
@@ -1278,21 +1278,37 @@ function getRecognitionCtor() {
|
|
|
1278
1278
|
const w = window;
|
|
1279
1279
|
return w.SpeechRecognition ?? w.webkitSpeechRecognition ?? null;
|
|
1280
1280
|
}
|
|
1281
|
+
function isTouchDevice() {
|
|
1282
|
+
if (typeof window === "undefined" || !window.matchMedia) return false;
|
|
1283
|
+
try {
|
|
1284
|
+
return window.matchMedia("(pointer: coarse)").matches;
|
|
1285
|
+
} catch {
|
|
1286
|
+
return false;
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1281
1289
|
var MIC_DENIED_MESSAGE = "Microphone access was blocked. Allow it in your browser's site settings to dictate.";
|
|
1290
|
+
var MIC_UNAVAILABLE_MESSAGE = "Dictation isn't available in this browser. You can type instead.";
|
|
1291
|
+
var RELEASE_SETTLE_MS = 250;
|
|
1292
|
+
var RETRY_DELAY_MS = 350;
|
|
1293
|
+
var sleep = (ms) => new Promise((resolve) => {
|
|
1294
|
+
setTimeout(resolve, ms);
|
|
1295
|
+
});
|
|
1282
1296
|
async function ensureMicrophoneAccess() {
|
|
1283
1297
|
const media = typeof navigator === "undefined" ? void 0 : navigator.mediaDevices;
|
|
1284
|
-
if (!media?.getUserMedia)
|
|
1298
|
+
if (!media?.getUserMedia)
|
|
1299
|
+
return { ok: true, confirmed: false, primed: false };
|
|
1285
1300
|
try {
|
|
1286
1301
|
const status = await navigator.permissions?.query({
|
|
1287
1302
|
name: "microphone"
|
|
1288
1303
|
});
|
|
1289
|
-
if (status?.state === "granted")
|
|
1304
|
+
if (status?.state === "granted")
|
|
1305
|
+
return { ok: true, confirmed: true, primed: false };
|
|
1290
1306
|
} catch {
|
|
1291
1307
|
}
|
|
1292
1308
|
try {
|
|
1293
1309
|
const stream = await media.getUserMedia({ audio: true });
|
|
1294
1310
|
for (const track of stream.getTracks()) track.stop();
|
|
1295
|
-
return { ok: true, confirmed: true };
|
|
1311
|
+
return { ok: true, confirmed: true, primed: true };
|
|
1296
1312
|
} catch (error) {
|
|
1297
1313
|
const name = typeof error === "object" && error !== null && "name" in error ? String(error.name) : "";
|
|
1298
1314
|
if (name === "NotAllowedError" || name === "SecurityError") {
|
|
@@ -1336,19 +1352,17 @@ function useSpeechInput({
|
|
|
1336
1352
|
const recognitionRef = react.useRef(null);
|
|
1337
1353
|
const startTokenRef = react.useRef(0);
|
|
1338
1354
|
const micConfirmedRef = react.useRef(false);
|
|
1339
|
-
const
|
|
1340
|
-
const
|
|
1341
|
-
(Ctor, token) => {
|
|
1342
|
-
beginRecognitionRef.current?.(Ctor, token);
|
|
1343
|
-
},
|
|
1344
|
-
[]
|
|
1345
|
-
);
|
|
1355
|
+
const wantsListeningRef = react.useRef(false);
|
|
1356
|
+
const retriedRef = react.useRef(false);
|
|
1346
1357
|
const finalRef = react.useRef(onFinalTranscript);
|
|
1347
1358
|
const errorRef = react.useRef(onError);
|
|
1348
1359
|
finalRef.current = onFinalTranscript;
|
|
1349
1360
|
errorRef.current = onError;
|
|
1361
|
+
const langRef = react.useRef(lang);
|
|
1362
|
+
langRef.current = lang;
|
|
1350
1363
|
const stop = react.useCallback(() => {
|
|
1351
1364
|
startTokenRef.current += 1;
|
|
1365
|
+
wantsListeningRef.current = false;
|
|
1352
1366
|
setListening(false);
|
|
1353
1367
|
setInterim("");
|
|
1354
1368
|
const recognition = recognitionRef.current;
|
|
@@ -1357,83 +1371,111 @@ function useSpeechInput({
|
|
|
1357
1371
|
recognition.stop();
|
|
1358
1372
|
} catch {
|
|
1359
1373
|
}
|
|
1360
|
-
setListening(false);
|
|
1361
|
-
setInterim("");
|
|
1362
1374
|
}, []);
|
|
1363
|
-
const
|
|
1375
|
+
const openSession = react.useCallback((token) => {
|
|
1364
1376
|
const Ctor = getRecognitionCtor();
|
|
1365
|
-
if (!Ctor) return;
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1377
|
+
if (!Ctor || token !== startTokenRef.current) return;
|
|
1378
|
+
const recognition = new Ctor();
|
|
1379
|
+
recognition.lang = langRef.current ?? (typeof navigator === "undefined" ? "en-US" : navigator.language || "en-US");
|
|
1380
|
+
recognition.continuous = !isTouchDevice();
|
|
1381
|
+
recognition.interimResults = true;
|
|
1382
|
+
recognition.maxAlternatives = 1;
|
|
1383
|
+
recognition.onstart = () => {
|
|
1384
|
+
if (token !== startTokenRef.current) return;
|
|
1385
|
+
setError(null);
|
|
1386
|
+
setListening(true);
|
|
1387
|
+
};
|
|
1388
|
+
recognition.onresult = (event) => {
|
|
1389
|
+
if (token !== startTokenRef.current) return;
|
|
1390
|
+
let settled = "";
|
|
1391
|
+
let pending = "";
|
|
1392
|
+
for (let i = event.resultIndex; i < event.results.length; i += 1) {
|
|
1393
|
+
const result = event.results[i];
|
|
1394
|
+
if (!result) continue;
|
|
1395
|
+
const text = result[0]?.transcript ?? "";
|
|
1396
|
+
if (result.isFinal) settled += text;
|
|
1397
|
+
else pending += text;
|
|
1398
|
+
}
|
|
1399
|
+
setInterim(pending);
|
|
1400
|
+
if (settled.trim() !== "") finalRef.current(settled);
|
|
1401
|
+
};
|
|
1402
|
+
recognition.onerror = (event) => {
|
|
1403
|
+
if (token !== startTokenRef.current) return;
|
|
1404
|
+
if ((event.error === "not-allowed" || event.error === "service-not-allowed") && micConfirmedRef.current && !retriedRef.current) {
|
|
1405
|
+
retriedRef.current = true;
|
|
1406
|
+
void sleep(RETRY_DELAY_MS).then(() => {
|
|
1407
|
+
if (token !== startTokenRef.current || !wantsListeningRef.current)
|
|
1408
|
+
return;
|
|
1409
|
+
openSessionRef.current?.(token);
|
|
1410
|
+
});
|
|
1411
|
+
return;
|
|
1412
|
+
}
|
|
1413
|
+
const message = micConfirmedRef.current && (event.error === "not-allowed" || event.error === "service-not-allowed") ? MIC_UNAVAILABLE_MESSAGE : describeError(event.error);
|
|
1414
|
+
wantsListeningRef.current = false;
|
|
1415
|
+
setListening(false);
|
|
1416
|
+
setInterim("");
|
|
1417
|
+
if (message !== "") {
|
|
1418
|
+
setError(message);
|
|
1419
|
+
errorRef.current?.(message);
|
|
1420
|
+
}
|
|
1421
|
+
};
|
|
1422
|
+
recognition.onend = () => {
|
|
1423
|
+
if (token !== startTokenRef.current) return;
|
|
1424
|
+
setInterim("");
|
|
1425
|
+
if (wantsListeningRef.current && isTouchDevice()) {
|
|
1426
|
+
void sleep(120).then(() => {
|
|
1427
|
+
if (token !== startTokenRef.current || !wantsListeningRef.current)
|
|
1428
|
+
return;
|
|
1429
|
+
openSessionRef.current?.(token);
|
|
1430
|
+
});
|
|
1431
|
+
return;
|
|
1432
|
+
}
|
|
1433
|
+
setListening(false);
|
|
1434
|
+
};
|
|
1435
|
+
recognitionRef.current = recognition;
|
|
1436
|
+
try {
|
|
1437
|
+
recognition.start();
|
|
1438
|
+
} catch {
|
|
1439
|
+
wantsListeningRef.current = false;
|
|
1440
|
+
setListening(false);
|
|
1441
|
+
}
|
|
1442
|
+
}, []);
|
|
1443
|
+
const openSessionRef = react.useRef(null);
|
|
1444
|
+
openSessionRef.current = openSession;
|
|
1445
|
+
const start = react.useCallback(() => {
|
|
1446
|
+
if (!getRecognitionCtor()) return;
|
|
1369
1447
|
startTokenRef.current += 1;
|
|
1370
1448
|
const token = startTokenRef.current;
|
|
1371
|
-
|
|
1449
|
+
wantsListeningRef.current = true;
|
|
1450
|
+
retriedRef.current = false;
|
|
1451
|
+
micConfirmedRef.current = false;
|
|
1452
|
+
setError(null);
|
|
1453
|
+
setListening(true);
|
|
1454
|
+
void ensureMicrophoneAccess().then(async (access) => {
|
|
1372
1455
|
if (token !== startTokenRef.current) return;
|
|
1373
|
-
micConfirmedRef.current = access.ok && access.confirmed;
|
|
1374
1456
|
if (!access.ok) {
|
|
1457
|
+
wantsListeningRef.current = false;
|
|
1375
1458
|
setListening(false);
|
|
1376
1459
|
setError(access.message);
|
|
1377
1460
|
errorRef.current?.(access.message);
|
|
1378
1461
|
return;
|
|
1379
1462
|
}
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
const beginRecognitionImpl = react.useCallback(
|
|
1384
|
-
(Ctor, token) => {
|
|
1385
|
-
const recognition = new Ctor();
|
|
1386
|
-
recognition.lang = lang ?? (typeof navigator === "undefined" ? "en-US" : navigator.language || "en-US");
|
|
1387
|
-
recognition.continuous = true;
|
|
1388
|
-
recognition.interimResults = true;
|
|
1389
|
-
recognition.maxAlternatives = 1;
|
|
1390
|
-
recognition.onstart = () => {
|
|
1463
|
+
micConfirmedRef.current = access.confirmed;
|
|
1464
|
+
if (access.primed) {
|
|
1465
|
+
await sleep(RELEASE_SETTLE_MS);
|
|
1391
1466
|
if (token !== startTokenRef.current) return;
|
|
1392
|
-
setError(null);
|
|
1393
|
-
setListening(true);
|
|
1394
|
-
};
|
|
1395
|
-
recognition.onresult = (event) => {
|
|
1396
|
-
let settled = "";
|
|
1397
|
-
let pending = "";
|
|
1398
|
-
for (let i = event.resultIndex; i < event.results.length; i += 1) {
|
|
1399
|
-
const result = event.results[i];
|
|
1400
|
-
if (!result) continue;
|
|
1401
|
-
const text = result[0]?.transcript ?? "";
|
|
1402
|
-
if (result.isFinal) settled += text;
|
|
1403
|
-
else pending += text;
|
|
1404
|
-
}
|
|
1405
|
-
setInterim(pending);
|
|
1406
|
-
if (settled.trim() !== "") finalRef.current(settled);
|
|
1407
|
-
};
|
|
1408
|
-
recognition.onerror = (event) => {
|
|
1409
|
-
const message = micConfirmedRef.current && (event.error === "not-allowed" || event.error === "service-not-allowed") ? "Dictation isn't available in this browser. You can type instead." : describeError(event.error);
|
|
1410
|
-
setListening(false);
|
|
1411
|
-
setInterim("");
|
|
1412
|
-
if (message !== "") {
|
|
1413
|
-
setError(message);
|
|
1414
|
-
errorRef.current?.(message);
|
|
1415
|
-
}
|
|
1416
|
-
};
|
|
1417
|
-
recognition.onend = () => {
|
|
1418
|
-
setListening(false);
|
|
1419
|
-
setInterim("");
|
|
1420
|
-
};
|
|
1421
|
-
recognitionRef.current = recognition;
|
|
1422
|
-
try {
|
|
1423
|
-
recognition.start();
|
|
1424
|
-
} catch {
|
|
1425
|
-
setListening(false);
|
|
1426
1467
|
}
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
);
|
|
1430
|
-
beginRecognitionRef.current = beginRecognitionImpl;
|
|
1468
|
+
openSessionRef.current?.(token);
|
|
1469
|
+
});
|
|
1470
|
+
}, []);
|
|
1431
1471
|
const toggle = react.useCallback(() => {
|
|
1432
1472
|
if (listening) stop();
|
|
1433
1473
|
else start();
|
|
1434
1474
|
}, [listening, start, stop]);
|
|
1435
1475
|
react.useEffect(
|
|
1436
1476
|
() => () => {
|
|
1477
|
+
startTokenRef.current += 1;
|
|
1478
|
+
wantsListeningRef.current = false;
|
|
1437
1479
|
const recognition = recognitionRef.current;
|
|
1438
1480
|
if (!recognition) return;
|
|
1439
1481
|
recognition.onresult = null;
|