ranui
Version:
A framework-agnostic Web Components UI library built on native custom elements, with TypeScript types, light/dark theming, SSR and PWA support.
176 lines (175 loc) • 8.66 kB
JavaScript
import { T as o, _ as a, n as l } from "./factory-WDxu11V0.js";
import { t as h } from "./events-Cqz3ramM.js";
import "./builder-EH76YCFS.js";
import { a as s, i, n as r, o as d, t as u } from "./utils-U-9uH7EX.js";
import { a as v, n as b } from "./ssr-BzykYefm.js";
import { createSpeechRecognizer as g } from "ranuts/utils";
var _ = ':host{display:inline-flex}:host([hidden]){display:none}.ran-voice{display:inline-flex;touch-action:none;align-items:center;justify-content:center;border:1px solid var(--ran-voice-border-color, var(--ran-color-border));border-radius:var(--ran-voice-border-radius, var(--ran-radius-md));background:var(--ran-voice-background, var(--ran-color-bg));padding:0;width:var(--ran-voice-size, var(--ran-size-7));height:var(--ran-voice-size, var(--ran-size-7));color:var(--ran-voice-color, var(--ran-color-text-secondary));cursor:pointer}.ran-voice:hover:not(:disabled){border-color:var(--ran-voice-hover-border-color, var(--ran-color-border-hover));color:var(--ran-voice-hover-color, var(--ran-color-text))}.ran-voice:focus-visible{outline:2px solid var(--ran-voice-focus-color, var(--ran-color-primary));outline-offset:2px}.ran-voice:disabled{color:var(--ran-voice-disabled-color, var(--ran-color-text-disabled));cursor:not-allowed}:host([listening]) .ran-voice{border-color:var(--ran-voice-active-border-color, var(--ran-color-danger));background:var(--ran-voice-active-background, var(--ran-red-200));color:var(--ran-voice-active-color, var(--ran-color-danger))}:host([listening]) .ran-voice:after{position:absolute;border:2px solid var(--ran-voice-active-border-color, var(--ran-color-danger));border-radius:var(--ran-voice-border-radius, var(--ran-radius-md));width:var(--ran-voice-size, var(--ran-size-7));height:var(--ran-voice-size, var(--ran-size-7));content:"";animation:ran-voice-pulse 1.6s ease-out infinite}.ran-voice{position:relative}@keyframes ran-voice-pulse{0%{opacity:.7;transform:scale(1)}to{opacity:0;transform:scale(1.6)}}@media(prefers-reduced-motion:reduce){:host([listening]) .ran-voice:after{animation:none;opacity:0}}.ran-voice-hint{position:absolute;bottom:calc(100% + var(--ran-voice-hint-gap, var(--ran-space-2)));left:50%;z-index:1;transform:translate(-50%);border-radius:var(--ran-voice-hint-radius, var(--ran-radius-md));background:var(--ran-voice-hint-background, var(--ran-color-bg-elevated));box-shadow:var(--ran-voice-hint-shadow, var(--ran-shadow-menu));padding:var(--ran-voice-hint-padding, var(--ran-space-2) var(--ran-space-3));min-width:max-content;color:var(--ran-voice-hint-color, var(--ran-color-text-secondary));font-size:var(--ran-voice-hint-font-size, var(--ran-text-label-3));text-align:center;pointer-events:none}:host(:not([holding])) .ran-voice-hint{display:none}:host([cancelling]) .ran-voice-hint{color:var(--ran-voice-hint-cancel-color, var(--ran-color-danger))}:host([cancelling]) .ran-voice{border-color:var(--ran-voice-active-border-color, var(--ran-color-danger));background:var(--ran-voice-cancel-background, var(--ran-color-bg-subtle))}', p = class c extends b {
_events = new h();
_shadowDom;
_button;
_icon;
_hint;
_recognizer = null;
_holdPointer = null;
_holdOrigin = 0;
static get observedAttributes() {
return [
"disabled",
"label",
"active-label",
"hold-hint",
"cancel-hint",
"listening",
"holding",
"cancelling",
"sheet"
];
}
constructor() {
super(), this._shadowDom = u(this, _);
const t = o(), e = o(), n = l().class("ran-voice").attr("part", "button").attr("type", "button").attr("aria-pressed", "false").children(a("r-icon").ref(t).attr("name", "mic").attr("part", "icon").build(), a("span").class("ran-voice-hint").ref(e).attr("part", "hint").build()).build();
this._shadowDom.appendChild(n), this._button = n, this._icon = s(t, "r icon"), this._hint = s(e, "hint");
}
get listening() {
return this.hasAttribute("listening");
}
get supported() {
return this._ensureRecognizer().supported;
}
get lang() {
return r(this, "lang") || document.documentElement.lang || navigator.language;
}
set lang(t) {
i(this, "lang", t);
}
get continuous() {
return r(this, "continuous", "true") !== "false";
}
set continuous(t) {
i(this, "continuous", t ? "true" : "false");
}
get disabled() {
return this.hasAttribute("disabled");
}
set disabled(t) {
t ? this.setAttribute("disabled", "") : this.removeAttribute("disabled");
}
get label() {
return r(this, "label", "Start voice input");
}
set label(t) {
i(this, "label", t);
}
get activeLabel() {
return r(this, "active-label", "Stop voice input");
}
set activeLabel(t) {
i(this, "active-label", t);
}
get holdHint() {
return r(this, "hold-hint", "Release to keep · slide up to cancel");
}
set holdHint(t) {
i(this, "hold-hint", t);
}
get cancelHint() {
return r(this, "cancel-hint", "Release to cancel");
}
set cancelHint(t) {
i(this, "cancel-hint", t);
}
get sheet() {
return r(this, "sheet");
}
set sheet(t) {
i(this, "sheet", t);
}
start() {
this.disabled || this._ensureRecognizer().start();
}
stop() {
this._recognizer?.stop();
}
abort() {
this._recognizer?.abort();
}
toggle() {
const t = this._ensureRecognizer();
t.active ? t.stop() : this.start();
}
connectedCallback() {
this.handlerExternalCss(), this._events.on(this._button, "click", this._onClick), this._events.on(this._button, "pointerdown", this._onPointerDown), this._events.on(this._button, "pointermove", this._onPointerMove), this._events.on(this._button, "pointerup", this._onPointerUp), this._events.on(this._button, "pointercancel", this._onPointerCancel), this._events.on(this, "keydown", this._onKeydown), this._syncButton(), this.supported || (this.hidden = !0);
}
disconnectedCallback() {
this._events.abort(), this._endHold(), this._recognizer?.abort();
}
attributeChangedCallback(t, e, n) {
e !== n && (t === "sheet" ? this.handlerExternalCss() : this._syncButton());
}
handlerExternalCss = () => {
d(this, this._shadowDom, "sheet", null, this.sheet);
};
static CANCEL_DISTANCE = 48;
_onClick = () => {
this._holdPointer === null && this.toggle();
};
_onPointerDown = (t) => {
t.pointerType !== "touch" || this.disabled || this.listening || (t.preventDefault(), this._holdPointer = t.pointerId, this._holdOrigin = t.clientY, this.setAttribute("holding", ""), this._button.setPointerCapture?.(t.pointerId), this._syncButton(), this.start());
};
_onPointerMove = (t) => {
if (t.pointerId !== this._holdPointer) return;
const e = this._holdOrigin - t.clientY > c.CANCEL_DISTANCE;
e !== this.hasAttribute("cancelling") && (e ? this.setAttribute("cancelling", "") : this.removeAttribute("cancelling"), this._syncButton());
};
_onPointerUp = (t) => {
if (t.pointerId !== this._holdPointer) return;
const e = this.hasAttribute("cancelling");
this._endHold(), e ? this.abort() : this.stop();
};
_onPointerCancel = (t) => {
t.pointerId === this._holdPointer && (this._endHold(), this.abort());
};
_endHold() {
this._holdPointer !== null && this._button.releasePointerCapture?.(this._holdPointer), this._holdPointer = null, this.removeAttribute("holding"), this.removeAttribute("cancelling"), this._syncButton();
}
_onKeydown = (t) => {
t.key !== "Escape" || !this.listening || (t.stopPropagation(), this.abort());
};
_emit(t, e) {
this.dispatchEvent(new CustomEvent(t, {
detail: e,
bubbles: !0,
composed: !0
}));
}
_ensureRecognizer() {
return this._recognizer ??= g({
lang: () => this.lang,
continuous: this.continuous,
onStart: () => {
this.setAttribute("listening", ""), this._syncButton(), this._emit("voicestart");
},
onResult: (t, e) => {
this._emit("voiceresult", {
transcript: t,
isFinal: e
});
},
onError: (t) => {
this._emit("voiceerror", t);
},
onEnd: () => {
this.removeAttribute("listening"), this._syncButton(), this._emit("voiceend");
}
}), this._recognizer;
}
_syncButton() {
const t = this.listening;
this._button.disabled = this.disabled, this._button.setAttribute("aria-pressed", t ? "true" : "false"), this._button.setAttribute("aria-label", t ? this.activeLabel : this.label), this._icon.setAttribute("name", t ? "mic-off" : "mic"), this._hint.textContent = this.hasAttribute("cancelling") ? this.cancelHint : this.holdHint;
}
};
v("r-voice-button", p);
export {
p as t
};