@overpunch/speechtype 1.0.11 → 1.1.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/dist/index.js CHANGED
@@ -1,136 +1,92 @@
1
- import { useEffect as x, forwardRef as C, useRef as E, useImperativeHandle as H } from "react";
2
- import { jsx as $ } from "react/jsx-runtime";
3
- const T = {
4
- word: "st-word"
5
- }, w = /* @__PURE__ */ new WeakMap();
6
- function M(t, e = {}) {
7
- if (typeof window > "u") return [];
8
- const r = window.scrollY, n = w.get(t), l = (n == null ? void 0 : n.originalHTML) ?? t.innerHTML;
9
- t.innerHTML = l;
10
- const f = (t.textContent ?? "").split(/(\s+)/);
11
- t.innerHTML = f.map((i) => !i || /^\s+$/.test(i) ? i : `<span class="${T.word}" aria-hidden="true">${i}</span>`).join("");
12
- const u = Array.from(t.querySelectorAll(`.${T.word}`)), o = e.transitionMs ?? 80;
13
- u.forEach((i) => {
14
- i.style.display = "inline", i.style.transition = [
15
- `font-variation-settings ${o}ms ease`,
16
- `letter-spacing ${o}ms ease`,
17
- `opacity ${o}ms ease`
18
- ].join(", ");
19
- });
20
- let c = t.querySelector("[data-st-live]");
21
- return c || (c = document.createElement("span"), c.setAttribute("data-st-live", ""), c.setAttribute("aria-live", "polite"), c.setAttribute("aria-atomic", "true"), c.style.cssText = "position:absolute;width:1px;height:1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0", t.appendChild(c)), w.set(t, { originalHTML: l, wordSpans: u, utterance: null, activeIndex: -1 }), requestAnimationFrame(() => {
22
- Math.abs(window.scrollY - r) > 2 && window.scrollTo({ top: r, behavior: "instant" });
23
- }), u;
24
- }
25
- function v(t, e, r = {}) {
26
- var c;
27
- if (typeof window > "u") return;
28
- const n = w.get(t);
29
- if (!n) return;
30
- const l = r.activeTracking ?? 0.06, s = r.activeWeight ?? 700, f = r.activeOpsz ?? 24, u = r.inactiveOpacity ?? 0.45;
31
- n.activeIndex = e;
32
- const o = t.querySelector("[data-st-live]");
33
- o && (o.textContent = e >= 0 ? ((c = n.wordSpans[e]) == null ? void 0 : c.textContent) ?? "" : ""), n.wordSpans.forEach((i, y) => {
34
- y === e ? (i.setAttribute("aria-current", "true"), i.style.fontVariationSettings = `"wght" ${s}, "opsz" ${f}`, i.style.letterSpacing = `${l}em`, i.style.opacity = "1") : (i.removeAttribute("aria-current"), i.style.fontVariationSettings = "", i.style.letterSpacing = "", i.style.opacity = e === -1 ? "1" : String(u));
35
- });
1
+ import { removeSpeechType as S, prepareSpeechType as k, applySpeechType as i } from "./core.js";
2
+ import { SPEECH_CLASSES as D, getCleanHTML as F, startSpeechType as G } from "./core.js";
3
+ import { useRef as s, useEffect as l, forwardRef as x, useImperativeHandle as H, Children as h, isValidElement as w } from "react";
4
+ import { jsx as L } from "react/jsx-runtime";
5
+ function N(y, u, e) {
6
+ const n = s(null), r = s(e);
7
+ r.current = e;
8
+ const a = JSON.stringify([e == null ? void 0 : e.activeTracking, e == null ? void 0 : e.activeWeight, e == null ? void 0 : e.activeOpsz, e == null ? void 0 : e.inactiveOpacity]);
9
+ l(() => {
10
+ const t = y.current, c = n.current;
11
+ if (!c || c.el !== t || c.transitionMs !== (e == null ? void 0 : e.transitionMs)) {
12
+ if (c && c.el !== t && S(c.el), n.current = null, !t) return;
13
+ k(t, r.current), n.current = { el: t, transitionMs: e == null ? void 0 : e.transitionMs }, i(t, u, r.current);
14
+ }
15
+ }), l(() => {
16
+ const t = y.current;
17
+ t && i(t, u, r.current);
18
+ }, [u, a]), l(() => () => {
19
+ n.current && S(n.current.el), n.current = null;
20
+ }, []);
36
21
  }
37
- function R(t, e = {}) {
38
- var g;
39
- if (typeof window > "u" || !("speechSynthesis" in window))
40
- return (g = e.onUnsupported) == null || g.call(e), () => {
41
- };
42
- const r = M(t, e), n = w.get(t);
43
- if (!n) return () => {
44
- };
45
- window.speechSynthesis.cancel();
46
- const l = r.map((a) => a.textContent ?? "").join(" "), s = new SpeechSynthesisUtterance(l);
47
- s.rate = e.rate ?? 0.9, s.pitch = e.pitch ?? 1, s.volume = e.volume ?? 1;
48
- const f = e.activeTracking ?? 0.06, u = e.activeWeight ?? 700, o = e.activeOpsz ?? 24, c = { ...e, activeTracking: f, activeWeight: u, activeOpsz: o };
49
- let i = 0;
50
- const y = r.map((a) => {
51
- var h;
52
- const d = i;
53
- return i += (((h = a.textContent) == null ? void 0 : h.length) ?? 0) + 1, d;
54
- });
55
- let p = !1;
56
- return s.onboundary = (a) => {
57
- if (p || a.name !== "word") return;
58
- const d = y.findIndex((h, m) => {
59
- const S = y[m + 1] ?? 1 / 0;
60
- return a.charIndex >= h && a.charIndex < S;
22
+ function z(y) {
23
+ const u = [], e = (n) => {
24
+ h.forEach(n, (r) => {
25
+ if (!(r == null || typeof r == "boolean")) {
26
+ if (typeof r == "string" || typeof r == "number") {
27
+ u.push(String(r));
28
+ return;
29
+ }
30
+ if (w(r)) {
31
+ const a = typeof r.type == "string" ? r.type : r.type.displayName ?? r.type.name ?? "C", t = r.props, c = Object.keys(t).filter((f) => f !== "children" && ["string", "number", "boolean"].includes(typeof t[f])).sort().map((f) => `${f}=${String(t[f])}`);
32
+ u.push(`<${a}${r.key != null ? "#" + r.key : ""} ${c.join(" ")}>`), e(t.children), u.push(`</${a}>`);
33
+ }
34
+ }
61
35
  });
62
- d !== -1 && v(t, d, c);
63
- }, s.onend = () => {
64
- p || (v(t, -1, c), n.utterance = null);
65
- }, s.onerror = (a) => {
66
- var d;
67
- a.error !== "interrupted" && ((d = e.onError) == null || d.call(e, a)), p || (v(t, -1, c), n.utterance = null);
68
- }, n.utterance = s, window.speechSynthesis.speak(s), () => {
69
- p || (p = !0, window.speechSynthesis.cancel(), v(t, -1, c), n.utterance = null);
70
36
  };
37
+ return e(y), u.join("\0");
71
38
  }
72
- function b(t) {
73
- const e = w.get(t);
74
- e && (typeof window < "u" && "speechSynthesis" in window && window.speechSynthesis.cancel(), t.innerHTML = e.originalHTML, w.delete(t));
75
- }
76
- function j(t) {
77
- const e = t.cloneNode(!0);
78
- return e.querySelectorAll(`.${T.word}`).forEach((r) => {
79
- const n = r.parentNode;
80
- if (n) {
81
- for (; r.firstChild; ) n.insertBefore(r.firstChild, r);
82
- n.removeChild(r);
83
- }
84
- }), e.innerHTML;
85
- }
86
- function A(t, e, r) {
87
- x(() => {
88
- const n = t.current;
89
- if (n)
90
- return M(n, r), () => b(n);
91
- }, [r == null ? void 0 : r.transitionMs]), x(() => {
92
- const n = t.current;
93
- n && v(n, e, r);
94
- }, [e]);
95
- }
96
- const k = C(
39
+ const _ = x(
97
40
  function({
98
- activeWordIndex: e,
99
- as: r = "p",
41
+ activeWordIndex: u,
42
+ as: e = "p",
100
43
  children: n,
101
- style: l,
102
- className: s,
44
+ style: r,
45
+ className: a,
103
46
  // Extract SpeechTypeOptions fields explicitly so they are NOT forwarded to the DOM
104
- activeTracking: f,
105
- activeWeight: u,
106
- activeOpsz: o,
107
- inactiveOpacity: c,
108
- transitionMs: i,
109
- rate: y,
110
- pitch: p,
111
- volume: g,
112
- onUnsupported: a,
113
- onError: d,
47
+ activeTracking: t,
48
+ activeWeight: c,
49
+ activeOpsz: f,
50
+ inactiveOpacity: T,
51
+ transitionMs: g,
52
+ rate: E,
53
+ pitch: $,
54
+ volume: v,
55
+ onUnsupported: C,
56
+ onError: b,
57
+ onEnd: M,
58
+ lang: m,
59
+ voice: j,
114
60
  // Remaining props (aria-*, data-*, role, lang, etc.) are forwarded to the DOM element
115
- ...h
116
- }, m) {
117
- const S = E(null);
118
- return H(m, () => S.current), A(S, e, {
119
- activeTracking: f,
120
- activeWeight: u,
121
- activeOpsz: o,
122
- inactiveOpacity: c,
123
- transitionMs: i
124
- }), /* @__PURE__ */ $(r, { ref: S, style: l, className: s, ...h, children: n });
61
+ ...O
62
+ }, R) {
63
+ const p = s(null);
64
+ return H(R, () => p.current), N(p, u, {
65
+ activeTracking: t,
66
+ activeWeight: c,
67
+ activeOpsz: f,
68
+ inactiveOpacity: T,
69
+ transitionMs: g,
70
+ rate: E,
71
+ pitch: $,
72
+ volume: v,
73
+ onUnsupported: C,
74
+ onError: b,
75
+ onEnd: M,
76
+ lang: m,
77
+ voice: j
78
+ }), // The library wraps the element's words in place, so React can't patch new children into it.
79
+ // When the children or the tag change, remount the element (key) and prepare the fresh content.
80
+ /* @__PURE__ */ L(e, { ref: p, style: r, className: a, lang: m, ...O, children: n }, `${typeof e == "string" ? e : "C"}|${z(n)}`);
125
81
  }
126
82
  );
127
83
  export {
128
- T as SPEECH_CLASSES,
129
- k as SpeechTypeText,
130
- v as applySpeechType,
131
- j as getCleanHTML,
132
- M as prepareSpeechType,
133
- b as removeSpeechType,
134
- R as startSpeechType,
135
- A as useSpeechType
84
+ D as SPEECH_CLASSES,
85
+ _ as SpeechTypeText,
86
+ i as applySpeechType,
87
+ F as getCleanHTML,
88
+ k as prepareSpeechType,
89
+ S as removeSpeechType,
90
+ G as startSpeechType,
91
+ N as useSpeechType
136
92
  };
@@ -1 +1 @@
1
- var SpeechType=(function(d){"use strict";const k={word:"st-word"},f=new WeakMap;function O(e,t={}){if(typeof window>"u")return[];const i=window.scrollY,n=f.get(e),p=(n==null?void 0:n.originalHTML)??e.innerHTML;e.innerHTML=p;const y=(e.textContent??"").split(/(\s+)/);e.innerHTML=y.map(s=>!s||/^\s+$/.test(s)?s:`<span class="${k.word}" aria-hidden="true">${s}</span>`).join("");const u=Array.from(e.querySelectorAll(`.${k.word}`)),o=t.transitionMs??80;u.forEach(s=>{s.style.display="inline",s.style.transition=[`font-variation-settings ${o}ms ease`,`letter-spacing ${o}ms ease`,`opacity ${o}ms ease`].join(", ")});let r=e.querySelector("[data-st-live]");return r||(r=document.createElement("span"),r.setAttribute("data-st-live",""),r.setAttribute("aria-live","polite"),r.setAttribute("aria-atomic","true"),r.style.cssText="position:absolute;width:1px;height:1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0",e.appendChild(r)),f.set(e,{originalHTML:p,wordSpans:u,utterance:null,activeIndex:-1}),requestAnimationFrame(()=>{Math.abs(window.scrollY-i)>2&&window.scrollTo({top:i,behavior:"instant"})}),u}function w(e,t,i={}){var r;if(typeof window>"u")return;const n=f.get(e);if(!n)return;const p=i.activeTracking??.06,a=i.activeWeight??700,y=i.activeOpsz??24,u=i.inactiveOpacity??.45;n.activeIndex=t;const o=e.querySelector("[data-st-live]");o&&(o.textContent=t>=0?((r=n.wordSpans[t])==null?void 0:r.textContent)??"":""),n.wordSpans.forEach((s,S)=>{S===t?(s.setAttribute("aria-current","true"),s.style.fontVariationSettings=`"wght" ${a}, "opsz" ${y}`,s.style.letterSpacing=`${p}em`,s.style.opacity="1"):(s.removeAttribute("aria-current"),s.style.fontVariationSettings="",s.style.letterSpacing="",s.style.opacity=t===-1?"1":String(u))})}function C(e,t={}){var A;if(typeof window>"u"||!("speechSynthesis"in window))return(A=t.onUnsupported)==null||A.call(t),()=>{};const i=O(e,t),n=f.get(e);if(!n)return()=>{};window.speechSynthesis.cancel();const p=i.map(c=>c.textContent??"").join(" "),a=new SpeechSynthesisUtterance(p);a.rate=t.rate??.9,a.pitch=t.pitch??1,a.volume=t.volume??1;const y=t.activeTracking??.06,u=t.activeWeight??700,o=t.activeOpsz??24,r={...t,activeTracking:y,activeWeight:u,activeOpsz:o};let s=0;const S=i.map(c=>{var m;const l=s;return s+=(((m=c.textContent)==null?void 0:m.length)??0)+1,l});let v=!1;return a.onboundary=c=>{if(v||c.name!=="word")return;const l=S.findIndex((m,F)=>{const P=S[F+1]??1/0;return c.charIndex>=m&&c.charIndex<P});l!==-1&&w(e,l,r)},a.onend=()=>{v||(w(e,-1,r),n.utterance=null)},a.onerror=c=>{var l;c.error!=="interrupted"&&((l=t.onError)==null||l.call(t,c)),v||(w(e,-1,r),n.utterance=null)},n.utterance=a,window.speechSynthesis.speak(a),()=>{v||(v=!0,window.speechSynthesis.cancel(),w(e,-1,r),n.utterance=null)}}function E(e){const t=f.get(e);t&&(typeof window<"u"&&"speechSynthesis"in window&&window.speechSynthesis.cancel(),e.innerHTML=t.originalHTML,f.delete(e))}const L="data-speechtype",H="false",h=new WeakMap;function b(e){const t=e.dataset,i={};if(t.stTracking!==void 0){const n=parseFloat(t.stTracking);isNaN(n)||(i.activeTracking=n)}if(t.stWeight!==void 0){const n=parseFloat(t.stWeight);isNaN(n)||(i.activeWeight=n)}if(t.stOpsz!==void 0){const n=parseFloat(t.stOpsz);isNaN(n)||(i.activeOpsz=n)}if(t.stInactiveOpacity!==void 0){const n=parseFloat(t.stInactiveOpacity);isNaN(n)||(i.inactiveOpacity=n)}if(t.stTransition!==void 0){const n=parseFloat(t.stTransition);isNaN(n)||(i.transitionMs=n)}if(t.stRate!==void 0){const n=parseFloat(t.stRate);isNaN(n)||(i.rate=n)}if(t.stPitch!==void 0){const n=parseFloat(t.stPitch);isNaN(n)||(i.pitch=n)}if(t.stVolume!==void 0){const n=parseFloat(t.stVolume);isNaN(n)||(i.volume=n)}return i.onUnsupported=()=>{console.warn("SpeechType: this browser does not support the Web Speech API — nothing will be spoken.")},i}function T(e){const t=h.get(e);if(t){if(t.stop){N(e);return}t.stop=C(e,b(e))}}function N(e){const t=h.get(e);!t||!t.stop||(t.stop(),t.stop=null)}function $(e){N(e),T(e)}function I(e){M(e),O(e,b(e));let t=null;e.dataset.stClick!==H&&(t=()=>T(e),e.addEventListener("click",t),e.style.cursor="pointer"),h.set(e,{stop:null,clickHandler:t})}function M(e){const t=h.get(e);t&&(t.stop&&t.stop(),t.clickHandler&&(e.removeEventListener("click",t.clickHandler),e.style.cursor=""),E(e),h.delete(e))}function g(e=document){e.querySelectorAll(`[${L}]`).forEach(I)}function W(){const e=()=>{var t;(t=document.fonts)!=null&&t.ready?document.fonts.ready.then(()=>g()).catch(()=>g()):g()};document.readyState==="loading"?document.addEventListener("DOMContentLoaded",e,{once:!0}):e()}return W(),d.destroy=M,d.init=g,d.restart=$,d.speak=T,d.stop=N,Object.defineProperty(d,Symbol.toStringTag,{value:"Module"}),d})({});
1
+ var SpeechType=(function(A){"use strict";const K={word:"st-word"},j=new Set(["SCRIPT","STYLE","TEXTAREA","NOSCRIPT","TEMPLATE","SVG","MATH","SELECT","OPTION","CANVAS","IFRAME","OBJECT","VIDEO","AUDIO","INPUT","BUTTON"]),x=new Set(["BR","HR","P","DIV","LI","UL","OL","DL","DT","DD","H1","H2","H3","H4","H5","H6","BLOCKQUOTE","PRE","SECTION","ARTICLE","ASIDE","HEADER","FOOTER","NAV","FIGURE","FIGCAPTION","TABLE","TR","TD","TH","CAPTION","IMG","ADDRESS","MAIN","DETAILS","SUMMARY"]),q=/[฀-໿က-႟ក-៿぀-ヿ㐀-䶿一-鿿豈-﫿]/,Y=80,J=1e4,X=1e3,$=typeof Intl<"u"&&"Segmenter"in Intl?new Intl.Segmenter(void 0,{granularity:"word"}):null,V=new Set;function F(t){V.has(t)||(V.add(t),console.warn(t))}function T(t,e,n,r,i){return t===void 0?e:typeof t=="number"&&Number.isFinite(t)?Math.min(r,Math.max(n,t)):(F(`[speechType] ${i} must be a finite number; got ${String(t)}, using ${e}`),e)}function Q(){var t,e;return typeof window<"u"&&!!((e=(t=window.matchMedia)==null?void 0:t.call(window,"(prefers-reduced-motion: reduce)"))!=null&&e.matches)}const v=new WeakMap;function Z(t){if(t.getAttribute("aria-hidden")==="true"||t.hasAttribute("hidden"))return!0;if(!t.isConnected)return!1;const e=getComputedStyle(t);return e.display==="none"||e.visibility==="hidden"}function U(t){for(const e of t.wrapped){const n=e.produced.find(r=>r.parentNode);n!=null&&n.parentNode&&n.parentNode.insertBefore(e.original,n),e.produced.forEach(r=>{var i;return(i=r.parentNode)==null?void 0:i.removeChild(r)})}}function _(t,e={}){var E;if(typeof window>"u"||!t)return[];const n=v.get(t);n&&((E=n.run)==null||E.stop(),U(n),v.delete(t));const r=t.innerHTML,i=[],s=[],a=[],o=[];let c="",p=-1,g=!0;const h=u=>{Array.from(u.childNodes).forEach(d=>{if(d.nodeType===Node.TEXT_NODE){I(d);return}if(d.nodeType!==Node.ELEMENT_NODE)return;const f=d,l=f.nodeName.toUpperCase();x.has(l)&&(g=!0),!(j.has(l)||f.isContentEditable||Z(f))&&(h(f),x.has(l)&&(g=!0))})},I=u=>{const d=u.data;if(!d||!u.parentNode)return;if(!/\S/.test(d)){g=!0;return}const f=[];for(const m of d.split(/(\s+)/)){if(!m)continue;if(/^\s+$/.test(m)){f.push(document.createTextNode(m)),g=!0;continue}($&&q.test(m)?Array.from($.segment(m),b=>b.segment):[m]).forEach((b,S)=>{(g||S>0)&&(c&&(c+=" "),p++,o[p]=c.length),g=!1;const w=document.createElement("span");w.className=K.word,w.textContent=b,f.push(w),s.push(w),a.push(p),c+=b})}const l=document.createDocumentFragment();f.forEach(m=>l.appendChild(m)),u.parentNode.replaceChild(l,u),i.push({original:u,produced:f})};h(t);const y=T(e.transitionMs,80,0,1e4,"transitionMs");return y>0&&!Q()&&s.forEach(u=>{u.style.transition=`font-variation-settings ${y}ms ease, letter-spacing ${y}ms ease, opacity ${y}ms ease`}),v.set(t,{originalHTML:r,wrapped:i,wordSpans:s,wordOf:a,text:c,wordStart:o,run:null,activeIndex:-1}),s}function ee(t){if(!t||t==="normal")return[];const e=[];for(const n of t.matchAll(/["']([^"']{4})["']\s+(-?[\d.]+(?:e[+-]?\d+)?)/gi))e.push([n[1],parseFloat(n[2])]);return e}function te(t,e){var E;const n=t.parentElement??t,r=getComputedStyle(n),i=ee(((E=r.getPropertyValue)==null?void 0:E.call(r,"font-variation-settings"))||r.fontVariationSettings||""),s=u=>{var d;return(d=i.find(([f])=>f===u))==null?void 0:d[1]},a=parseFloat(r.fontSize)||16,o=s("wght")??(parseFloat(r.fontWeight)||400),c=s("opsz")??a,p=T(e.activeWeight,Math.min(1e3,o+300),1,1e3,"activeWeight"),g=T(e.activeOpsz,c*1.5,1,1e3,"activeOpsz"),h=T(e.activeTracking,.06,-1,1,"activeTracking"),I=i.filter(([u])=>u!=="wght"&&u!=="opsz").map(([u,d])=>`"${u}" ${d}`),y=parseFloat(r.letterSpacing)||0;return{fvs:[...I,`"wght" ${+p.toFixed(1)}`,`"opsz" ${+g.toFixed(1)}`].join(", "),ls:y?`calc(${y}px + ${h}em)`:`${h}em`}}function M(t,e,n={}){if(typeof window>"u")return;const r=v.get(t);if(!r)return;const i=Number.isInteger(e)&&e>=0&&e<r.wordSpans.length?e:-1,s=T(n.inactiveOpacity,.45,0,1,"inactiveOpacity"),a=i>=0?r.wordOf[i]:-1;r.activeIndex=i,r.wordSpans.forEach((o,c)=>{if(a>=0&&r.wordOf[c]===a){const p=te(o,n);o.setAttribute("aria-current","true"),o.style.fontVariationSettings=p.fvs,o.style.letterSpacing=p.ls,o.style.opacity="1"}else o.removeAttribute("aria-current"),o.style.fontVariationSettings="",o.style.letterSpacing="",o.style.opacity=a===-1?"1":String(s)})}let O=null;function ne(t,e){var i,s;const n=((s=(i=window.speechSynthesis).getVoices)==null?void 0:s.call(i))??[];if(e&&typeof e=="object")return e;if(typeof e=="string"){const a=n.find(o=>o.name===e||o.voiceURI===e);if(a)return a;F(`[speechType] no voice named ${JSON.stringify(e)}; using the language's default`)}if(!t)return null;const r=t.toLowerCase();return n.find(a=>a.lang.toLowerCase()===r)??n.find(a=>a.lang.toLowerCase().split("-")[0]===r.split("-")[0])??null}function re(t,e={}){var P,b;if(typeof window>"u"||!t||!("speechSynthesis"in window)||typeof SpeechSynthesisUtterance>"u")return(P=e.onUnsupported)==null||P.call(e),()=>{};const n=T(e.rate,.9,.1,10,"rate"),r=T(e.pitch,1,0,2,"pitch"),i=T(e.volume,1,0,1,"volume"),s=window.speechSynthesis,a=s.speaking||s.pending;_(t,e);const o=v.get(t);if(!o||!o.text)return()=>{};const c=new SpeechSynthesisUtterance(o.text);c.rate=n,c.pitch=r,c.volume=i;const p=e.lang??((b=t.closest("[lang]"))==null?void 0:b.lang)??document.documentElement.lang??"";p&&(c.lang=p);const g=ne(p,e.voice);g&&(c.voice=g);let h=!1,I=!1,y=null,E=null,u=null,d=!1;const f=()=>{var S;h||(h=!0,y&&clearInterval(y),E&&clearInterval(E),u&&clearTimeout(u),O===l&&(O=null),o.run===l&&(o.run=null),M(t,-1,e),(S=e.onEnd)==null||S.call(e))};c.onstart=()=>{d=!0},c.onboundary=S=>{if(h||S.name!=="word")return;d=!0;let w=-1;for(let L=0;L<o.wordStart.length&&o.wordStart[L]<=S.charIndex;L++)w=L;const G=o.wordOf.indexOf(w);G!==-1&&M(t,G,e)},c.onend=()=>f(),c.onerror=S=>{var w;S.error!=="interrupted"&&!(I&&S.error==="canceled")&&((w=e.onError)==null||w.call(e,S)),f()};const l={utterance:c,stop:()=>{h||(O===l&&(I=!0,s.cancel()),f())}};O?O.stop():a&&s.cancel(),O=l,o.run=l;const m=()=>{u=null,!h&&(s.speak(c),/Chrome\//.test(navigator.userAgent)&&(y=setInterval(()=>{s.speaking&&!s.paused&&(s.pause(),s.resume())},J)),E=setInterval(()=>{d&&!s.speaking&&!s.pending&&f()},X))};return a?u=setTimeout(m,Y):m(),l.stop}function ie(t){var n;const e=v.get(t);e&&((n=e.run)==null||n.stop(),M(t,-1),U(e),v.delete(t))}const H="data-speechtype",se="false",k=new WeakMap;function z(t){const e=t.dataset,n={},r=i=>{if(i===void 0)return;const s=parseFloat(i);return Number.isFinite(s)?s:void 0};n.activeTracking=r(e.stTracking),n.activeWeight=r(e.stWeight),n.activeOpsz=r(e.stOpsz),n.inactiveOpacity=r(e.stInactiveOpacity),n.transitionMs=r(e.stTransition),n.rate=r(e.stRate),n.pitch=r(e.stPitch),n.volume=r(e.stVolume),e.stVoice&&(n.voice=e.stVoice);for(const i of Object.keys(n))n[i]===void 0&&delete n[i];return n.onUnsupported=()=>{console.warn("SpeechType: this browser does not support the Web Speech API — nothing will be spoken.")},n}function D(t,e){e.speaking=!1,e.stop=null,e.keyHandler&&t.setAttribute("aria-pressed","false")}function C(t){const e=k.get(t);if(!e)return;if(e.speaking){R(t);return}const n=z(t);let r=null;n.onEnd=()=>{(e.stop===r||r===null)&&D(t,e)},e.speaking=!0,e.keyHandler&&t.setAttribute("aria-pressed","true"),r=re(t,n),e.speaking&&(e.stop=r),typeof speechSynthesis>"u"&&D(t,e)}function R(t){const e=k.get(t);!e||!e.stop||(e.stop(),D(t,e))}function oe(t){R(t),C(t)}function W(t){B(t),_(t,z(t));let e=null,n=null;const r={tabIndex:t.getAttribute("tabindex"),role:t.getAttribute("role"),pressed:t.getAttribute("aria-pressed")};t.dataset.stClick!==se&&(e=i=>{var s,a;(a=(s=i.target)==null?void 0:s.closest)!=null&&a.call(s,"a, button, input, select, textarea, label, summary")||C(t)},t.addEventListener("click",e),t.style.cursor="pointer",n=i=>{i.target!==t||i.key!=="Enter"&&i.key!==" "||(i.preventDefault(),C(t))},t.addEventListener("keydown",n),r.tabIndex===null&&t.setAttribute("tabindex","0"),!r.role&&!t.querySelector("a, button, input, select, textarea")&&t.setAttribute("role","button"),t.setAttribute("aria-pressed","false")),k.set(t,{stop:null,speaking:!1,clickHandler:e,keyHandler:n,saved:r})}function B(t){const e=k.get(t);if(e){if(e.stop&&e.stop(),e.clickHandler&&(t.removeEventListener("click",e.clickHandler),t.style.cursor=""),e.keyHandler){t.removeEventListener("keydown",e.keyHandler);const n=(r,i)=>{i===null?t.removeAttribute(r):t.setAttribute(r,i)};n("tabindex",e.saved.tabIndex),n("role",e.saved.role),n("aria-pressed",e.saved.pressed)}ie(t),k.delete(t)}}function N(t=document){t.querySelectorAll(`[${H}]`).forEach(W)}function ae(){const t=()=>{var e;(e=document.fonts)!=null&&e.ready?document.fonts.ready.then(()=>N()).catch(()=>N()):N(),typeof MutationObserver<"u"&&document.body&&new MutationObserver(n=>{for(const r of n)r.addedNodes.forEach(i=>{if(!(i instanceof HTMLElement)||!i.isConnected)return;const s=i.matches(`[${H}]`)?[i]:[];i.querySelectorAll(`[${H}]`).forEach(a=>s.push(a));for(const a of s)k.has(a)||W(a)})}).observe(document.body,{childList:!0,subtree:!0})};document.readyState==="loading"?document.addEventListener("DOMContentLoaded",t,{once:!0}):t()}return ae(),A.destroy=B,A.init=N,A.restart=oe,A.speak=C,A.stop=R,Object.defineProperty(A,Symbol.toStringTag,{value:"Module"}),A})({});
package/package.json CHANGED
@@ -1,91 +1,96 @@
1
- {
2
- "name": "@overpunch/speechtype",
3
- "version": "1.0.11",
4
- "description": "Typography that follows your voice — per-word typographic emphasis synced to Web Speech API boundary events",
5
- "type": "module",
6
- "main": "dist/index.cjs",
7
- "module": "dist/index.js",
8
- "types": "dist/index.d.ts",
9
- "exports": {
10
- ".": {
11
- "types": "./dist/index.d.ts",
12
- "import": "./dist/index.js",
13
- "require": "./dist/index.cjs"
14
- }
15
- },
16
- "files": [
17
- "dist"
18
- ],
19
- "scripts": {
20
- "build": "vite build",
21
- "build:webflow": "vite build --config vite.webflow.config.ts",
22
- "test": "vitest run",
23
- "typecheck": "tsc --noEmit",
24
- "prepublishOnly": "npm run test && npm run build && npm run build:webflow"
25
- },
26
- "peerDependencies": {
27
- "react": ">=17",
28
- "react-dom": ">=17"
29
- },
30
- "peerDependenciesMeta": {
31
- "react": {
32
- "optional": true
33
- },
34
- "react-dom": {
35
- "optional": true
36
- }
37
- },
38
- "devDependencies": {
39
- "@testing-library/react": "^16.3.2",
40
- "@testing-library/user-event": "^14.6.1",
41
- "@types/react": "^19.0.0",
42
- "@vitejs/plugin-react": "^4.0.0",
43
- "happy-dom": "^20.10.6",
44
- "next": "16.2.2",
45
- "react": "^19.0.0",
46
- "typescript": "^5.0.0",
47
- "vite": "^6.0.0",
48
- "vite-plugin-dts": "^4.0.0",
49
- "vitest": "^3.0.0"
50
- },
51
- "keywords": [
52
- "typography",
53
- "web-typography",
54
- "frontend",
55
- "liiift-studio",
56
- "speech-synthesis",
57
- "web-speech-api",
58
- "tts",
59
- "text-to-speech",
60
- "voice",
61
- "karaoke",
62
- "word-highlight",
63
- "accessibility",
64
- "a11y",
65
- "language-learning",
66
- "teleprompter",
67
- "variable-font",
68
- "font-variation-settings",
69
- "wght",
70
- "opsz",
71
- "letter-spacing",
72
- "zero-dependencies",
73
- "react",
74
- "typescript",
75
- "css"
76
- ],
77
- "author": "Quinn Keaveney <quinn@liiift.studio>",
78
- "license": "MIT",
79
- "homepage": "https://speechtype.vercel.app",
80
- "repository": {
81
- "type": "git",
82
- "url": "git+https://github.com/over-punch/speechType.git"
83
- },
84
- "bugs": {
85
- "url": "https://github.com/over-punch/speechType/issues"
86
- },
87
- "sideEffects": false,
88
- "publishConfig": {
89
- "access": "public"
90
- }
91
- }
1
+ {
2
+ "name": "@overpunch/speechtype",
3
+ "version": "1.1.0",
4
+ "description": "Typography that follows your voice — per-word typographic emphasis synced to Web Speech API boundary events",
5
+ "type": "module",
6
+ "main": "dist/index.cjs",
7
+ "module": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ },
15
+ "./core": {
16
+ "types": "./dist/core.d.ts",
17
+ "import": "./dist/core.js",
18
+ "require": "./dist/core.cjs"
19
+ }
20
+ },
21
+ "files": [
22
+ "dist"
23
+ ],
24
+ "scripts": {
25
+ "build": "vite build",
26
+ "build:webflow": "vite build --config vite.webflow.config.ts",
27
+ "test": "vitest run",
28
+ "typecheck": "tsc --noEmit",
29
+ "prepublishOnly": "npm run test && npm run build && npm run build:webflow"
30
+ },
31
+ "peerDependencies": {
32
+ "react": ">=17",
33
+ "react-dom": ">=17"
34
+ },
35
+ "peerDependenciesMeta": {
36
+ "react": {
37
+ "optional": true
38
+ },
39
+ "react-dom": {
40
+ "optional": true
41
+ }
42
+ },
43
+ "devDependencies": {
44
+ "@testing-library/react": "^16.3.2",
45
+ "@testing-library/user-event": "^14.6.1",
46
+ "@types/react": "^19.0.0",
47
+ "@vitejs/plugin-react": "^4.0.0",
48
+ "happy-dom": "^20.10.6",
49
+ "next": "16.2.2",
50
+ "react": "^19.0.0",
51
+ "typescript": "^5.0.0",
52
+ "vite": "^6.0.0",
53
+ "vite-plugin-dts": "^4.0.0",
54
+ "vitest": "^3.0.0"
55
+ },
56
+ "keywords": [
57
+ "typography",
58
+ "web-typography",
59
+ "frontend",
60
+ "liiift-studio",
61
+ "speech-synthesis",
62
+ "web-speech-api",
63
+ "tts",
64
+ "text-to-speech",
65
+ "voice",
66
+ "karaoke",
67
+ "word-highlight",
68
+ "accessibility",
69
+ "a11y",
70
+ "language-learning",
71
+ "teleprompter",
72
+ "variable-font",
73
+ "font-variation-settings",
74
+ "wght",
75
+ "opsz",
76
+ "letter-spacing",
77
+ "zero-dependencies",
78
+ "react",
79
+ "typescript",
80
+ "css"
81
+ ],
82
+ "author": "Quinn Keaveney <quinn@overpunch.ca>",
83
+ "license": "MIT",
84
+ "homepage": "https://speechtype.vercel.app",
85
+ "repository": {
86
+ "type": "git",
87
+ "url": "git+https://github.com/over-punch/speechType.git"
88
+ },
89
+ "bugs": {
90
+ "url": "https://github.com/over-punch/speechType/issues"
91
+ },
92
+ "sideEffects": false,
93
+ "publishConfig": {
94
+ "access": "public"
95
+ }
96
+ }