@seams/voice-capture 0.68.0 → 0.69.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 +56 -2
- package/dist/browser/card.js +267 -18
- package/dist/esm/card/index.js +1 -1
- package/dist/esm/card/phraseCard.js +2 -2
- package/dist/esm/card/prompt.js +329 -19
- package/dist/esm/card/ribbon.js +106 -5
- package/dist/esm/card/rowMotion.js +3 -3
- package/dist/esm/card/surface.js +121 -6
- package/dist/esm/card/visual.js +2 -2
- package/dist/esm/chunks/{visual-Di3ojTED.js → visual-DU1OVwCA.js} +35 -3
- package/dist/types/card/index.d.ts +2 -2
- package/dist/types/card/prompt.d.ts +13 -1
- package/dist/types/card/ribbon.d.ts +19 -0
- package/dist/types/card/rowMotion.d.ts +3 -3
- package/dist/types/card/surface.d.ts +63 -0
- package/dist/types/card/visual.d.ts +6 -1
- package/package.json +1 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
//#region src/card/rowMotion.ts
|
|
2
2
|
const ROW_OUT_MS = 150;
|
|
3
|
-
const ROW_IN_MS =
|
|
4
|
-
const ROW_IN_DELAY_MS =
|
|
5
|
-
const ROW_STAGGER_MS =
|
|
3
|
+
const ROW_IN_MS = 140;
|
|
4
|
+
const ROW_IN_DELAY_MS = 0;
|
|
5
|
+
const ROW_STAGGER_MS = 20;
|
|
6
6
|
const ROW_EASE = "cubic-bezier(0.2, 0, 0, 1)";
|
|
7
7
|
//#endregion
|
|
8
8
|
export { ROW_EASE, ROW_IN_DELAY_MS, ROW_IN_MS, ROW_OUT_MS, ROW_STAGGER_MS };
|
package/dist/esm/card/surface.js
CHANGED
|
@@ -8,7 +8,29 @@ import { VoicePrompt, leaveSurface } from "./prompt.js";
|
|
|
8
8
|
//#region src/card/surface.ts
|
|
9
9
|
const LABEL = "Say this with your voice";
|
|
10
10
|
const PROMPT_LABEL = "Say this sentence";
|
|
11
|
-
|
|
11
|
+
/**
|
|
12
|
+
* The status line before a recording: empty, so the sentence is the one text to read. It holds
|
|
13
|
+
* "Opening microphone…", a loading line or a problem with the recording.
|
|
14
|
+
*/
|
|
15
|
+
const PROMPT_MESSAGE = "";
|
|
16
|
+
/** The colours that a phrase card reads. */
|
|
17
|
+
const PHRASE_CARD_COLORS = [
|
|
18
|
+
"colorBackground",
|
|
19
|
+
"textPrimary",
|
|
20
|
+
"textSecondary",
|
|
21
|
+
"textMuted",
|
|
22
|
+
"borderPrimary",
|
|
23
|
+
"surface",
|
|
24
|
+
"buttonBackground",
|
|
25
|
+
"textButton",
|
|
26
|
+
"error",
|
|
27
|
+
"info",
|
|
28
|
+
"voiceLine",
|
|
29
|
+
"voiceEcho",
|
|
30
|
+
"voiceEchoDeep",
|
|
31
|
+
"voiceFill",
|
|
32
|
+
"voicePixelBackground"
|
|
33
|
+
];
|
|
12
34
|
/**
|
|
13
35
|
* Shows the given sentence in the prompt. The surface records clips with the quality check, and
|
|
14
36
|
* stays open so the caller can show its check and the result in the same card. A too quiet or
|
|
@@ -27,6 +49,14 @@ var CardSurface = class {
|
|
|
27
49
|
pressed;
|
|
28
50
|
/** Back, when the card shows it apart from Close. */
|
|
29
51
|
back;
|
|
52
|
+
/** Minimize, when the host can hide the card. */
|
|
53
|
+
minimize;
|
|
54
|
+
minimizedHook;
|
|
55
|
+
progressedHook;
|
|
56
|
+
/** The person pressed Minimize, and the card has not asked to show again since. */
|
|
57
|
+
minimized = false;
|
|
58
|
+
/** The last stage that the host heard, as a key. */
|
|
59
|
+
progress = "";
|
|
30
60
|
recorder = null;
|
|
31
61
|
recording = null;
|
|
32
62
|
choice = null;
|
|
@@ -55,8 +85,10 @@ var CardSurface = class {
|
|
|
55
85
|
this.element.setAttribute("role", "dialog");
|
|
56
86
|
this.element.setAttribute("aria-modal", "true");
|
|
57
87
|
this.element.setAttribute("aria-label", LABEL);
|
|
88
|
+
paintColors(this.element, input.colors);
|
|
58
89
|
this.prompt = new VoicePrompt(document, LABEL, sentence);
|
|
59
|
-
|
|
90
|
+
const promptLabel = input.context?.sentenceLabel ?? PROMPT_LABEL;
|
|
91
|
+
this.prompt.title.textContent = promptLabel;
|
|
60
92
|
const shown = /[.!?]$/.test(sentence) ? sentence : `${sentence}.`;
|
|
61
93
|
this.prompt.phrase.textContent = shown;
|
|
62
94
|
delete this.prompt.phrase.dataset.voiceidPhrase;
|
|
@@ -65,7 +97,11 @@ var CardSurface = class {
|
|
|
65
97
|
const controls = frame.controls ?? "close";
|
|
66
98
|
this.pressed = frame.pressed;
|
|
67
99
|
const back = controls === "close" ? null : this.prompt.showBack(controls === "back_and_close");
|
|
100
|
+
if (input.context) this.prompt.showContext(input.context.label, input.context.text);
|
|
68
101
|
this.back = back === this.prompt.cancel ? null : back;
|
|
102
|
+
this.minimizedHook = frame.minimized;
|
|
103
|
+
this.progressedHook = frame.progressed;
|
|
104
|
+
this.minimize = frame.minimized ? this.prompt.showMinimize() : null;
|
|
69
105
|
const handedOverPrompt = handedOver?.querySelector(":scope > .seams-voiceid-prompt") ?? null;
|
|
70
106
|
const rows = handedOverPrompt ? handedOverRows(handedOverPrompt) : null;
|
|
71
107
|
if (handedOverPrompt) {
|
|
@@ -74,23 +110,31 @@ var CardSurface = class {
|
|
|
74
110
|
this.autoStart = false;
|
|
75
111
|
}
|
|
76
112
|
this.card = new PhraseCard(this.prompt, {
|
|
77
|
-
label:
|
|
113
|
+
label: promptLabel,
|
|
78
114
|
sentence: shown,
|
|
79
115
|
message: PROMPT_MESSAGE,
|
|
80
116
|
action: "Record"
|
|
81
117
|
}, rows);
|
|
118
|
+
if (this.autoStart) {
|
|
119
|
+
this.prompt.record.hidden = true;
|
|
120
|
+
this.prompt.element.dataset.autoStart = "";
|
|
121
|
+
this.prompt.element.tabIndex = -1;
|
|
122
|
+
}
|
|
82
123
|
this.element.append(this.prompt.element);
|
|
83
124
|
this.prompt.record.addEventListener("click", this.press);
|
|
84
125
|
const corner = controls === "back" ? this.pressBack : this.pressClose;
|
|
85
126
|
this.prompt.cancel.addEventListener("click", corner);
|
|
86
127
|
this.back?.addEventListener("click", this.pressBack);
|
|
128
|
+
this.minimize?.addEventListener("click", this.pressMinimize);
|
|
87
129
|
this.element.addEventListener("cancel", this.cancel);
|
|
88
130
|
outer.addEventListener("abort", this.cancel);
|
|
89
131
|
if (handedOver) handedOver.replaceWith(this.element);
|
|
90
132
|
else parent.appendChild(this.element);
|
|
133
|
+
const voiceLine = input.voiceLine ?? themeVoiceLine(this.prompt.element);
|
|
134
|
+
if (voiceLine === "pixels") this.prompt.useVoiceLine(voiceLine);
|
|
91
135
|
if (frame.hugged) this.element.classList.add("seams-voiceid-hugged");
|
|
92
136
|
this.unmounted = frame.mounted?.(this.element);
|
|
93
|
-
if (rows) this.prompt.element.focus({ preventScroll: true });
|
|
137
|
+
if (rows || this.autoStart) this.prompt.element.focus({ preventScroll: true });
|
|
94
138
|
else this.prompt.record.focus();
|
|
95
139
|
if (outer.aborted) this.cancel();
|
|
96
140
|
}
|
|
@@ -102,15 +146,36 @@ var CardSurface = class {
|
|
|
102
146
|
this.recording = recording;
|
|
103
147
|
if (this.view) {
|
|
104
148
|
this.view = null;
|
|
149
|
+
this.report({
|
|
150
|
+
stage: "recording",
|
|
151
|
+
done: 0,
|
|
152
|
+
total: 0
|
|
153
|
+
});
|
|
105
154
|
this.card.showPrompt();
|
|
106
155
|
} else if (this.autoStart) this.begin(recording);
|
|
107
156
|
this.autoStart = false;
|
|
108
157
|
return recording.promise;
|
|
109
158
|
}
|
|
110
159
|
show(view) {
|
|
160
|
+
delete this.prompt.element.dataset.autoStart;
|
|
111
161
|
if (this.closed) return Promise.resolve("closed");
|
|
112
162
|
this.settleChoice("replaced");
|
|
113
163
|
this.view = view;
|
|
164
|
+
if (view.kind === "check") {
|
|
165
|
+
const done = view.segments.filter((state) => state === "done" || state === "skip").length;
|
|
166
|
+
this.report({
|
|
167
|
+
stage: "checking",
|
|
168
|
+
done,
|
|
169
|
+
total: view.segments.length
|
|
170
|
+
});
|
|
171
|
+
} else {
|
|
172
|
+
this.report({
|
|
173
|
+
stage: "result",
|
|
174
|
+
done: 0,
|
|
175
|
+
total: 0
|
|
176
|
+
});
|
|
177
|
+
this.needPerson();
|
|
178
|
+
}
|
|
114
179
|
this.card.show(view);
|
|
115
180
|
return new Promise((resolve) => {
|
|
116
181
|
this.choice = resolve;
|
|
@@ -129,6 +194,7 @@ var CardSurface = class {
|
|
|
129
194
|
this.prompt.cancel.removeEventListener("click", this.pressBack);
|
|
130
195
|
this.prompt.cancel.removeEventListener("click", this.pressClose);
|
|
131
196
|
this.back?.removeEventListener("click", this.pressBack);
|
|
197
|
+
this.minimize?.removeEventListener("click", this.pressMinimize);
|
|
132
198
|
this.card.dispose();
|
|
133
199
|
this.prompt.dispose();
|
|
134
200
|
leaveSurface(this.element);
|
|
@@ -144,6 +210,11 @@ var CardSurface = class {
|
|
|
144
210
|
};
|
|
145
211
|
begin(recording) {
|
|
146
212
|
this.started = true;
|
|
213
|
+
this.report({
|
|
214
|
+
stage: "recording",
|
|
215
|
+
done: 0,
|
|
216
|
+
total: 0
|
|
217
|
+
});
|
|
147
218
|
this.prompt.record.disabled = true;
|
|
148
219
|
this.prompt.status.textContent = this.speechDetectorReady ? "Opening microphone…" : `Loading ${captureConfig().label}…`;
|
|
149
220
|
this.attempt(recording, this.autoStart);
|
|
@@ -192,14 +263,46 @@ var CardSurface = class {
|
|
|
192
263
|
};
|
|
193
264
|
/** The end of speech starts the check in place; every other capture event is the prompt's. */
|
|
194
265
|
capture = (event) => {
|
|
195
|
-
if (event.kind === "processing")
|
|
196
|
-
|
|
266
|
+
if (event.kind === "processing") {
|
|
267
|
+
this.report({
|
|
268
|
+
stage: "checking",
|
|
269
|
+
done: 0,
|
|
270
|
+
total: 0
|
|
271
|
+
});
|
|
272
|
+
this.card.beginCheck();
|
|
273
|
+
} else this.prompt.capture(event);
|
|
197
274
|
};
|
|
198
275
|
retry(message, action = "Try again") {
|
|
199
276
|
this.started = false;
|
|
277
|
+
delete this.prompt.element.dataset.autoStart;
|
|
200
278
|
this.prompt.record.textContent = action;
|
|
201
279
|
this.prompt.ready(message);
|
|
202
280
|
this.prompt.record.focus();
|
|
281
|
+
this.report({
|
|
282
|
+
stage: "recording",
|
|
283
|
+
done: 0,
|
|
284
|
+
total: 0
|
|
285
|
+
});
|
|
286
|
+
this.needPerson();
|
|
287
|
+
}
|
|
288
|
+
/** Tells the host each new stage of the card. */
|
|
289
|
+
report(progress) {
|
|
290
|
+
const key = `${progress.stage}:${progress.done}:${progress.total}`;
|
|
291
|
+
if (key === this.progress || this.closed) return;
|
|
292
|
+
this.progress = key;
|
|
293
|
+
this.progressedHook?.(progress);
|
|
294
|
+
}
|
|
295
|
+
/** The person asks the host to hide the card. A host that showed it again can hide it again. */
|
|
296
|
+
pressMinimize = () => {
|
|
297
|
+
if (this.closed) return;
|
|
298
|
+
this.minimized = true;
|
|
299
|
+
this.minimizedHook?.(true);
|
|
300
|
+
};
|
|
301
|
+
/** The card needs the person: the host shows the card that it hid. */
|
|
302
|
+
needPerson() {
|
|
303
|
+
if (!this.minimized) return;
|
|
304
|
+
this.minimized = false;
|
|
305
|
+
this.minimizedHook?.(false);
|
|
203
306
|
}
|
|
204
307
|
settleChoice(choice) {
|
|
205
308
|
const settle = this.choice;
|
|
@@ -226,6 +329,18 @@ var CardSurface = class {
|
|
|
226
329
|
if (this.outer.aborted) this.close();
|
|
227
330
|
};
|
|
228
331
|
};
|
|
332
|
+
/** Sets the card's own colours. Keys that a card does not read are left out. */
|
|
333
|
+
function paintColors(element, colors) {
|
|
334
|
+
if (!colors) return;
|
|
335
|
+
for (const key of PHRASE_CARD_COLORS) {
|
|
336
|
+
const value = colors[key];
|
|
337
|
+
if (value) element.style.setProperty(`--seams-colors-${key}`, value);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
/** The theme's `--seams-shape-voiceLine`: `pixels`, or else the wave lines. */
|
|
341
|
+
function themeVoiceLine(element) {
|
|
342
|
+
return getComputedStyle(element).getPropertyValue("--seams-shape-voiceLine").trim() === "pixels" ? "pixels" : "wave";
|
|
343
|
+
}
|
|
229
344
|
/** A failed early load runs again when Record needs the detector, and shows its error there. */
|
|
230
345
|
function ignoreEarlyLoad() {}
|
|
231
346
|
/** The rows that the handed-over card shows now. */
|
package/dist/esm/card/visual.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as VoiceVisual } from "../chunks/visual-
|
|
2
|
-
export { VoiceVisual };
|
|
1
|
+
import { n as beamPalette, t as VoiceVisual } from "../chunks/visual-DU1OVwCA.js";
|
|
2
|
+
export { VoiceVisual, beamPalette };
|
|
@@ -1430,6 +1430,31 @@ ${Le.split("{id}").join(de)}` : ca }), /* @__PURE__ */ jsxs("div", {
|
|
|
1430
1430
|
]
|
|
1431
1431
|
})] });
|
|
1432
1432
|
});
|
|
1433
|
+
/** The Beam's palette without a theme value. */
|
|
1434
|
+
const DEFAULT_BEAM = "ocean";
|
|
1435
|
+
/**
|
|
1436
|
+
* The band of the Beam in greys, for the `mono` palette: its own band has colour fringes in
|
|
1437
|
+
* every palette, pink on light and red and blue on dark.
|
|
1438
|
+
*/
|
|
1439
|
+
const GREY_BAND = {
|
|
1440
|
+
light: {
|
|
1441
|
+
core: "#8b98a5",
|
|
1442
|
+
above: "#cfd9de",
|
|
1443
|
+
mid: "#aab8c2",
|
|
1444
|
+
below: "#657786"
|
|
1445
|
+
},
|
|
1446
|
+
dark: {
|
|
1447
|
+
core: "#ffffff",
|
|
1448
|
+
above: "#8b98a5",
|
|
1449
|
+
mid: "#cfd9de",
|
|
1450
|
+
below: "#536471"
|
|
1451
|
+
}
|
|
1452
|
+
};
|
|
1453
|
+
/** The theme's `voiceBeam`, the name of a Beam palette, or the default one. */
|
|
1454
|
+
function beamPalette(value) {
|
|
1455
|
+
const name = value.trim();
|
|
1456
|
+
return Object.hasOwn(Va, name) ? name : DEFAULT_BEAM;
|
|
1457
|
+
}
|
|
1433
1458
|
/** Shared by enrollment, unlock and signing. The recorder remains the only audio owner. */
|
|
1434
1459
|
var VoiceVisual = class {
|
|
1435
1460
|
element;
|
|
@@ -1459,6 +1484,10 @@ var VoiceVisual = class {
|
|
|
1459
1484
|
attributeFilter: ["data-theme"]
|
|
1460
1485
|
});
|
|
1461
1486
|
}
|
|
1487
|
+
/** Draws the voice as wave lines (the default) or as columns of pixels. */
|
|
1488
|
+
setLineStyle(style) {
|
|
1489
|
+
this.ribbon.setLineStyle(style);
|
|
1490
|
+
}
|
|
1462
1491
|
listen() {
|
|
1463
1492
|
this.setPhase("listening");
|
|
1464
1493
|
}
|
|
@@ -1503,18 +1532,21 @@ var VoiceVisual = class {
|
|
|
1503
1532
|
}
|
|
1504
1533
|
render = () => {
|
|
1505
1534
|
if (this.disposed) return;
|
|
1535
|
+
const mode = (this.prompt.closest("[data-theme]")?.getAttribute("data-theme") ?? this.prompt.ownerDocument.documentElement.getAttribute("data-theme")) === "light" ? "light" : "dark";
|
|
1536
|
+
const palette = beamPalette(getComputedStyle(this.prompt).getPropertyValue("--seams-colors-voiceBeam"));
|
|
1506
1537
|
render(createElement(VoiceBeamView, {
|
|
1507
1538
|
className: "seams-voiceid-beam",
|
|
1508
|
-
theme:
|
|
1539
|
+
theme: mode,
|
|
1540
|
+
...palette === "mono" ? { bandColors: GREY_BAND[mode] } : {},
|
|
1509
1541
|
level: this.ribbon.readLevel,
|
|
1510
1542
|
processing: this.phase === "processing",
|
|
1511
1543
|
active: this.phase !== "idle",
|
|
1512
1544
|
staticColors: true,
|
|
1513
|
-
colorVariant:
|
|
1545
|
+
colorVariant: palette,
|
|
1514
1546
|
borderRadius: Number.parseFloat(getComputedStyle(this.prompt).borderRadius) || 16,
|
|
1515
1547
|
children: null
|
|
1516
1548
|
}), this.glow);
|
|
1517
1549
|
};
|
|
1518
1550
|
};
|
|
1519
1551
|
//#endregion
|
|
1520
|
-
export { VoiceVisual as t };
|
|
1552
|
+
export { beamPalette as n, VoiceVisual as t };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export { openPhraseCard, type PhraseCardControl, type PhraseCardControls, type PhraseCardFrame, type PhraseCardInput, } from './surface';
|
|
1
|
+
export { openPhraseCard, type PhraseCardColor, type PhraseCardColors, type PhraseCardContext, type PhraseCardControl, type PhraseCardControls, type PhraseCardFrame, type PhraseCardInput, type PhraseCardProgress, } from './surface';
|
|
2
2
|
export type { PhraseCheckView, PhraseChoice, PhraseRecording, PhraseResultView, PhraseSegmentState, PhraseSurface, PhraseText, PhraseView, } from './types';
|
|
3
3
|
export { leaveSurface, VoicePrompt, type PhraseDisplay, type PromptChange } from './prompt';
|
|
4
4
|
export { PhraseCard, type HandedOverRows, type PromptCopy } from './phraseCard';
|
|
5
|
-
export { VoiceRibbon, type VoiceCheckingStyle } from './ribbon';
|
|
5
|
+
export { VoiceRibbon, type VoiceCheckingStyle, type VoiceLineStyle } from './ribbon';
|
|
6
6
|
export { VoiceVisual } from './visual';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { VoiceCaptureEvent } from '../microphone';
|
|
2
|
-
import type { VoiceCheckingStyle } from './ribbon';
|
|
2
|
+
import type { VoiceCheckingStyle, VoiceLineStyle } from './ribbon';
|
|
3
3
|
/** How the card shows a phrase: by default as it is, with a capital letter and a full stop. */
|
|
4
4
|
export type PhraseDisplay = (phrase: string) => string;
|
|
5
5
|
/** A new state of the card's rows. Rows that it leaves out keep their content. */
|
|
@@ -45,12 +45,24 @@ export declare class VoicePrompt {
|
|
|
45
45
|
constructor(document: Document, label: string, phrase: string, checking?: VoiceCheckingStyle,
|
|
46
46
|
/** The text to show for a phrase, such as the phrase with its commas. */
|
|
47
47
|
display?: PhraseDisplay);
|
|
48
|
+
/** Draws the voice as wave lines (the default) or as columns of pixels. */
|
|
49
|
+
useVoiceLine(style: VoiceLineStyle): void;
|
|
50
|
+
/**
|
|
51
|
+
* Shows the text that the sentence speaks for, such as the note that a spoken summary signs,
|
|
52
|
+
* above the sentence. It stays for the check and the result.
|
|
53
|
+
*/
|
|
54
|
+
showContext(label: string, text: string): void;
|
|
48
55
|
/**
|
|
49
56
|
* In an app's dialog with a step before this card, Back in the leading corner returns to that
|
|
50
57
|
* step. With `close`, Close stays in the trailing corner and ends the whole dialog; without it,
|
|
51
58
|
* Back takes Close's place. Each one ends the card as cancelled. The result is the Back control.
|
|
52
59
|
*/
|
|
53
60
|
showBack(close: boolean): HTMLButtonElement;
|
|
61
|
+
/**
|
|
62
|
+
* Minimize in the trailing corner, beside Close. It stays in the check, where Close is hidden,
|
|
63
|
+
* so the person can hide the card while the check goes on. The result is the control.
|
|
64
|
+
*/
|
|
65
|
+
showMinimize(): HTMLButtonElement;
|
|
54
66
|
/**
|
|
55
67
|
* Moves the rows to a new state. The rows whose content changes fade out together, take their
|
|
56
68
|
* new content and fade in one after another; the other rows stay still. A change that comes
|
|
@@ -2,6 +2,8 @@ import type { VoiceActivityEvent } from '../microphone';
|
|
|
2
2
|
type RibbonPhase = 'idle' | 'listening' | 'processing';
|
|
3
3
|
/** How the line shows that the voice is being checked. */
|
|
4
4
|
export type VoiceCheckingStyle = 'ring' | 'segments';
|
|
5
|
+
/** How the card draws the voice while it listens: sine wave lines, or columns of pixels. */
|
|
6
|
+
export type VoiceLineStyle = 'wave' | 'pixels';
|
|
5
7
|
/** Visual feedback from recorder activity. Never opens or retains an audio stream. */
|
|
6
8
|
export declare class VoiceRibbon {
|
|
7
9
|
private readonly checking;
|
|
@@ -25,8 +27,21 @@ export declare class VoiceRibbon {
|
|
|
25
27
|
private target;
|
|
26
28
|
private level;
|
|
27
29
|
private stopped;
|
|
30
|
+
private lineStyle;
|
|
31
|
+
/** The pixel style's dots by column, top row first. Made when the style is first chosen. */
|
|
32
|
+
private pixels;
|
|
33
|
+
/** The level of each column, the newest at the right end. */
|
|
34
|
+
private readonly pixelLevels;
|
|
35
|
+
/** The rows that each column shows now, above and below the middle row. */
|
|
36
|
+
private readonly pixelHeights;
|
|
37
|
+
private pixelClock;
|
|
28
38
|
constructor(document: Document, checking?: VoiceCheckingStyle);
|
|
29
39
|
readonly readLevel: () => number;
|
|
40
|
+
/** Draws the voice as wave lines (the default) or as columns of pixels. */
|
|
41
|
+
setLineStyle(style: VoiceLineStyle): void;
|
|
42
|
+
private makePixels;
|
|
43
|
+
/** The voice line's width now: 1, or the theme's `voiceLineWidth`. */
|
|
44
|
+
private lineWidth;
|
|
30
45
|
setPhase(phase: RibbonPhase): void;
|
|
31
46
|
/**
|
|
32
47
|
* Closes the ring into the success seal; resolves when the check is drawn.
|
|
@@ -41,8 +56,12 @@ export declare class VoiceRibbon {
|
|
|
41
56
|
private wake;
|
|
42
57
|
private readonly draw;
|
|
43
58
|
private idleAmplitude;
|
|
59
|
+
/** Adds the level of each step at the right end; the older columns move left. */
|
|
60
|
+
private stepPixels;
|
|
44
61
|
private paint;
|
|
45
62
|
private paintLine;
|
|
63
|
+
/** Each column shows the middle dot and as many rows above and below it as its level. */
|
|
64
|
+
private paintPixels;
|
|
46
65
|
private paintSegments;
|
|
47
66
|
/** Each segment brightens one beat after the one before it, from the resting line's opacity. */
|
|
48
67
|
private segmentOpacity;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export declare const ROW_OUT_MS = 150;
|
|
2
|
-
export declare const ROW_IN_MS =
|
|
3
|
-
export declare const ROW_IN_DELAY_MS =
|
|
4
|
-
export declare const ROW_STAGGER_MS =
|
|
2
|
+
export declare const ROW_IN_MS = 140;
|
|
3
|
+
export declare const ROW_IN_DELAY_MS = 0;
|
|
4
|
+
export declare const ROW_STAGGER_MS = 20;
|
|
5
5
|
export declare const ROW_EASE = "cubic-bezier(0.2, 0, 0, 1)";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type VoiceRecordingOptions } from '../microphone';
|
|
2
|
+
import type { VoiceLineStyle } from './ribbon';
|
|
2
3
|
import type { PhraseSurface } from './types';
|
|
3
4
|
/** What the host page adds around the card. Every field is optional. */
|
|
4
5
|
export type PhraseCardFrame = Readonly<{
|
|
@@ -14,6 +15,16 @@ export type PhraseCardFrame = Readonly<{
|
|
|
14
15
|
controls?: PhraseCardControls;
|
|
15
16
|
/** Runs when the person presses Back or Close, before the card ends. Escape is not a press. */
|
|
16
17
|
pressed?(control: PhraseCardControl): void;
|
|
18
|
+
/**
|
|
19
|
+
* With it, the card draws Minimize in the trailing corner, beside Close, also in the check.
|
|
20
|
+
* A press runs `minimized(true)`. The card does not hide itself: the host hides it, and the
|
|
21
|
+
* recording, the check and the caller's work go on. The card runs `minimized(false)` when it
|
|
22
|
+
* needs the person again: on a result, and when a recording needs another try. The host can
|
|
23
|
+
* show the card again before that.
|
|
24
|
+
*/
|
|
25
|
+
minimized?(minimized: boolean): void;
|
|
26
|
+
/** Runs on each new stage of the card, so the host can show the progress of a hidden card. */
|
|
27
|
+
progressed?(progress: PhraseCardProgress): void;
|
|
17
28
|
/** The host sizes its frame to the surface, so the card follows the frame. */
|
|
18
29
|
hugged?: boolean;
|
|
19
30
|
/** Runs when the surface is in the document. Its return value runs when the surface closes. */
|
|
@@ -23,6 +34,16 @@ export type PhraseCardFrame = Readonly<{
|
|
|
23
34
|
export type PhraseCardControls = 'close' | 'back' | 'back_and_close';
|
|
24
35
|
/** One corner control of a phrase card. */
|
|
25
36
|
export type PhraseCardControl = 'back' | 'close';
|
|
37
|
+
/**
|
|
38
|
+
* The stage of a phrase card: it records the sentence (or waits for Record), it checks the
|
|
39
|
+
* recording, or it shows a result.
|
|
40
|
+
*/
|
|
41
|
+
export type PhraseCardProgress = Readonly<{
|
|
42
|
+
stage: 'recording' | 'checking' | 'result';
|
|
43
|
+
/** The parts of the check that are over, of `total`. Both are 0 outside a check. */
|
|
44
|
+
done: number;
|
|
45
|
+
total: number;
|
|
46
|
+
}>;
|
|
26
47
|
export type PhraseCardInput = Readonly<{
|
|
27
48
|
sentence: string;
|
|
28
49
|
signal: AbortSignal;
|
|
@@ -35,6 +56,47 @@ export type PhraseCardInput = Readonly<{
|
|
|
35
56
|
* start audio without a press, the card stays with its Record button.
|
|
36
57
|
*/
|
|
37
58
|
autoStart?: boolean;
|
|
59
|
+
/**
|
|
60
|
+
* The text that the sentence speaks for, such as the note that a spoken summary signs. With
|
|
61
|
+
* it, the card is a form: the text in a soft box under its label, the sentence centred in a
|
|
62
|
+
* box under its own label, and one button at the foot that is the voice line while the card
|
|
63
|
+
* records. The text stays for the check and the result.
|
|
64
|
+
*/
|
|
65
|
+
context?: PhraseCardContext;
|
|
66
|
+
/**
|
|
67
|
+
* How the card draws the voice while it listens: `wave`, sine wave lines (the default), or
|
|
68
|
+
* `pixels`, columns of dots in blue tones. Without it, the theme value `--seams-shape-voiceLine`
|
|
69
|
+
* (`wave` or `pixels`) decides, so a Wallet host can choose it with its theme.
|
|
70
|
+
*/
|
|
71
|
+
voiceLine?: VoiceLineStyle;
|
|
72
|
+
/**
|
|
73
|
+
* The card's colours, as CSS colours, for example `{ buttonBackground: '#1d9bf0' }`. Each one
|
|
74
|
+
* sets the CSS variable `--seams-colors-<key>` on this card, over the page's own value. A
|
|
75
|
+
* Seams Wallet app sets the same keys in `appearance.theme.colors`.
|
|
76
|
+
*/
|
|
77
|
+
colors?: PhraseCardColors;
|
|
78
|
+
}>;
|
|
79
|
+
/** The colours that a phrase card reads. */
|
|
80
|
+
declare const PHRASE_CARD_COLORS: readonly [
|
|
81
|
+
/** The card, its text, its lines and its soft boxes. */
|
|
82
|
+
'colorBackground', 'textPrimary', 'textSecondary', 'textMuted', 'borderPrimary', 'surface',
|
|
83
|
+
/** The main button (Record, the voice line of a form card, Done) and its text. */
|
|
84
|
+
'buttonBackground', 'textButton',
|
|
85
|
+
/** A failed check, and a held one. */
|
|
86
|
+
'error', 'info',
|
|
87
|
+
/** The voice line, its echoes, and the bands of the pixel style. */
|
|
88
|
+
'voiceLine', 'voiceEcho', 'voiceEchoDeep',
|
|
89
|
+
/** A form card: the fill of the sentence's box, and the button behind the pixels. */
|
|
90
|
+
'voiceFill', 'voicePixelBackground'];
|
|
91
|
+
export type PhraseCardColor = (typeof PHRASE_CARD_COLORS)[number];
|
|
92
|
+
export type PhraseCardColors = Partial<Readonly<Record<PhraseCardColor, string>>>;
|
|
93
|
+
/** The text that a phrase card shows above its sentence. */
|
|
94
|
+
export type PhraseCardContext = Readonly<{
|
|
95
|
+
/** The label above the text, such as "Your note on @alice". */
|
|
96
|
+
label: string;
|
|
97
|
+
text: string;
|
|
98
|
+
/** The label above the sentence, such as "Read this aloud to post". Default: "Say this sentence". */
|
|
99
|
+
sentenceLabel?: string;
|
|
38
100
|
}>;
|
|
39
101
|
/**
|
|
40
102
|
* Shows the given sentence in the prompt. The surface records clips with the quality check, and
|
|
@@ -42,3 +104,4 @@ export type PhraseCardInput = Readonly<{
|
|
|
42
104
|
* interrupted recording stays on the surface for another try.
|
|
43
105
|
*/
|
|
44
106
|
export declare function openPhraseCard(input: PhraseCardInput): PhraseSurface;
|
|
107
|
+
export {};
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import { type VoiceBeamColorVariant } from 'voice-glow';
|
|
1
2
|
import type { VoiceActivityEvent } from '../microphone';
|
|
2
|
-
import { type VoiceCheckingStyle } from './ribbon';
|
|
3
|
+
import { type VoiceCheckingStyle, type VoiceLineStyle } from './ribbon';
|
|
4
|
+
/** The theme's `voiceBeam`, the name of a Beam palette, or the default one. */
|
|
5
|
+
export declare function beamPalette(value: string): VoiceBeamColorVariant;
|
|
3
6
|
/** Shared by enrollment, unlock and signing. The recorder remains the only audio owner. */
|
|
4
7
|
export declare class VoiceVisual {
|
|
5
8
|
readonly element: SVGSVGElement;
|
|
@@ -10,6 +13,8 @@ export declare class VoiceVisual {
|
|
|
10
13
|
private phase;
|
|
11
14
|
private disposed;
|
|
12
15
|
constructor(document: Document, prompt: HTMLElement, checking: VoiceCheckingStyle);
|
|
16
|
+
/** Draws the voice as wave lines (the default) or as columns of pixels. */
|
|
17
|
+
setLineStyle(style: VoiceLineStyle): void;
|
|
13
18
|
listen(): void;
|
|
14
19
|
add(event: VoiceActivityEvent): void;
|
|
15
20
|
checking(): void;
|
package/package.json
CHANGED