@usero/sdk 1.1.5 → 1.1.7
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/plugins/user-test.cjs +75 -11
- package/dist/plugins/user-test.cjs.map +1 -1
- package/dist/plugins/user-test.d.cts +52 -0
- package/dist/plugins/user-test.d.ts +52 -0
- package/dist/plugins/user-test.js +75 -11
- package/dist/plugins/user-test.js.map +1 -1
- package/dist/react.cjs +1 -1
- package/dist/react.cjs.map +1 -1
- package/dist/react.js +1 -1
- package/dist/react.js.map +1 -1
- package/dist/usero.iife.js +7 -7
- package/dist/usero.iife.js.map +1 -1
- package/dist/vanilla.cjs +1 -1
- package/dist/vanilla.cjs.map +1 -1
- package/dist/vanilla.js +1 -1
- package/dist/vanilla.js.map +1 -1
- package/package.json +1 -1
|
@@ -219,12 +219,37 @@ function buildIndicator(host, store, callbacks) {
|
|
|
219
219
|
color: #fcd34d;
|
|
220
220
|
}
|
|
221
221
|
.mic[data-mic-state="muted"]:hover { background: rgba(251, 191, 36, 0.26); }
|
|
222
|
-
.
|
|
223
|
-
|
|
224
|
-
|
|
222
|
+
/* Connecting: getUserMedia pending. Steady amber tint reads as "working",
|
|
223
|
+
distinct from the live red pulse and the failed state below. The icon
|
|
224
|
+
gets a gentle non-pulsing breathe so it feels alive without alarming. */
|
|
225
|
+
.mic[data-mic-state="connecting"] {
|
|
226
|
+
background: rgba(251, 191, 36, 0.14);
|
|
227
|
+
border-color: rgba(251, 191, 36, 0.32);
|
|
228
|
+
color: #fcd34d;
|
|
225
229
|
cursor: default;
|
|
226
230
|
}
|
|
227
|
-
.mic[data-mic-state="
|
|
231
|
+
.mic[data-mic-state="connecting"]:hover { background: rgba(251, 191, 36, 0.14); }
|
|
232
|
+
.mic[data-mic-state="connecting"] .mic-icon {
|
|
233
|
+
color: #fbbf24;
|
|
234
|
+
animation: micBreathe 1.4s ease-in-out infinite;
|
|
235
|
+
}
|
|
236
|
+
/* Failed terminal state, actionable. Tappable affordance: clearer border,
|
|
237
|
+
pointer cursor, brightens on hover/focus to invite the retry tap. */
|
|
238
|
+
.mic[data-mic-state="none"] {
|
|
239
|
+
background: rgba(255,255,255,0.05);
|
|
240
|
+
border-color: rgba(255,255,255,0.14);
|
|
241
|
+
color: rgba(255,255,255,0.72);
|
|
242
|
+
cursor: pointer;
|
|
243
|
+
}
|
|
244
|
+
.mic[data-mic-state="none"]:hover {
|
|
245
|
+
background: rgba(255,255,255,0.12);
|
|
246
|
+
border-color: rgba(255,255,255,0.24);
|
|
247
|
+
color: #fff;
|
|
248
|
+
}
|
|
249
|
+
@keyframes micBreathe {
|
|
250
|
+
0%, 100% { opacity: 0.55; }
|
|
251
|
+
50% { opacity: 1; }
|
|
252
|
+
}
|
|
228
253
|
.mic-icon { width: 13px; height: 13px; display: inline-block; flex-shrink: 0; }
|
|
229
254
|
.mic-label { font-weight: 500; letter-spacing: 0.01em; white-space: nowrap; }
|
|
230
255
|
|
|
@@ -693,7 +718,10 @@ function micChipState(store) {
|
|
|
693
718
|
if (store.indicatorState === "finishing" || store.indicatorState === "done" || store.indicatorState === "error") {
|
|
694
719
|
return "inactive";
|
|
695
720
|
}
|
|
696
|
-
if (!store.hasMicPermission)
|
|
721
|
+
if (!store.hasMicPermission) {
|
|
722
|
+
if (store.micAcquiring) return "connecting";
|
|
723
|
+
return "none";
|
|
724
|
+
}
|
|
697
725
|
return store.muted ? "muted" : "recording";
|
|
698
726
|
}
|
|
699
727
|
function renderIndicatorState(store) {
|
|
@@ -708,6 +736,8 @@ function renderIndicatorState(store) {
|
|
|
708
736
|
dot.setAttribute("data-state", store.indicatorState);
|
|
709
737
|
const chipState = micChipState(store);
|
|
710
738
|
mic.setAttribute("data-mic-state", chipState === "inactive" ? "none" : chipState);
|
|
739
|
+
mic.removeAttribute("data-mic-fail");
|
|
740
|
+
if (chipState === "none") mic.setAttribute("data-mic-fail", store.micFailReason ?? "blocked");
|
|
711
741
|
switch (store.indicatorState) {
|
|
712
742
|
case "recording":
|
|
713
743
|
case "no-audio":
|
|
@@ -742,13 +772,23 @@ function renderIndicatorState(store) {
|
|
|
742
772
|
mic.setAttribute("aria-pressed", "true");
|
|
743
773
|
mic.removeAttribute("tabindex");
|
|
744
774
|
break;
|
|
745
|
-
case "
|
|
746
|
-
micIcon.innerHTML =
|
|
747
|
-
micLabel.textContent = "
|
|
748
|
-
mic.setAttribute("aria-label", "
|
|
775
|
+
case "connecting":
|
|
776
|
+
micIcon.innerHTML = MIC_ICON_SVG;
|
|
777
|
+
micLabel.textContent = "Connecting mic";
|
|
778
|
+
mic.setAttribute("aria-label", "Connecting microphone");
|
|
749
779
|
mic.setAttribute("aria-pressed", "false");
|
|
750
780
|
mic.setAttribute("tabindex", "-1");
|
|
751
781
|
break;
|
|
782
|
+
case "none": {
|
|
783
|
+
micIcon.innerHTML = MIC_MUTED_ICON_SVG;
|
|
784
|
+
const failLabel = store.micFailReason === "not-found" ? "No mic found, tap to retry" : "Mic blocked, tap to retry";
|
|
785
|
+
const failAria = store.micFailReason === "not-found" ? "No microphone found, tap to retry. Replay continues." : "Microphone blocked, tap to retry. Replay continues.";
|
|
786
|
+
micLabel.textContent = failLabel;
|
|
787
|
+
mic.setAttribute("aria-label", failAria);
|
|
788
|
+
mic.setAttribute("aria-pressed", "false");
|
|
789
|
+
mic.removeAttribute("tabindex");
|
|
790
|
+
break;
|
|
791
|
+
}
|
|
752
792
|
case "inactive":
|
|
753
793
|
micIcon.innerHTML = MIC_ICON_SVG;
|
|
754
794
|
micLabel.textContent = store.indicatorState === "finishing" ? "Saving" : store.indicatorState === "done" ? "Saved" : "Save failed";
|
|
@@ -1327,8 +1367,13 @@ function enqueueChunk(store, ctx, blob) {
|
|
|
1327
1367
|
});
|
|
1328
1368
|
}
|
|
1329
1369
|
async function startRecording(store, ctx) {
|
|
1370
|
+
store.micAcquiring = true;
|
|
1371
|
+
store.micFailReason = null;
|
|
1372
|
+
renderIndicatorState(store);
|
|
1330
1373
|
if (!isMediaRecorderSupported()) {
|
|
1331
1374
|
ctx.logger.warn("MediaRecorder not supported, continuing without audio");
|
|
1375
|
+
store.micAcquiring = false;
|
|
1376
|
+
store.micFailReason = "unsupported";
|
|
1332
1377
|
store.indicatorState = "no-audio";
|
|
1333
1378
|
renderIndicatorState(store);
|
|
1334
1379
|
return;
|
|
@@ -1338,12 +1383,17 @@ async function startRecording(store, ctx) {
|
|
|
1338
1383
|
stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
1339
1384
|
} catch (err) {
|
|
1340
1385
|
ctx.logger.warn("mic permission denied or unavailable", err);
|
|
1386
|
+
store.micAcquiring = false;
|
|
1387
|
+
const name = err instanceof Error ? err.name : "";
|
|
1388
|
+
store.micFailReason = name === "NotFoundError" || name === "DevicesNotFoundError" ? "not-found" : "blocked";
|
|
1341
1389
|
store.indicatorState = "no-audio";
|
|
1342
1390
|
renderIndicatorState(store);
|
|
1343
1391
|
return;
|
|
1344
1392
|
}
|
|
1345
1393
|
store.stream = stream;
|
|
1346
1394
|
store.hasMicPermission = true;
|
|
1395
|
+
store.micAcquiring = false;
|
|
1396
|
+
store.micFailReason = null;
|
|
1347
1397
|
const mimeType = pickMimeType();
|
|
1348
1398
|
let recorder;
|
|
1349
1399
|
try {
|
|
@@ -1352,6 +1402,9 @@ async function startRecording(store, ctx) {
|
|
|
1352
1402
|
ctx.logger.error("MediaRecorder construction failed", err);
|
|
1353
1403
|
stream.getTracks().forEach((t) => t.stop());
|
|
1354
1404
|
store.stream = null;
|
|
1405
|
+
store.hasMicPermission = false;
|
|
1406
|
+
store.micAcquiring = false;
|
|
1407
|
+
store.micFailReason = "unsupported";
|
|
1355
1408
|
store.indicatorState = "no-audio";
|
|
1356
1409
|
renderIndicatorState(store);
|
|
1357
1410
|
return;
|
|
@@ -1366,6 +1419,10 @@ async function startRecording(store, ctx) {
|
|
|
1366
1419
|
ctx.logger.error("MediaRecorder error", event);
|
|
1367
1420
|
});
|
|
1368
1421
|
recorder.start(store.options.chunkSeconds * 1e3);
|
|
1422
|
+
if (store.indicatorState === "no-audio") {
|
|
1423
|
+
store.indicatorState = "recording";
|
|
1424
|
+
}
|
|
1425
|
+
renderIndicatorState(store);
|
|
1369
1426
|
}
|
|
1370
1427
|
function toggleMute(store) {
|
|
1371
1428
|
if (!store.stream || !store.hasMicPermission) return false;
|
|
@@ -1521,6 +1578,8 @@ function userTest(options = {}) {
|
|
|
1521
1578
|
outsidePointerHandler: null,
|
|
1522
1579
|
keydownHandler: null,
|
|
1523
1580
|
hasMicPermission: false,
|
|
1581
|
+
micAcquiring: true,
|
|
1582
|
+
micFailReason: null,
|
|
1524
1583
|
muted: false,
|
|
1525
1584
|
mutedSinceMs: null,
|
|
1526
1585
|
mutedSegments: [],
|
|
@@ -1545,7 +1604,12 @@ function userTest(options = {}) {
|
|
|
1545
1604
|
};
|
|
1546
1605
|
const onToggleTasks = () => setPanelOpen(!store.tasksPanelOpen);
|
|
1547
1606
|
const onToggleMute = () => {
|
|
1548
|
-
if (!store.hasMicPermission)
|
|
1607
|
+
if (!store.hasMicPermission) {
|
|
1608
|
+
if (!store.micAcquiring && store.indicatorState !== "finishing" && store.indicatorState !== "done" && store.indicatorState !== "error") {
|
|
1609
|
+
void startRecording(store, ctx);
|
|
1610
|
+
}
|
|
1611
|
+
return;
|
|
1612
|
+
}
|
|
1549
1613
|
const ok = toggleMute(store);
|
|
1550
1614
|
if (!ok) return;
|
|
1551
1615
|
if (store.muted) showMuteToast(store);
|
|
@@ -1674,7 +1738,7 @@ function userTest(options = {}) {
|
|
|
1674
1738
|
}
|
|
1675
1739
|
};
|
|
1676
1740
|
}
|
|
1677
|
-
var __test__ = { getTestSlug, pickMimeType, isMediaRecorderSupported };
|
|
1741
|
+
var __test__ = { getTestSlug, pickMimeType, isMediaRecorderSupported, micChipState };
|
|
1678
1742
|
|
|
1679
1743
|
exports.__test__ = __test__;
|
|
1680
1744
|
exports.userTest = userTest;
|