@wcstack/speech 1.16.0 → 1.18.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.ja.md +73 -0
- package/README.md +73 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.esm.js +124 -0
- package/dist/index.esm.js.map +1 -1
- package/dist/index.esm.min.js +1 -1
- package/dist/index.esm.min.js.map +1 -1
- package/package.json +1 -1
package/README.ja.md
CHANGED
|
@@ -164,6 +164,79 @@ export default {
|
|
|
164
164
|
|
|
165
165
|
---
|
|
166
166
|
|
|
167
|
+
## `:state()` による CSS スタイリング
|
|
168
|
+
|
|
169
|
+
`<wcs-speak>` と `<wcs-listen>` はそれぞれ、自分自身の boolean 出力ステートを
|
|
170
|
+
[`ElementInternals` の `CustomStateSet`](https://developer.mozilla.org/ja/docs/Web/API/CustomStateSet)
|
|
171
|
+
に反映します。そのため `data-wcs` バインディングやクラスの手動トグルなしに、CSS の
|
|
172
|
+
`:state()` 疑似クラスで直接スタイリングできます。
|
|
173
|
+
|
|
174
|
+
### `<wcs-speak>`
|
|
175
|
+
|
|
176
|
+
| ステート | on になる条件 |
|
|
177
|
+
|----------|----------------|
|
|
178
|
+
| `speaking` | `wcs-speak:speaking-changed` が `true` で発火(`false` でクリア) |
|
|
179
|
+
| `paused` | `wcs-speak:paused-changed` が `true` で発火(`false` でクリア) |
|
|
180
|
+
| `pending` | `wcs-speak:pending-changed` が `true` で発火(`false` でクリア) |
|
|
181
|
+
| `unsupported` | `wcs-speak:unsupported-changed` が `true` で発火(`false` でクリア) |
|
|
182
|
+
| `error` | `wcs-speak:error` が非 `null` の detail で発火(`null` でクリア) |
|
|
183
|
+
|
|
184
|
+
```css
|
|
185
|
+
wcs-speak:state(speaking) ~ .indicator { color: green; }
|
|
186
|
+
wcs-speak:state(unsupported) ~ .fallback { display: block; }
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### `<wcs-listen>`
|
|
190
|
+
|
|
191
|
+
| ステート | on になる条件 |
|
|
192
|
+
|----------|----------------|
|
|
193
|
+
| `listening` | `wcs-listen:listening-changed` が `true` で発火(`false` でクリア) |
|
|
194
|
+
| `unsupported` | `wcs-listen:unsupported-changed` が `true` で発火(`false` でクリア) |
|
|
195
|
+
| `error` | `wcs-listen:error` が非 `null` の detail で発火(`null` でクリア) |
|
|
196
|
+
|
|
197
|
+
```css
|
|
198
|
+
wcs-listen:state(listening) ~ .mic-indicator { color: red; }
|
|
199
|
+
form:has(wcs-listen:state(error)) .banner { display: block; }
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
属性やクラスと異なり `:state()` は要素の外部から書き込めないため、この出力ステートが
|
|
203
|
+
入力と混同される心配がありません。
|
|
204
|
+
|
|
205
|
+
**対応ブラウザ**(新構文 `:state(x)`): Chrome/Edge 125+、Safari 17.4+、Firefox 126+。
|
|
206
|
+
非対応の環境ではステートが一切 set されないだけです — `:state()` セレクタがマッチしなく
|
|
207
|
+
なりますが、各コンポーネント自体は通常どおり動作し続けます(graceful degradation・
|
|
208
|
+
never-throw)。これは特に `<wcs-listen>` の `unsupported` ステートで意味を持ちます。
|
|
209
|
+
SpeechRecognition 自体が Chrome 系のみの対応だからです(後述の「注意・制限」参照)——
|
|
210
|
+
`:state(unsupported)` はまさに、それ以外のブラウザでフォールバックを表示するために使う
|
|
211
|
+
セレクタです。
|
|
212
|
+
|
|
213
|
+
**SSR:** `:state()` は HTML にシリアライズできないため、サーバーレンダリングされた
|
|
214
|
+
マークアップの初期ペイントにはこれらのステートは乗りません(`@wcstack/server` は無改変)。
|
|
215
|
+
ハイドレーション前の見た目を制御したい場合は、代わりに
|
|
216
|
+
`wcs-speak:not(:defined)` / `wcs-listen:not(:defined)` と組み合わせてください。
|
|
217
|
+
|
|
218
|
+
### デバッグ
|
|
219
|
+
|
|
220
|
+
カスタムステートは DevTools の Elements パネルには表示されず、`attachInternals()`
|
|
221
|
+
は同一要素に 2 回呼べないため、コンソールから直接覗く手段がありません。そのための
|
|
222
|
+
デバッグ専用の補助を 2 つ用意しています:
|
|
223
|
+
|
|
224
|
+
- `el.debugStates` — 現在 on になっているステート名の**スナップショット**配列
|
|
225
|
+
(例: `["speaking"]`)。`wc-bindable` の一部ではなく(バインド対象ではない)、
|
|
226
|
+
形状も契約として保証されません — デバッグ用途にのみ使ってください。
|
|
227
|
+
- `debug-states` 属性(opt-in・既定 OFF)は、ステート変化を要素の
|
|
228
|
+
`data-wcs-state-*` 属性にミラーします。Elements パネルを開いておけば、
|
|
229
|
+
トグルのたびにハイライトされます:
|
|
230
|
+
|
|
231
|
+
```html
|
|
232
|
+
<wcs-speak say="Hello" debug-states></wcs-speak>
|
|
233
|
+
<wcs-listen debug-states></wcs-listen>
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
**CSS は `data-wcs-state-*` ではなく `:state()` に書いてください。** ミラーされた
|
|
237
|
+
属性は、DevTools を開いた状態でステート変化を可視化するためだけのものであり、
|
|
238
|
+
スタイリング用の正式なフックではありません。
|
|
239
|
+
|
|
167
240
|
## 注意・制限
|
|
168
241
|
|
|
169
242
|
- **セキュアコンテキスト必須。** 両 API とも HTTPS か `localhost` が必要。`<wcs-listen>` はさらにマイク permission が必要です。
|
package/README.md
CHANGED
|
@@ -165,6 +165,79 @@ Clicking an element with `data-listentarget="<id>"` toggles `start()` / `stop()`
|
|
|
165
165
|
|
|
166
166
|
---
|
|
167
167
|
|
|
168
|
+
## CSS styling with `:state()`
|
|
169
|
+
|
|
170
|
+
`<wcs-speak>` and `<wcs-listen>` each reflect their boolean output states onto
|
|
171
|
+
their own [`ElementInternals` `CustomStateSet`](https://developer.mozilla.org/en-US/docs/Web/API/CustomStateSet),
|
|
172
|
+
so you can style them directly from CSS with the `:state()` pseudo-class — no
|
|
173
|
+
`data-wcs` binding or extra class toggling required.
|
|
174
|
+
|
|
175
|
+
### `<wcs-speak>`
|
|
176
|
+
|
|
177
|
+
| State | On when |
|
|
178
|
+
|-------|---------|
|
|
179
|
+
| `speaking` | `wcs-speak:speaking-changed` fires with `true` (cleared on `false`) |
|
|
180
|
+
| `paused` | `wcs-speak:paused-changed` fires with `true` (cleared on `false`) |
|
|
181
|
+
| `pending` | `wcs-speak:pending-changed` fires with `true` (cleared on `false`) |
|
|
182
|
+
| `unsupported` | `wcs-speak:unsupported-changed` fires with `true` (cleared on `false`) |
|
|
183
|
+
| `error` | `wcs-speak:error` fires with a non-`null` detail (cleared on `null`) |
|
|
184
|
+
|
|
185
|
+
```css
|
|
186
|
+
wcs-speak:state(speaking) ~ .indicator { color: green; }
|
|
187
|
+
wcs-speak:state(unsupported) ~ .fallback { display: block; }
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### `<wcs-listen>`
|
|
191
|
+
|
|
192
|
+
| State | On when |
|
|
193
|
+
|-------|---------|
|
|
194
|
+
| `listening` | `wcs-listen:listening-changed` fires with `true` (cleared on `false`) |
|
|
195
|
+
| `unsupported` | `wcs-listen:unsupported-changed` fires with `true` (cleared on `false`) |
|
|
196
|
+
| `error` | `wcs-listen:error` fires with a non-`null` detail (cleared on `null`) |
|
|
197
|
+
|
|
198
|
+
```css
|
|
199
|
+
wcs-listen:state(listening) ~ .mic-indicator { color: red; }
|
|
200
|
+
form:has(wcs-listen:state(error)) .banner { display: block; }
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Unlike attributes or classes, `:state()` cannot be written from outside the
|
|
204
|
+
element, so there is no risk of confusing this output state with an input.
|
|
205
|
+
|
|
206
|
+
**Browser support** (`:state(x)` syntax): Chrome/Edge 125+, Safari 17.4+,
|
|
207
|
+
Firefox 126+. In older browsers the states are simply never set — `:state()`
|
|
208
|
+
selectors never match, but the components keep working normally (graceful
|
|
209
|
+
degradation, never-throw). This matters in particular for `<wcs-listen>`'s
|
|
210
|
+
`unsupported` state, since SpeechRecognition itself is Chrome-only (see
|
|
211
|
+
"Notes & limitations" below) — `:state(unsupported)` is exactly the selector
|
|
212
|
+
you would use to show a fallback in every other browser.
|
|
213
|
+
|
|
214
|
+
**SSR**: `:state()` cannot be serialized into HTML, so server-rendered markup
|
|
215
|
+
never carries these states on first paint (`@wcstack/server` is unaffected).
|
|
216
|
+
If you need to style the pre-hydration gap, pair your rule with
|
|
217
|
+
`wcs-speak:not(:defined)` / `wcs-listen:not(:defined)` instead.
|
|
218
|
+
|
|
219
|
+
### Debugging
|
|
220
|
+
|
|
221
|
+
Custom states are invisible in DevTools' Elements panel and `attachInternals()`
|
|
222
|
+
cannot be called twice, so there is no console way to inspect them directly.
|
|
223
|
+
Two debug-only aids are provided for that:
|
|
224
|
+
|
|
225
|
+
- `el.debugStates` — a **snapshot** array of the currently-on state names
|
|
226
|
+
(e.g. `["speaking"]`). It is not part of `wc-bindable` (not a bind target)
|
|
227
|
+
and its shape is not a guaranteed contract — use it for debugging only.
|
|
228
|
+
- The `debug-states` attribute (opt-in, default off) mirrors state changes
|
|
229
|
+
onto `data-wcs-state-*` attributes on the element, so the Elements panel
|
|
230
|
+
highlights them as they toggle:
|
|
231
|
+
|
|
232
|
+
```html
|
|
233
|
+
<wcs-speak say="Hello" debug-states></wcs-speak>
|
|
234
|
+
<wcs-listen debug-states></wcs-listen>
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
**Write your CSS against `:state()`, not `data-wcs-state-*`.** The mirrored
|
|
238
|
+
attributes exist purely to make state changes visible while debugging with
|
|
239
|
+
DevTools open; they are not a supported styling hook.
|
|
240
|
+
|
|
168
241
|
## Notes & limitations
|
|
169
242
|
|
|
170
243
|
- **Secure context required.** Both APIs need HTTPS or `localhost`; `<wcs-listen>` additionally needs microphone permission.
|
package/dist/index.d.ts
CHANGED
|
@@ -346,8 +346,12 @@ declare class WcsSpeak extends HTMLElement {
|
|
|
346
346
|
private _core;
|
|
347
347
|
private _say;
|
|
348
348
|
private _connectedCallbackPromise;
|
|
349
|
+
private _internals;
|
|
349
350
|
constructor();
|
|
350
351
|
get connectedCallbackPromise(): Promise<void>;
|
|
352
|
+
get debugStates(): string[];
|
|
353
|
+
private _initInternals;
|
|
354
|
+
private _wireStates;
|
|
351
355
|
get rate(): number;
|
|
352
356
|
set rate(value: number);
|
|
353
357
|
get pitch(): number;
|
|
@@ -492,8 +496,12 @@ declare class WcsListen extends HTMLElement {
|
|
|
492
496
|
private _core;
|
|
493
497
|
private _trigger;
|
|
494
498
|
private _connectedCallbackPromise;
|
|
499
|
+
private _internals;
|
|
495
500
|
constructor();
|
|
496
501
|
get connectedCallbackPromise(): Promise<void>;
|
|
502
|
+
get debugStates(): string[];
|
|
503
|
+
private _initInternals;
|
|
504
|
+
private _wireStates;
|
|
497
505
|
get lang(): string;
|
|
498
506
|
set lang(value: string | null);
|
|
499
507
|
get continuous(): boolean;
|
package/dist/index.esm.js
CHANGED
|
@@ -570,13 +570,75 @@ class WcsSpeak extends HTMLElement {
|
|
|
570
570
|
_core;
|
|
571
571
|
_say = "";
|
|
572
572
|
_connectedCallbackPromise = Promise.resolve();
|
|
573
|
+
_internals = null;
|
|
573
574
|
constructor() {
|
|
574
575
|
super();
|
|
576
|
+
// States are wired BEFORE the Core is constructed (unlike the canonical
|
|
577
|
+
// Core-then-internals-then-wireStates order): SpeakCore's constructor
|
|
578
|
+
// synchronously dispatches `wcs-speak:unsupported-changed` when the
|
|
579
|
+
// SpeechSynthesis API is absent, so the listener must already be attached
|
|
580
|
+
// to observe that first (and, in a fixed-support environment, only) event.
|
|
581
|
+
this._internals = this._initInternals();
|
|
582
|
+
this._wireStates({
|
|
583
|
+
"wcs-speak:speaking-changed": (d) => ({ speaking: d === true }),
|
|
584
|
+
"wcs-speak:paused-changed": (d) => ({ paused: d === true }),
|
|
585
|
+
"wcs-speak:pending-changed": (d) => ({ pending: d === true }),
|
|
586
|
+
"wcs-speak:unsupported-changed": (d) => ({ unsupported: d === true }),
|
|
587
|
+
"wcs-speak:error": (d) => ({ error: d != null }),
|
|
588
|
+
});
|
|
575
589
|
this._core = new SpeakCore(this);
|
|
576
590
|
}
|
|
577
591
|
get connectedCallbackPromise() {
|
|
578
592
|
return this._connectedCallbackPromise;
|
|
579
593
|
}
|
|
594
|
+
// CSS state reflection (:state()) — debug-only snapshot getter. NOT part of
|
|
595
|
+
// wc-bindable (not a bind target); see README "CSS styling with :state()".
|
|
596
|
+
// MUST NOT return the live CustomStateSet (that would let callers write
|
|
597
|
+
// states from outside, defeating the point of :state() being read-only).
|
|
598
|
+
get debugStates() {
|
|
599
|
+
return this._internals ? [...this._internals.states] : [];
|
|
600
|
+
}
|
|
601
|
+
_initInternals() {
|
|
602
|
+
// never-throw (async-io-node-guidelines.md §3.6): attachInternals is absent
|
|
603
|
+
// in happy-dom / older environments, and pre-125 Chromium rejects
|
|
604
|
+
// non-dashed state names from states.add() (probed and discarded here).
|
|
605
|
+
// Either case silently disables reflection — the component still works,
|
|
606
|
+
// it just doesn't expose :state() selectors.
|
|
607
|
+
try {
|
|
608
|
+
if (typeof this.attachInternals !== "function")
|
|
609
|
+
return null;
|
|
610
|
+
const internals = this.attachInternals();
|
|
611
|
+
internals.states.add("wcs-probe");
|
|
612
|
+
internals.states.delete("wcs-probe");
|
|
613
|
+
return internals;
|
|
614
|
+
}
|
|
615
|
+
catch {
|
|
616
|
+
return null;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
_wireStates(map) {
|
|
620
|
+
if (this._internals === null)
|
|
621
|
+
return;
|
|
622
|
+
const states = this._internals.states;
|
|
623
|
+
for (const [event, toStates] of Object.entries(map)) {
|
|
624
|
+
this.addEventListener(event, (e) => {
|
|
625
|
+
const debug = this.hasAttribute("debug-states");
|
|
626
|
+
for (const [name, on] of Object.entries(toStates(e.detail))) {
|
|
627
|
+
try {
|
|
628
|
+
if (on) {
|
|
629
|
+
states.add(name);
|
|
630
|
+
}
|
|
631
|
+
else {
|
|
632
|
+
states.delete(name);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
catch { /* never-throw */ }
|
|
636
|
+
if (debug)
|
|
637
|
+
this.toggleAttribute(`data-wcs-state-${name}`, on);
|
|
638
|
+
}
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
}
|
|
580
642
|
// --- Attribute accessors ---
|
|
581
643
|
get rate() {
|
|
582
644
|
return this._numberAttr("rate", 1);
|
|
@@ -1202,13 +1264,75 @@ class WcsListen extends HTMLElement {
|
|
|
1202
1264
|
_core;
|
|
1203
1265
|
_trigger = false;
|
|
1204
1266
|
_connectedCallbackPromise = Promise.resolve();
|
|
1267
|
+
_internals = null;
|
|
1205
1268
|
constructor() {
|
|
1206
1269
|
super();
|
|
1270
|
+
// States are wired BEFORE the Core is constructed (unlike the canonical
|
|
1271
|
+
// Core-then-internals-then-wireStates order): ListenCore's constructor
|
|
1272
|
+
// synchronously dispatches `wcs-listen:unsupported-changed` when the
|
|
1273
|
+
// SpeechRecognition API is absent (notably Safari, which ships
|
|
1274
|
+
// SpeechSynthesis but not SpeechRecognition), so the listener must already
|
|
1275
|
+
// be attached to observe that first (and, in a fixed-support environment,
|
|
1276
|
+
// only) event.
|
|
1277
|
+
this._internals = this._initInternals();
|
|
1278
|
+
this._wireStates({
|
|
1279
|
+
"wcs-listen:listening-changed": (d) => ({ listening: d === true }),
|
|
1280
|
+
"wcs-listen:unsupported-changed": (d) => ({ unsupported: d === true }),
|
|
1281
|
+
"wcs-listen:error": (d) => ({ error: d != null }),
|
|
1282
|
+
});
|
|
1207
1283
|
this._core = new ListenCore(this);
|
|
1208
1284
|
}
|
|
1209
1285
|
get connectedCallbackPromise() {
|
|
1210
1286
|
return this._connectedCallbackPromise;
|
|
1211
1287
|
}
|
|
1288
|
+
// CSS state reflection (:state()) — debug-only snapshot getter. NOT part of
|
|
1289
|
+
// wc-bindable (not a bind target); see README "CSS styling with :state()".
|
|
1290
|
+
// MUST NOT return the live CustomStateSet (that would let callers write
|
|
1291
|
+
// states from outside, defeating the point of :state() being read-only).
|
|
1292
|
+
get debugStates() {
|
|
1293
|
+
return this._internals ? [...this._internals.states] : [];
|
|
1294
|
+
}
|
|
1295
|
+
_initInternals() {
|
|
1296
|
+
// never-throw (async-io-node-guidelines.md §3.6): attachInternals is absent
|
|
1297
|
+
// in happy-dom / older environments, and pre-125 Chromium rejects
|
|
1298
|
+
// non-dashed state names from states.add() (probed and discarded here).
|
|
1299
|
+
// Either case silently disables reflection — the component still works,
|
|
1300
|
+
// it just doesn't expose :state() selectors.
|
|
1301
|
+
try {
|
|
1302
|
+
if (typeof this.attachInternals !== "function")
|
|
1303
|
+
return null;
|
|
1304
|
+
const internals = this.attachInternals();
|
|
1305
|
+
internals.states.add("wcs-probe");
|
|
1306
|
+
internals.states.delete("wcs-probe");
|
|
1307
|
+
return internals;
|
|
1308
|
+
}
|
|
1309
|
+
catch {
|
|
1310
|
+
return null;
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
_wireStates(map) {
|
|
1314
|
+
if (this._internals === null)
|
|
1315
|
+
return;
|
|
1316
|
+
const states = this._internals.states;
|
|
1317
|
+
for (const [event, toStates] of Object.entries(map)) {
|
|
1318
|
+
this.addEventListener(event, (e) => {
|
|
1319
|
+
const debug = this.hasAttribute("debug-states");
|
|
1320
|
+
for (const [name, on] of Object.entries(toStates(e.detail))) {
|
|
1321
|
+
try {
|
|
1322
|
+
if (on) {
|
|
1323
|
+
states.add(name);
|
|
1324
|
+
}
|
|
1325
|
+
else {
|
|
1326
|
+
states.delete(name);
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
catch { /* never-throw */ }
|
|
1330
|
+
if (debug)
|
|
1331
|
+
this.toggleAttribute(`data-wcs-state-${name}`, on);
|
|
1332
|
+
}
|
|
1333
|
+
});
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1212
1336
|
// --- Attribute accessors ---
|
|
1213
1337
|
get lang() {
|
|
1214
1338
|
return this.getAttribute("lang") ?? "";
|