ranui
Version:
A framework-agnostic Web Components UI library built on native custom elements, with TypeScript types, light/dark theming, SSR and PWA support.
117 lines (116 loc) • 5.15 kB
TypeScript
import { EventManager } from '../../utils/builder';
import { RanElement } from '../../utils';
/**
* `<r-voice-button>` — dictation for a text composer.
*
* A microphone button, and nothing else. It owns the capture and reports what was heard;
* where that text goes is the caller's decision, because a component that also wrote into
* an input would have to know which input, whether to append or replace, and what to do
* about the caret — three answers that differ per app.
*
* ```ts
* const mic = document.querySelector('r-voice-button');
* let base = '';
* mic.addEventListener('voicestart', () => { base = input.value; });
* mic.addEventListener('voiceresult', (e) => {
* input.value = base + e.detail.transcript; // the whole capture, revised as it firms up
* });
* ```
*
* **It does not send.** Recognition is wrong often enough that committing on its behalf
* takes away the review the speaker needs, so this fills the box and stops there.
*
* **It hides itself where speech recognition does not exist** — Firefox, and any browser
* with the API absent. A button that cannot work is worse than no button: it invites a tap
* and then explains itself.
*
* **Two gestures, because two devices want different ones.** With a mouse or pen it is a
* toggle: click to start, click to stop, Escape to discard — the desktop pattern, and the
* only one available to a keyboard. Under a finger it is push-to-talk: hold to record,
* slide away to cancel, release to keep. That is the gesture a decade of voice messaging
* has taught people, and it costs a touch user nothing to learn.
*
* Attributes: `lang` (BCP 47; defaults to the document's), `continuous`, `disabled`,
* `label`, `active-label`, `hold-hint`, `cancel-hint`, `sheet`. `listening` is reflected
* while capturing; `holding` and `cancelling` while a press-and-hold is in progress.
*
* Events: `voicestart`, `voiceresult` (`{ transcript, isFinal }`), `voiceerror`
* (`{ kind, detail }`), `voiceend`.
*/
export declare class VoiceButton extends RanElement {
_events: EventManager;
_shadowDom: ShadowRoot;
_button: HTMLButtonElement;
_icon: HTMLElement;
_hint: HTMLElement;
private _recognizer;
/** Pointer id of a press-and-hold in progress, so a second finger cannot end the first. */
private _holdPointer;
/** Where the holding pointer went down, to measure the slide that cancels. */
private _holdOrigin;
static get observedAttributes(): string[];
constructor();
/** Whether a capture is running. Reflected, so `:host([listening])` can style it. */
get listening(): boolean;
/** Whether this platform can recognize speech at all. */
get supported(): boolean;
/**
* Language being spoken, as a BCP 47 tag.
*
* Read per capture rather than once, and defaulting to the document's own language, so an
* app that switches locale mid-session dictates in the language it is showing.
*/
get lang(): string;
set lang(value: string);
/** Keep listening across pauses instead of stopping at the first one. */
get continuous(): boolean;
set continuous(value: boolean);
get disabled(): boolean;
set disabled(value: boolean);
/** Accessible name while idle. */
get label(): string;
set label(value: string);
/** Accessible name while listening; the name has to change, not only the icon. */
get activeLabel(): string;
set activeLabel(value: string);
/** Shown above the button while a finger is held down. */
get holdHint(): string;
set holdHint(value: string);
/** Replaces {@link holdHint} once the finger has slid far enough to discard. */
get cancelHint(): string;
set cancelHint(value: string);
get sheet(): string;
set sheet(value: string);
/** Starts a capture. Ignored while one is running, or when unsupported or disabled. */
start(): void;
/** Ends the capture, keeping what was recognized. */
stop(): void;
/** Ends the capture and discards it — what Escape should do. */
abort(): void;
/**
* Starts if idle, stops if running.
*
* Decided from the recognizer's own state, not the reflected `listening` attribute. The
* attribute follows the platform's start event, and a capture that has begun but not yet
* reported it would leave the two disagreeing — the next activation would try to start a
* second capture, be refused, and the button would sit there doing nothing.
*/
toggle(): void;
connectedCallback(): void;
disconnectedCallback(): void;
attributeChangedCallback(name: string, old: string | null, next: string | null): void;
handlerExternalCss: () => void;
/** Distance a finger must travel before releasing discards instead of keeping. */
private static readonly CANCEL_DISTANCE;
private _onClick;
private _onPointerDown;
private _onPointerMove;
private _onPointerUp;
private _onPointerCancel;
private _endHold;
private _onKeydown;
private _emit;
private _ensureRecognizer;
private _syncButton;
}
export default VoiceButton;