Skip to content

Commit 8e647b7

Browse files
committed
fix: Re-factor MyTextToSpeechElement - #private etc (14-Sep-2025)
1 parent 8f4acc1 commit 8e647b7

3 files changed

Lines changed: 90 additions & 22 deletions

File tree

demo/my-speech.html

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<meta name="viewport" content="width=device-width, initial-scale=1" />
55
<meta charset="utf-8">
66

7-
<link rel="stylesheet" href="style/app.css" />
7+
<link rel="stylesheet" href="style/app.v2.css" />
88

99
<my-page>
1010
<h2> my-text-to-speech </h2>
@@ -61,8 +61,21 @@ <h2> my-text-to-speech </h2>
6161

6262
</my-page>
6363

64-
<script type="module" src="./app.js"></script>
6564

65+
<script type="importmap">
66+
{
67+
"imports": {
68+
"my-elements": "../i.js"
69+
},
70+
"myElements": { "use": [ "my-text-to-speech" ] }
71+
}
72+
</script>
73+
74+
<script type="module"> import 'my-elements'; </script>
75+
76+
<!--
77+
<script type="module" src="./app.js"></script>
78+
-->
6679
<!--
6780
https://www.mondly.com/blog/2019/08/23/71-best-tongue-twisters-to-perfect-your-english-pronunciation/;
6881
https://www.fluentin3months.com/chinese-proverbs/

