@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/README.md +271 -258
- package/dist/core.cjs +1 -0
- package/dist/core.d.ts +126 -0
- package/dist/core.js +196 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +34 -23
- package/dist/index.js +80 -124
- package/dist/speechtype.webflow.min.js +1 -1
- package/package.json +96 -91
package/dist/core.d.ts
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Emphasise the word containing the span at activeIndex (every span of a word split by markup); dim the
|
|
3
|
+
* others. Pass -1 to reset all words to neutral. The emphasis is a heavier weight, larger optical size and
|
|
4
|
+
* wider tracking around the text's own values (explicit options are absolute).
|
|
5
|
+
*
|
|
6
|
+
* @param el - Element previously prepared by prepareSpeechType
|
|
7
|
+
* @param activeIndex - Index of the word span to emphasise, -1 for none
|
|
8
|
+
* @param options - SpeechTypeOptions (merged with defaults)
|
|
9
|
+
*/
|
|
10
|
+
export declare function applySpeechType(el: HTMLElement, activeIndex: number, options?: SpeechTypeOptions): void;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The element's markup without speechType's word spans. Exact for a prepared element; for any other
|
|
14
|
+
* element, unwraps .st-word spans.
|
|
15
|
+
*
|
|
16
|
+
* @param el - Element to read clean HTML from
|
|
17
|
+
*/
|
|
18
|
+
export declare function getCleanHTML(el: HTMLElement): string;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Wrap each visible word of el in a <span class="st-word">, in place: the element's markup, ids,
|
|
22
|
+
* listeners, form values and line breaks are kept, and hidden text, styles, scripts, form fields and SVG
|
|
23
|
+
* are left alone (and not spoken). The text stays readable to screen readers. Idempotent — calling again
|
|
24
|
+
* re-wraps from the original. Returns the word spans in document order.
|
|
25
|
+
*
|
|
26
|
+
* @param el - Element whose text will be wrapped
|
|
27
|
+
* @param options - SpeechTypeOptions (transitionMs is used)
|
|
28
|
+
*/
|
|
29
|
+
export declare function prepareSpeechType(el: HTMLElement, options?: SpeechTypeOptions): HTMLElement[];
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Stop this element's speech (only if it is speaking), put the original text nodes back and delete the
|
|
33
|
+
* saved state. No-op if prepareSpeechType was never called.
|
|
34
|
+
*
|
|
35
|
+
* @param el - The element previously prepared by prepareSpeechType
|
|
36
|
+
*/
|
|
37
|
+
export declare function removeSpeechType(el: HTMLElement): void;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* CSS class names injected by speechType.
|
|
41
|
+
* Only `word` ('st-word') is used — there are no st-active or st-inactive classes.
|
|
42
|
+
* Emphasis is applied via inline styles (fontVariationSettings, letterSpacing, opacity),
|
|
43
|
+
* not via class toggles.
|
|
44
|
+
*/
|
|
45
|
+
export declare const SPEECH_CLASSES: {
|
|
46
|
+
readonly word: "st-word";
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Options controlling how speechType emphasises spoken words.
|
|
51
|
+
*
|
|
52
|
+
* @remarks
|
|
53
|
+
* Visual options (activeTracking, activeWeight, activeOpsz, inactiveOpacity, transitionMs)
|
|
54
|
+
* are used by prepareSpeechType and applySpeechType.
|
|
55
|
+
* Speech options (rate, pitch, volume) are only used by startSpeechType and are ignored
|
|
56
|
+
* by applySpeechType, useSpeechType, and SpeechTypeText.
|
|
57
|
+
*
|
|
58
|
+
* activeWeight and activeOpsz are written directly into font-variation-settings. Ensure
|
|
59
|
+
* the values are within the wght/opsz axis ranges supported by your font — out-of-range
|
|
60
|
+
* values may cause some engines to silently ignore the entire declaration.
|
|
61
|
+
*
|
|
62
|
+
* inactiveOpacity: ensure the resulting contrast ratio remains at least 4.5:1 against
|
|
63
|
+
* your background colour to meet WCAG AA. The default of 0.45 may fail this threshold
|
|
64
|
+
* depending on your foreground/background pairing.
|
|
65
|
+
*/
|
|
66
|
+
export declare interface SpeechTypeOptions {
|
|
67
|
+
/** Letter-spacing on the active (currently spoken) word in em. Default: 0.06 */
|
|
68
|
+
activeTracking?: number;
|
|
69
|
+
/**
|
|
70
|
+
* wght axis value on the active word. Default: 700.
|
|
71
|
+
* Must be within the font's supported wght axis range (e.g. 100–900 for most variable fonts).
|
|
72
|
+
*/
|
|
73
|
+
activeWeight?: number;
|
|
74
|
+
/**
|
|
75
|
+
* opsz axis value on the active word. Default: 24.
|
|
76
|
+
* Must be within the font's supported opsz axis range (e.g. 6–72 for many variable fonts).
|
|
77
|
+
*/
|
|
78
|
+
activeOpsz?: number;
|
|
79
|
+
/**
|
|
80
|
+
* Opacity of inactive (not currently spoken) words. Default: 0.45.
|
|
81
|
+
* Values below ~0.5 may reduce contrast below WCAG AA (4.5:1) depending on your colours.
|
|
82
|
+
* Minimum recommended value: 0.3.
|
|
83
|
+
*/
|
|
84
|
+
inactiveOpacity?: number;
|
|
85
|
+
/** CSS transition duration in ms for style changes. Default: 80 */
|
|
86
|
+
transitionMs?: number;
|
|
87
|
+
/** Speech rate (0.1–10). Used only by startSpeechType. Default: 0.9 */
|
|
88
|
+
rate?: number;
|
|
89
|
+
/** Speech pitch (0–2). Used only by startSpeechType. Default: 1 */
|
|
90
|
+
pitch?: number;
|
|
91
|
+
/** Speech volume (0–1). Used only by startSpeechType. Default: 1 */
|
|
92
|
+
volume?: number;
|
|
93
|
+
/**
|
|
94
|
+
* Called when speech synthesis is unavailable in the current browser.
|
|
95
|
+
* Used only by startSpeechType.
|
|
96
|
+
*/
|
|
97
|
+
/**
|
|
98
|
+
* Language of the speech (BCP 47, e.g. 'ja'). Default: the element's own `lang` (nearest ancestor),
|
|
99
|
+
* else the document's. A voice for that language is chosen when the browser has one.
|
|
100
|
+
*/
|
|
101
|
+
lang?: string;
|
|
102
|
+
/** Voice to use: a SpeechSynthesisVoice, or a voice name / voiceURI. Default: the language's voice. */
|
|
103
|
+
voice?: SpeechSynthesisVoice | string;
|
|
104
|
+
/** Called when a run ends: speech finished, stopped, cancelled by a newer run, or failed (after onError). */
|
|
105
|
+
onEnd?: () => void;
|
|
106
|
+
onUnsupported?: () => void;
|
|
107
|
+
/**
|
|
108
|
+
* Called when a real speech error occurs (any error code other than "interrupted",
|
|
109
|
+
* which is a normal cancellation). Receives the SpeechSynthesisErrorEvent.
|
|
110
|
+
* Used only by startSpeechType.
|
|
111
|
+
*/
|
|
112
|
+
onError?: (event: SpeechSynthesisErrorEvent) => void;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Speak el's text, emphasising each word as the Web Speech API reaches it. Calls prepareSpeechType
|
|
117
|
+
* first. Earlier speech started by speechType is stopped (other speech on the page is left alone unless
|
|
118
|
+
* this run has to take over the speech engine). Returns a stop() function that stops this run and resets
|
|
119
|
+
* the emphasis; the words stay wrapped until removeSpeechType.
|
|
120
|
+
*
|
|
121
|
+
* @param el - Element to speak and highlight
|
|
122
|
+
* @param options - SpeechTypeOptions (merged with defaults)
|
|
123
|
+
*/
|
|
124
|
+
export declare function startSpeechType(el: HTMLElement, options?: SpeechTypeOptions): () => void;
|
|
125
|
+
|
|
126
|
+
export { }
|
package/dist/core.js
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
const x = {
|
|
2
|
+
word: "st-word"
|
|
3
|
+
}, b = /* @__PURE__ */ new Set(["SCRIPT", "STYLE", "TEXTAREA", "NOSCRIPT", "TEMPLATE", "SVG", "MATH", "SELECT", "OPTION", "CANVAS", "IFRAME", "OBJECT", "VIDEO", "AUDIO", "INPUT", "BUTTON"]), R = /* @__PURE__ */ 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"]), k = /[-က-႟ក--ヿ㐀-䶿一-鿿豈-]/, U = 80, F = 1e4, V = 1e3, D = typeof Intl < "u" && "Segmenter" in Intl ? new Intl.Segmenter(void 0, { granularity: "word" }) : null, H = /* @__PURE__ */ new Set();
|
|
4
|
+
function P(t) {
|
|
5
|
+
H.has(t) || (H.add(t), console.warn(t));
|
|
6
|
+
}
|
|
7
|
+
function y(t, e, r, n, s) {
|
|
8
|
+
return t === void 0 ? e : typeof t == "number" && Number.isFinite(t) ? Math.min(n, Math.max(r, t)) : (P(`[speechType] ${s} must be a finite number; got ${String(t)}, using ${e}`), e);
|
|
9
|
+
}
|
|
10
|
+
function _() {
|
|
11
|
+
var t, e;
|
|
12
|
+
return typeof window < "u" && !!((e = (t = window.matchMedia) == null ? void 0 : t.call(window, "(prefers-reduced-motion: reduce)")) != null && e.matches);
|
|
13
|
+
}
|
|
14
|
+
const A = /* @__PURE__ */ new WeakMap();
|
|
15
|
+
function z(t) {
|
|
16
|
+
if (t.getAttribute("aria-hidden") === "true" || t.hasAttribute("hidden")) return !0;
|
|
17
|
+
if (!t.isConnected) return !1;
|
|
18
|
+
const e = getComputedStyle(t);
|
|
19
|
+
return e.display === "none" || e.visibility === "hidden";
|
|
20
|
+
}
|
|
21
|
+
function $(t) {
|
|
22
|
+
for (const e of t.wrapped) {
|
|
23
|
+
const r = e.produced.find((n) => n.parentNode);
|
|
24
|
+
r != null && r.parentNode && r.parentNode.insertBefore(e.original, r), e.produced.forEach((n) => {
|
|
25
|
+
var s;
|
|
26
|
+
return (s = n.parentNode) == null ? void 0 : s.removeChild(n);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function B(t, e = {}) {
|
|
31
|
+
var T;
|
|
32
|
+
if (typeof window > "u" || !t) return [];
|
|
33
|
+
const r = A.get(t);
|
|
34
|
+
r && ((T = r.run) == null || T.stop(), $(r), A.delete(t));
|
|
35
|
+
const n = t.innerHTML, s = [], o = [], u = [], i = [];
|
|
36
|
+
let a = "", p = -1, g = !0;
|
|
37
|
+
const h = (c) => {
|
|
38
|
+
Array.from(c.childNodes).forEach((f) => {
|
|
39
|
+
if (f.nodeType === Node.TEXT_NODE) {
|
|
40
|
+
I(f);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (f.nodeType !== Node.ELEMENT_NODE) return;
|
|
44
|
+
const d = f, l = d.nodeName.toUpperCase();
|
|
45
|
+
R.has(l) && (g = !0), !(b.has(l) || d.isContentEditable || z(d)) && (h(d), R.has(l) && (g = !0));
|
|
46
|
+
});
|
|
47
|
+
}, I = (c) => {
|
|
48
|
+
const f = c.data;
|
|
49
|
+
if (!f || !c.parentNode) return;
|
|
50
|
+
if (!/\S/.test(f)) {
|
|
51
|
+
g = !0;
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const d = [];
|
|
55
|
+
for (const w of f.split(/(\s+)/)) {
|
|
56
|
+
if (!w) continue;
|
|
57
|
+
if (/^\s+$/.test(w)) {
|
|
58
|
+
d.push(document.createTextNode(w)), g = !0;
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
(D && k.test(w) ? Array.from(D.segment(w), (C) => C.segment) : [w]).forEach((C, m) => {
|
|
62
|
+
(g || m > 0) && (a && (a += " "), p++, i[p] = a.length), g = !1;
|
|
63
|
+
const E = document.createElement("span");
|
|
64
|
+
E.className = x.word, E.textContent = C, d.push(E), o.push(E), u.push(p), a += C;
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
const l = document.createDocumentFragment();
|
|
68
|
+
d.forEach((w) => l.appendChild(w)), c.parentNode.replaceChild(l, c), s.push({ original: c, produced: d });
|
|
69
|
+
};
|
|
70
|
+
h(t);
|
|
71
|
+
const S = y(e.transitionMs, 80, 0, 1e4, "transitionMs");
|
|
72
|
+
return S > 0 && !_() && o.forEach((c) => {
|
|
73
|
+
c.style.transition = `font-variation-settings ${S}ms ease, letter-spacing ${S}ms ease, opacity ${S}ms ease`;
|
|
74
|
+
}), A.set(t, { originalHTML: n, wrapped: s, wordSpans: o, wordOf: u, text: a, wordStart: i, run: null, activeIndex: -1 }), o;
|
|
75
|
+
}
|
|
76
|
+
function G(t) {
|
|
77
|
+
if (!t || t === "normal") return [];
|
|
78
|
+
const e = [];
|
|
79
|
+
for (const r of t.matchAll(/["']([^"']{4})["']\s+(-?[\d.]+(?:e[+-]?\d+)?)/gi)) e.push([r[1], parseFloat(r[2])]);
|
|
80
|
+
return e;
|
|
81
|
+
}
|
|
82
|
+
function W(t, e) {
|
|
83
|
+
var T;
|
|
84
|
+
const r = t.parentElement ?? t, n = getComputedStyle(r), s = G(((T = n.getPropertyValue) == null ? void 0 : T.call(n, "font-variation-settings")) || n.fontVariationSettings || ""), o = (c) => {
|
|
85
|
+
var f;
|
|
86
|
+
return (f = s.find(([d]) => d === c)) == null ? void 0 : f[1];
|
|
87
|
+
}, u = parseFloat(n.fontSize) || 16, i = o("wght") ?? (parseFloat(n.fontWeight) || 400), a = o("opsz") ?? u, p = y(e.activeWeight, Math.min(1e3, i + 300), 1, 1e3, "activeWeight"), g = y(e.activeOpsz, a * 1.5, 1, 1e3, "activeOpsz"), h = y(e.activeTracking, 0.06, -1, 1, "activeTracking"), I = s.filter(([c]) => c !== "wght" && c !== "opsz").map(([c, f]) => `"${c}" ${f}`), S = parseFloat(n.letterSpacing) || 0;
|
|
88
|
+
return {
|
|
89
|
+
fvs: [...I, `"wght" ${+p.toFixed(1)}`, `"opsz" ${+g.toFixed(1)}`].join(", "),
|
|
90
|
+
// Added to the text's own letter-spacing.
|
|
91
|
+
ls: S ? `calc(${S}px + ${h}em)` : `${h}em`
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function v(t, e, r = {}) {
|
|
95
|
+
if (typeof window > "u") return;
|
|
96
|
+
const n = A.get(t);
|
|
97
|
+
if (!n) return;
|
|
98
|
+
const s = Number.isInteger(e) && e >= 0 && e < n.wordSpans.length ? e : -1, o = y(r.inactiveOpacity, 0.45, 0, 1, "inactiveOpacity"), u = s >= 0 ? n.wordOf[s] : -1;
|
|
99
|
+
n.activeIndex = s, n.wordSpans.forEach((i, a) => {
|
|
100
|
+
if (u >= 0 && n.wordOf[a] === u) {
|
|
101
|
+
const p = W(i, r);
|
|
102
|
+
i.setAttribute("aria-current", "true"), i.style.fontVariationSettings = p.fvs, i.style.letterSpacing = p.ls, i.style.opacity = "1";
|
|
103
|
+
} else
|
|
104
|
+
i.removeAttribute("aria-current"), i.style.fontVariationSettings = "", i.style.letterSpacing = "", i.style.opacity = u === -1 ? "1" : String(o);
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
let N = null;
|
|
108
|
+
function K(t, e) {
|
|
109
|
+
var s, o;
|
|
110
|
+
const r = ((o = (s = window.speechSynthesis).getVoices) == null ? void 0 : o.call(s)) ?? [];
|
|
111
|
+
if (e && typeof e == "object") return e;
|
|
112
|
+
if (typeof e == "string") {
|
|
113
|
+
const u = r.find((i) => i.name === e || i.voiceURI === e);
|
|
114
|
+
if (u) return u;
|
|
115
|
+
P(`[speechType] no voice named ${JSON.stringify(e)}; using the language's default`);
|
|
116
|
+
}
|
|
117
|
+
if (!t) return null;
|
|
118
|
+
const n = t.toLowerCase();
|
|
119
|
+
return r.find((u) => u.lang.toLowerCase() === n) ?? r.find((u) => u.lang.toLowerCase().split("-")[0] === n.split("-")[0]) ?? null;
|
|
120
|
+
}
|
|
121
|
+
function Y(t, e = {}) {
|
|
122
|
+
var M, C;
|
|
123
|
+
if (typeof window > "u" || !t || !("speechSynthesis" in window) || typeof SpeechSynthesisUtterance > "u")
|
|
124
|
+
return (M = e.onUnsupported) == null || M.call(e), () => {
|
|
125
|
+
};
|
|
126
|
+
const r = y(e.rate, 0.9, 0.1, 10, "rate"), n = y(e.pitch, 1, 0, 2, "pitch"), s = y(e.volume, 1, 0, 1, "volume"), o = window.speechSynthesis, u = o.speaking || o.pending;
|
|
127
|
+
B(t, e);
|
|
128
|
+
const i = A.get(t);
|
|
129
|
+
if (!i || !i.text) return () => {
|
|
130
|
+
};
|
|
131
|
+
const a = new SpeechSynthesisUtterance(i.text);
|
|
132
|
+
a.rate = r, a.pitch = n, a.volume = s;
|
|
133
|
+
const p = e.lang ?? ((C = t.closest("[lang]")) == null ? void 0 : C.lang) ?? document.documentElement.lang ?? "";
|
|
134
|
+
p && (a.lang = p);
|
|
135
|
+
const g = K(p, e.voice);
|
|
136
|
+
g && (a.voice = g);
|
|
137
|
+
let h = !1, I = !1, S = null, T = null, c = null, f = !1;
|
|
138
|
+
const d = () => {
|
|
139
|
+
var m;
|
|
140
|
+
h || (h = !0, S && clearInterval(S), T && clearInterval(T), c && clearTimeout(c), N === l && (N = null), i.run === l && (i.run = null), v(t, -1, e), (m = e.onEnd) == null || m.call(e));
|
|
141
|
+
};
|
|
142
|
+
a.onstart = () => {
|
|
143
|
+
f = !0;
|
|
144
|
+
}, a.onboundary = (m) => {
|
|
145
|
+
if (h || m.name !== "word") return;
|
|
146
|
+
f = !0;
|
|
147
|
+
let E = -1;
|
|
148
|
+
for (let O = 0; O < i.wordStart.length && i.wordStart[O] <= m.charIndex; O++)
|
|
149
|
+
E = O;
|
|
150
|
+
const L = i.wordOf.indexOf(E);
|
|
151
|
+
L !== -1 && v(t, L, e);
|
|
152
|
+
}, a.onend = () => d(), a.onerror = (m) => {
|
|
153
|
+
var E;
|
|
154
|
+
m.error !== "interrupted" && !(I && m.error === "canceled") && ((E = e.onError) == null || E.call(e, m)), d();
|
|
155
|
+
};
|
|
156
|
+
const l = {
|
|
157
|
+
utterance: a,
|
|
158
|
+
stop: () => {
|
|
159
|
+
h || (N === l && (I = !0, o.cancel()), d());
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
N ? N.stop() : u && o.cancel(), N = l, i.run = l;
|
|
163
|
+
const w = () => {
|
|
164
|
+
c = null, !h && (o.speak(a), /Chrome\//.test(navigator.userAgent) && (S = setInterval(() => {
|
|
165
|
+
o.speaking && !o.paused && (o.pause(), o.resume());
|
|
166
|
+
}, F)), T = setInterval(() => {
|
|
167
|
+
f && !o.speaking && !o.pending && d();
|
|
168
|
+
}, V));
|
|
169
|
+
};
|
|
170
|
+
return u ? c = setTimeout(w, U) : w(), l.stop;
|
|
171
|
+
}
|
|
172
|
+
function j(t) {
|
|
173
|
+
var r;
|
|
174
|
+
const e = A.get(t);
|
|
175
|
+
e && ((r = e.run) == null || r.stop(), v(t, -1), $(e), A.delete(t));
|
|
176
|
+
}
|
|
177
|
+
function q(t) {
|
|
178
|
+
const e = A.get(t);
|
|
179
|
+
if (e) return e.originalHTML;
|
|
180
|
+
const r = t.cloneNode(!0);
|
|
181
|
+
return r.querySelectorAll(`.${x.word}`).forEach((n) => {
|
|
182
|
+
const s = n.parentNode;
|
|
183
|
+
if (s) {
|
|
184
|
+
for (; n.firstChild; ) s.insertBefore(n.firstChild, n);
|
|
185
|
+
s.removeChild(n);
|
|
186
|
+
}
|
|
187
|
+
}), r.querySelectorAll("[data-st-live]").forEach((n) => n.remove()), r.normalize(), r.innerHTML;
|
|
188
|
+
}
|
|
189
|
+
export {
|
|
190
|
+
x as SPEECH_CLASSES,
|
|
191
|
+
v as applySpeechType,
|
|
192
|
+
q as getCleanHTML,
|
|
193
|
+
B as prepareSpeechType,
|
|
194
|
+
j as removeSpeechType,
|
|
195
|
+
Y as startSpeechType
|
|
196
|
+
};
|
package/dist/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const n=require("./core.cjs"),y=require("react"),H=require("react/jsx-runtime");function T(s,u,e){const c=y.useRef(null),r=y.useRef(e);r.current=e;const f=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]);y.useEffect(()=>{const t=s.current,p=c.current;if(!p||p.el!==t||p.transitionMs!==(e==null?void 0:e.transitionMs)){if(p&&p.el!==t&&n.removeSpeechType(p.el),c.current=null,!t)return;n.prepareSpeechType(t,r.current),c.current={el:t,transitionMs:e==null?void 0:e.transitionMs},n.applySpeechType(t,u,r.current)}}),y.useEffect(()=>{const t=s.current;t&&n.applySpeechType(t,u,r.current)},[u,f]),y.useEffect(()=>()=>{c.current&&n.removeSpeechType(c.current.el),c.current=null},[])}function O(s){const u=[],e=c=>{y.Children.forEach(c,r=>{if(!(r==null||typeof r=="boolean")){if(typeof r=="string"||typeof r=="number"){u.push(String(r));return}if(y.isValidElement(r)){const f=typeof r.type=="string"?r.type:r.type.displayName??r.type.name??"C",t=r.props,p=Object.keys(t).filter(a=>a!=="children"&&["string","number","boolean"].includes(typeof t[a])).sort().map(a=>`${a}=${String(t[a])}`);u.push(`<${f}${r.key!=null?"#"+r.key:""} ${p.join(" ")}>`),e(t.children),u.push(`</${f}>`)}}})};return e(s),u.join("\0")}const L=y.forwardRef(function({activeWordIndex:u,as:e="p",children:c,style:r,className:f,activeTracking:t,activeWeight:p,activeOpsz:a,inactiveOpacity:i,transitionMs:h,rate:m,pitch:E,volume:g,onUnsupported:v,onError:C,onEnd:$,lang:l,voice:b,...M},R){const S=y.useRef(null);y.useImperativeHandle(R,()=>S.current),T(S,u,{activeTracking:t,activeWeight:p,activeOpsz:a,inactiveOpacity:i,transitionMs:h,rate:m,pitch:E,volume:g,onUnsupported:v,onError:C,onEnd:$,lang:l,voice:b});const j=e;return H.jsx(j,{ref:S,style:r,className:f,lang:l,...M,children:c},`${typeof e=="string"?e:"C"}|${O(c)}`)});exports.SPEECH_CLASSES=n.SPEECH_CLASSES;exports.applySpeechType=n.applySpeechType;exports.getCleanHTML=n.getCleanHTML;exports.prepareSpeechType=n.prepareSpeechType;exports.removeSpeechType=n.removeSpeechType;exports.startSpeechType=n.startSpeechType;exports.SpeechTypeText=L;exports.useSpeechType=T;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,41 +1,42 @@
|
|
|
1
|
+
import { default as default_2 } from 'react';
|
|
1
2
|
import { ElementType } from 'react';
|
|
2
|
-
import { ForwardRefExoticComponent } from 'react';
|
|
3
|
-
import { RefAttributes } from 'react';
|
|
4
3
|
import { RefObject } from 'react';
|
|
5
4
|
|
|
6
5
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* Emphasise the word containing the span at activeIndex (every span of a word split by markup); dim the
|
|
7
|
+
* others. Pass -1 to reset all words to neutral. The emphasis is a heavier weight, larger optical size and
|
|
8
|
+
* wider tracking around the text's own values (explicit options are absolute).
|
|
9
9
|
*
|
|
10
10
|
* @param el - Element previously prepared by prepareSpeechType
|
|
11
|
-
* @param activeIndex - Index of the word to emphasise, -1 for none
|
|
11
|
+
* @param activeIndex - Index of the word span to emphasise, -1 for none
|
|
12
12
|
* @param options - SpeechTypeOptions (merged with defaults)
|
|
13
13
|
*/
|
|
14
14
|
export declare function applySpeechType(el: HTMLElement, activeIndex: number, options?: SpeechTypeOptions): void;
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
17
|
+
* The element's markup without speechType's word spans. Exact for a prepared element; for any other
|
|
18
|
+
* element, unwraps .st-word spans.
|
|
19
19
|
*
|
|
20
20
|
* @param el - Element to read clean HTML from
|
|
21
21
|
*/
|
|
22
22
|
export declare function getCleanHTML(el: HTMLElement): string;
|
|
23
23
|
|
|
24
|
-
declare type HTMLForwardProps = Omit<
|
|
24
|
+
declare type HTMLForwardProps = Omit<default_2.HTMLAttributes<HTMLElement>, keyof SpeechTypeOptions | 'children' | 'style' | 'className'>;
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
|
-
* Wrap
|
|
28
|
-
*
|
|
29
|
-
*
|
|
27
|
+
* Wrap each visible word of el in a <span class="st-word">, in place: the element's markup, ids,
|
|
28
|
+
* listeners, form values and line breaks are kept, and hidden text, styles, scripts, form fields and SVG
|
|
29
|
+
* are left alone (and not spoken). The text stays readable to screen readers. Idempotent — calling again
|
|
30
|
+
* re-wraps from the original. Returns the word spans in document order.
|
|
30
31
|
*
|
|
31
32
|
* @param el - Element whose text will be wrapped
|
|
32
|
-
* @param options - SpeechTypeOptions (
|
|
33
|
+
* @param options - SpeechTypeOptions (transitionMs is used)
|
|
33
34
|
*/
|
|
34
35
|
export declare function prepareSpeechType(el: HTMLElement, options?: SpeechTypeOptions): HTMLElement[];
|
|
35
36
|
|
|
36
37
|
/**
|
|
37
|
-
*
|
|
38
|
-
*
|
|
38
|
+
* Stop this element's speech (only if it is speaking), put the original text nodes back and delete the
|
|
39
|
+
* saved state. No-op if prepareSpeechType was never called.
|
|
39
40
|
*
|
|
40
41
|
* @param el - The element previously prepared by prepareSpeechType
|
|
41
42
|
*/
|
|
@@ -99,6 +100,15 @@ export declare interface SpeechTypeOptions {
|
|
|
99
100
|
* Called when speech synthesis is unavailable in the current browser.
|
|
100
101
|
* Used only by startSpeechType.
|
|
101
102
|
*/
|
|
103
|
+
/**
|
|
104
|
+
* Language of the speech (BCP 47, e.g. 'ja'). Default: the element's own `lang` (nearest ancestor),
|
|
105
|
+
* else the document's. A voice for that language is chosen when the browser has one.
|
|
106
|
+
*/
|
|
107
|
+
lang?: string;
|
|
108
|
+
/** Voice to use: a SpeechSynthesisVoice, or a voice name / voiceURI. Default: the language's voice. */
|
|
109
|
+
voice?: SpeechSynthesisVoice | string;
|
|
110
|
+
/** Called when a run ends: speech finished, stopped, cancelled by a newer run, or failed (after onError). */
|
|
111
|
+
onEnd?: () => void;
|
|
102
112
|
onUnsupported?: () => void;
|
|
103
113
|
/**
|
|
104
114
|
* Called when a real speech error occurs (any error code other than "interrupted",
|
|
@@ -114,7 +124,7 @@ export declare interface SpeechTypeOptions {
|
|
|
114
124
|
* Forwards a ref to the underlying DOM element for imperative startSpeechType access.
|
|
115
125
|
* All aria-*, data-*, role, lang, and other HTML attributes are forwarded to the DOM element.
|
|
116
126
|
*/
|
|
117
|
-
export declare const SpeechTypeText: ForwardRefExoticComponent<SpeechTypeTextProps & RefAttributes<HTMLElement>>;
|
|
127
|
+
export declare const SpeechTypeText: default_2.ForwardRefExoticComponent<SpeechTypeTextProps & default_2.RefAttributes<HTMLElement>>;
|
|
118
128
|
|
|
119
129
|
/** Props accepted by SpeechTypeText */
|
|
120
130
|
declare interface SpeechTypeTextProps extends SpeechTypeOptions, HTMLForwardProps {
|
|
@@ -123,17 +133,18 @@ declare interface SpeechTypeTextProps extends SpeechTypeOptions, HTMLForwardProp
|
|
|
123
133
|
/** HTML element tag to render. Default: 'p' */
|
|
124
134
|
as?: ElementType;
|
|
125
135
|
/** React children (text content to highlight) */
|
|
126
|
-
children:
|
|
136
|
+
children: default_2.ReactNode;
|
|
127
137
|
/** Inline styles forwarded to the rendered element */
|
|
128
|
-
style?:
|
|
138
|
+
style?: default_2.CSSProperties;
|
|
129
139
|
/** Class name forwarded to the rendered element */
|
|
130
140
|
className?: string;
|
|
131
141
|
}
|
|
132
142
|
|
|
133
143
|
/**
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
* Returns a stop() function that
|
|
144
|
+
* Speak el's text, emphasising each word as the Web Speech API reaches it. Calls prepareSpeechType
|
|
145
|
+
* first. Earlier speech started by speechType is stopped (other speech on the page is left alone unless
|
|
146
|
+
* this run has to take over the speech engine). Returns a stop() function that stops this run and resets
|
|
147
|
+
* the emphasis; the words stay wrapped until removeSpeechType.
|
|
137
148
|
*
|
|
138
149
|
* @param el - Element to speak and highlight
|
|
139
150
|
* @param options - SpeechTypeOptions (merged with defaults)
|
|
@@ -141,11 +152,11 @@ declare interface SpeechTypeTextProps extends SpeechTypeOptions, HTMLForwardProp
|
|
|
141
152
|
export declare function startSpeechType(el: HTMLElement, options?: SpeechTypeOptions): () => void;
|
|
142
153
|
|
|
143
154
|
/**
|
|
144
|
-
* Prepare word spans on mount and
|
|
145
|
-
*
|
|
155
|
+
* Prepare word spans on mount (and again when the element or transitionMs changes) and apply emphasis for
|
|
156
|
+
* activeWordIndex. Restores the element on unmount; speech is only cancelled if this element is speaking.
|
|
146
157
|
*
|
|
147
158
|
* @param ref - Ref to the element containing text to highlight
|
|
148
|
-
* @param activeWordIndex - Index of the currently active word (-1 = none)
|
|
159
|
+
* @param activeWordIndex - Index of the currently active word span (-1 = none)
|
|
149
160
|
* @param options - SpeechTypeOptions (merged with defaults)
|
|
150
161
|
*/
|
|
151
162
|
export declare function useSpeechType(ref: RefObject<HTMLElement | null>, activeWordIndex: number, options?: SpeechTypeOptions): void;
|