UNPKG

dictate-button

Version:

Customizable Web Component that adds speech-to-text dictation capabilities to text fields

141 lines (140 loc) 5.26 kB
function v(t, c = {}) { const { buttonSize: n = 30, verbose: i = !1, customApiEndpoint: a } = c, l = document.querySelectorAll(t); for (const e of l) { if (e.hasAttribute("data-dictate-button-enabled")) continue; const p = e.parentNode; if (!e.isConnected || !p) { i && console.debug("injectDictateButton: skipping detached field", e); continue; } e.setAttribute("data-dictate-button-enabled", ""); const r = document.createElement("div"); r.style.position = "relative"; const g = getComputedStyle(e), s = g.display === "block"; r.style.display = s ? "block" : "inline-block", r.style.width = s ? "100%" : "auto", r.style.color = "inherit", r.classList.add("dictate-button-wrapper"), p.insertBefore(r, e), r.appendChild(e), r.style.margin = g.margin, e.style.margin = "0", e.style.boxSizing = "border-box"; const u = m(g); e.style.paddingRight = `${n + u * 2}px`; const o = document.createElement("dictate-button"); o.size = n, o.style.position = "absolute", o.style.right = "0", o.style.top = E( r, g, e.tagName, n ) + "px", o.style.marginRight = o.style.marginLeft = `${u}px`, o.style.marginTop = "0", o.style.marginBottom = "0", a && (o.apiEndpoint = a), o.language = h(), o.addEventListener("recording:started", (d) => { i && console.debug("recording:started", d); }), o.addEventListener("recording:stopped", (d) => { i && console.debug("recording:stopped", d); }), o.addEventListener("recording:failed", (d) => { i && console.debug("recording:failed", d), f(e); }), o.addEventListener("transcribing:started", (d) => { i && console.debug("transcribing:started", d); }), o.addEventListener("transcribing:finished", (d) => { i && console.debug("transcribing:finished", d); const b = d.detail; T(e, b); }), o.addEventListener("transcribing:failed", (d) => { i && console.debug("transcribing:failed", d), f(e); }), r.appendChild(o); } } function h() { const t = document.documentElement.lang; if (t && t.length >= 2) try { return (Intl?.Locale ? new Intl.Locale(t) : null)?.language ?? t.split(/[-_]/)[0].toLowerCase(); } catch { return t.split(/[-_]/)[0].toLowerCase(); } } function E(t, c, n, i) { if (n.toLowerCase() === "textarea") { const l = parseFloat(c.paddingTop || "0"); return Math.max(4, l); } const a = Math.round(t.clientHeight / 2 - i / 2); return Math.max(4, a); } function m(t) { const c = parseFloat(t.paddingRight || "0"); return Math.max(c, 4); } function T(t, c) { const n = typeof c == "string" ? c.trim() : String(c ?? "").trim(); n.length !== 0 && (y(t) ? N(t, n) : C(t, n), t.dispatchEvent(new Event("input", { bubbles: !0, composed: !0 })), f(t)); } function f(t) { try { t.focus({ preventScroll: !0 }); } catch { t.focus(); } } function y(t) { return t.isContentEditable; } function C(t, c) { const n = t.selectionStart ?? 0, i = t.selectionEnd ?? 0, a = n > 0 ? t.value.charAt(n - 1) : "", l = a && !/\s/.test(a), e = i < t.value.length ? t.value.charAt(i) : "", p = e && !/\s/.test(e), r = (l ? " " : "") + c + (p ? " " : ""), g = n + r.length, s = typeof t.scrollTop == "number" ? t.scrollTop : null; if (typeof t.setRangeText == "function") t.setRangeText(r, n, i, "end"); else { t.value = t.value.substring(0, n) + r + t.value.substring(i); try { t.selectionStart = g, t.selectionEnd = g; } catch { } } s !== null && (t.scrollTop = s); } function N(t, c) { const n = window.getSelection(); if (!(n && n.rangeCount > 0 && t.contains(n.getRangeAt(0).commonAncestorContainer))) { f(t); const l = document.createRange(); l.selectNodeContents(t), l.collapse(!1), n?.removeAllRanges(), n?.addRange(l); } const a = n?.getRangeAt(0); if (a) { const l = a.cloneRange(), e = a.cloneRange(); let p = !1; l.collapse(!0); try { l.setStart(a.startContainer, 0); const s = l.toString(), u = s.length > 0 ? s.charAt(s.length - 1) : ""; p = u !== "" && !/\s/.test(u); } catch (s) { console.debug( "insertIntoContentEditable: Error checking text before cursor:", s ); } let r = !1; e.collapse(!1); try { if (e.endContainer.nodeType === Node.TEXT_NODE) { const o = e.endContainer; e.setEnd(o, o.length); } else if (e.endContainer.nodeType === Node.ELEMENT_NODE) { const o = e.endContainer; o.childNodes.length > e.endOffset && e.setEnd(o, e.endOffset + 1); } const s = e.toString(), u = s.length > 0 ? s.charAt(0) : ""; r = u !== "" && !/\s/.test(u); } catch (s) { console.debug( "insertIntoContentEditable: Error checking text after cursor:", s ); } const g = (p ? " " : "") + c + (r ? " " : ""); try { a.deleteContents(); const s = document.createTextNode(g); a.insertNode(s), a.setStartAfter(s), a.setEndAfter(s), n?.removeAllRanges(), n?.addRange(a); } catch (s) { console.debug("insertIntoContentEditable: Error inserting text:", s), f(t), t.textContent = (t.textContent || "") + g; } } } export { v as injectDictateButton };