i.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,11 @@ try {
2222

2323
const PR = KLASS.map(async ({ klass, path }) => {
2424
const MOD = await import(path);
25-
// console.debug('class:', MOD[klass]); // HTMLElement.prototype.isPrototypeOf(MOD[klass]));
26-
console.assert(typeof MOD[klass] === 'function', `Should be an exported class: ${klass}`);
27-
console.assert(MOD[klass].getTag, '"getTag()" - Static function not found.');
28-
customElements.define(MOD[klass].getTag(), MOD[klass]);
25+
const DEF = MOD[klass];
26+
// console.debug('class:', DEF); // HTMLElement.prototype.isPrototypeOf(MOD[klass]));
27+
console.assert(DEF.prototype instanceof HTMLElement, `Should be an exported class: ${klass}`);
28+
console.assert(DEF.getTag, '"getTag()" - Static function not found.');
29+
customElements.define(DEF.getTag(), DEF);
2930
});
3031
await Promise.all(PR);
3132

src/components/MyTextToSpeechElement.js

Lines changed: 70 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
import attachTemplate from '../util/attachTemplate.js';
2+
// import { MyElement } from '../MyElement.js';
3+
4+
const { speechSynthesis, SpeechSynthesisUtterance } = window;
5+
16
/**
27
* Speech synthesis using the Web Speech API.
38
*
@@ -16,19 +21,14 @@
1621
* @status experimental
1722
* @since 1.X.0
1823
*/
19-
20-
import { MyElement } from '../MyElement.js';
21-
22-
const { speechSynthesis, SpeechSynthesisUtterance } = window;
23-
24-
export class MyTextToSpeechElement extends MyElement {
24+
export class MyTextToSpeechElement extends HTMLElement {
2525
static getTag () {
2626
return 'my-text-to-speech'; // Was: 'my-speech-synthesis';
2727
}
2828

29-
/* constructor () { // "Useless constructor"!
30-
super();
31-
} */
29+
get pitch () { return parseFloat(this.getAttribute('pitch')) || 1; } // Default: 1; Range: 0-2;
30+
get rate () { return parseFloat(this.getAttribute('rate')) || 1; } // Default: 1; Range: 0.1-10;
31+
get volume () { return parseFloat(this.getAttribute('volume')) || 1; } // Default: 1; Range: 0-1;
3232

3333
async connectedCallback () {
3434
const langRegex = this.getAttribute('lang-regex') || 'en.*';
@@ -37,11 +37,12 @@ export class MyTextToSpeechElement extends MyElement {
3737
const rate = parseFloat(this.getAttribute('rate')) || 1; // Default: 1; Range: 0.1-10;
3838
const volume = parseFloat(this.getAttribute('volume')) || 1; // Range: 0-1;
3939

40-
await this._initialize({ langRegex, voxRegex, pitch, rate, volume });
40+
await this.#initialize({ langRegex, voxRegex, pitch, rate, volume });
4141
}
4242

43-
async _initialize (ATTR = {}) {
44-
await this.getTemplate('my-text-to-speech');
43+
async #initialize (ATTR = {}) {
44+
attachTemplate(this.#htmlTemplate).to.shadowDOM(this);
45+
// Was: await this.getTemplate('my-text-to-speech');
4546

4647
const utterElem = this.shadowRoot.querySelector('#utterance');
4748
const voxSelect = this.shadowRoot.querySelector('#vox select');
@@ -54,15 +55,15 @@ export class MyTextToSpeechElement extends MyElement {
5455

5556
speechSynthesis.onvoiceschanged = this._addVoicesToSelect;
5657

57-
setTimeout(() => this._addVoicesToSelect(), 1500);
58+
setTimeout(() => this.#addVoicesToSelect(), 1500);
5859

5960
console.debug('my-text-to-speech:', this);
6061
console.dir(this);
6162
}
6263

6364
_speak (say = null, ev = null) {
6465
const ATTR = this.$$;
65-
const SAY = this._getText(say);
66+
const SAY = this.#getText(say);
6667
const VOX = this._findVoiceByName(ATTR.voxRegex); // /(Fiona|Goo*UK Eng*Female)/);
6768

6869
const utterThis = new SpeechSynthesisUtterance(SAY);
@@ -78,7 +79,7 @@ export class MyTextToSpeechElement extends MyElement {
7879
console.debug('Speak:', SAY, utterThis, VOX, ev);
7980
}
8081

81-
_getText (say = null) {
82+
#getText (say = null) {
8283
const TEXT = this.textContent.replace(/[ \n]{2,}/g, '\n');
8384
const EL = this.$$.utterElem; // this.shadowRoot.querySelector('#utterance');
8485
// console.debug('Utter:', ELEM, ELEM.innerHTML, ELEM.content);
@@ -100,7 +101,7 @@ export class MyTextToSpeechElement extends MyElement {
100101
return VOX.find(vox => nameRegex.test(vox.name));
101102
}
102103

103-
_addVoicesToSelect () {
104+
#addVoicesToSelect () {
104105
const ALL = /\?vox=all/.test(document.location.href);
105106
const SELECT = this.$$.voxSelect; // document.querySelector('#vox');
106107
const LOG = this.$$.log; // document.querySelector('#log');
@@ -126,4 +127,57 @@ export class MyTextToSpeechElement extends MyElement {
126127

127128
console.debug('Voices:', SELECT, VOICES);
128129
}
130+
131+
// get #utterElem () { return this.shadowRoot.querySelector('#utterance'); }
132+
// get #button () { return this.shadowRoot.querySelector('button'); }
133+
134+
get #stylesheet () {
135+
return `
136+
button,
137+
select {
138+
font: inherit;
139+
padding: .5rem;
140+
}
141+
button {
142+
padding: .2rem 2rem;
143+
}
144+
select {
145+
margin: 0 .5rem;
146+
min-width: 9rem;
147+
max-width: 100%;
148+
}
149+
pre {
150+
font-size: small;
151+
line-height: 1.2;
152+
}
153+
label,
154+
[ contenteditable = true ] {
155+
X-padding: 0 .2rem;
156+
outline-offset: .3rem;
157+
}
158+
`;
159+
}
160+
161+
get #htmlTemplate () {
162+
return `
163+
<template>
164+
<style>${this.#stylesheet}</style>
165+
166+
<div id="utterance" contenteditable="true" aria-label="Text to speak" part="utterance">
167+
<slot> Hello! You can edit me. </slot>
168+
</div>
169+
<!-- <textarea><slot></slot></textarea> -->
170+
171+
<p>
172+
<label id="vox">Voice:<select></select></label>
173+
<button part="button"> Speak </button>
174+
</p>
175+
176+
<details part="details">
177+
<summary> Voices </summary>
178+
<pre id="log"><pre>
179+
</details>
180+
</template>
181+
`;
182+
}
129183
}

0 commit comments

Comments
 (0)