@pie-players/pie-tool-answer-eliminator 0.3.50 → 0.3.52
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 +19 -26
- package/dist/{dist-DfQrc9XY.js → dist-BV1q85ZL.js} +25 -24
- package/dist/tool-answer-eliminator.js +1266 -1079
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -179,18 +179,21 @@ if (saved) {
|
|
|
179
179
|
|
|
180
180
|
### 1. Choice Detection
|
|
181
181
|
|
|
182
|
-
The tool
|
|
182
|
+
The tool detects choices through an **adapter registry** (`AdapterRegistry`), not a fixed selector list. Each adapter knows how to find and operate on choices for a specific PIE element type, and the registry runs them in priority order (`canHandle()`, then `findChoices()`), so a single question can mix element types:
|
|
183
|
+
|
|
184
|
+
| Adapter | Element type | How it finds choices |
|
|
185
|
+
| --- | --- | --- |
|
|
186
|
+
| `MultipleChoiceAdapter` | `multiple-choice` (single- or multi-select) | `.corespring-checkbox` / `.corespring-radio-button` choices |
|
|
187
|
+
| `EBSRAdapter` | `ebsr` | delegates to the multiple-choice adapter for each `ebsr-multiple-choice` part (Part A/B), prefixing choice IDs with the part |
|
|
188
|
+
| `InlineDropdownAdapter` | `inline-dropdown` | dropdown items with `role="option"` |
|
|
189
|
+
|
|
190
|
+
Support more element types at runtime by registering your own adapter (any object implementing the `ChoiceAdapter` interface: `canHandle`, `findChoices`, `getChoiceId`, `getChoiceLabel`, `canEliminate`, `createChoiceRange`, `getButtonContainer`):
|
|
183
191
|
|
|
184
192
|
```typescript
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
'[role="radio"]', // Accessibility
|
|
190
|
-
'[role="checkbox"]', // Accessibility
|
|
191
|
-
'input[type="radio"]', // Native inputs
|
|
192
|
-
'input[type="checkbox"]' // Native inputs
|
|
193
|
-
];
|
|
193
|
+
import { AdapterRegistry } from '@pie-players/pie-tool-answer-eliminator';
|
|
194
|
+
|
|
195
|
+
const registry = new AdapterRegistry();
|
|
196
|
+
registry.registerAdapter(myCustomChoiceAdapter);
|
|
194
197
|
```
|
|
195
198
|
|
|
196
199
|
### 2. State Storage
|
|
@@ -205,26 +208,16 @@ Eliminated choices are stored by choice ID:
|
|
|
205
208
|
|
|
206
209
|
### 3. Visual Feedback
|
|
207
210
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
```css
|
|
211
|
-
.choice.eliminated {
|
|
212
|
-
text-decoration: line-through;
|
|
213
|
-
opacity: 0.5;
|
|
214
|
-
}
|
|
215
|
-
```
|
|
211
|
+
Elimination styling is applied by a pluggable `EliminationStrategy` built on the **CSS Custom Highlight API** (zero DOM mutation, so the choice text and structure stay intact for screen readers). Two strategies ship today:
|
|
216
212
|
|
|
217
|
-
|
|
213
|
+
- `strikethrough` (default): a `::highlight(pie-answer-eliminated-<id>)` rule renders a line-through over the choice label.
|
|
214
|
+
- `mask`: a `::highlight(pie-answer-masked-<id>)` rule dims and blurs the choice.
|
|
218
215
|
|
|
219
|
-
|
|
216
|
+
For browsers without the Highlight API, each strategy falls back to a class on the choice container. Either way the eliminated choice also receives ARIA hooks (`data-pie-answer-eliminated`, plus `aria-disabled`/`aria-hidden` and an offscreen "(eliminated)" announcement) for assistive technology.
|
|
220
217
|
|
|
221
|
-
|
|
222
|
-
// First click: eliminate
|
|
223
|
-
choiceElement.classList.add('eliminated');
|
|
218
|
+
### 4. Toggle Behavior
|
|
224
219
|
|
|
225
|
-
|
|
226
|
-
choiceElement.classList.remove('eliminated');
|
|
227
|
-
```
|
|
220
|
+
Each detected choice gets its own elimination toggle button (class `pie-answer-eliminator-toggle`, rendered as `⊗`) placed via the adapter's `getButtonContainer()`. Clicking the button toggles that choice through the active strategy. Eliminating is blocked when the adapter's `canEliminate()` returns false — for example when the choice is already selected, disabled, or the item is in evaluate/view mode. An active button is marked with `pie-answer-eliminator-toggle--active` and `aria-pressed="true"`; clicking again restores the choice.
|
|
228
221
|
|
|
229
222
|
## Cleanup
|
|
230
223
|
|
|
@@ -37,22 +37,20 @@ function R(t) {
|
|
|
37
37
|
var I = (t) => {
|
|
38
38
|
const e = (t.providerOptions && typeof t.providerOptions == "object" ? t.providerOptions : {}).__pieTelemetry;
|
|
39
39
|
return typeof e == "function" ? e : void 0;
|
|
40
|
-
},
|
|
40
|
+
}, M = {
|
|
41
41
|
pie: 3e3,
|
|
42
42
|
custom: 3e3
|
|
43
|
-
},
|
|
43
|
+
}, P = /* @__PURE__ */ new Set(["polly", "google"]), O = (t) => {
|
|
44
44
|
if (!t || b(t) !== "pie") return !1;
|
|
45
45
|
const e = (t.provider || "polly").toLowerCase();
|
|
46
|
-
return
|
|
47
|
-
}, S = (t) => t.replace(/\/+$/, ""),
|
|
46
|
+
return P.has(e);
|
|
47
|
+
}, S = (t) => t.replace(/\/+$/, ""), U = (t) => {
|
|
48
48
|
const e = S(t.apiEndpoint), r = (t.provider || "").toLowerCase();
|
|
49
49
|
return r === "polly" || r === "google" ? `${e}/${r}/voices` : `${e}/voices`;
|
|
50
|
-
}, b = (t) => t.transportMode === "custom" ? "custom" : t.transportMode === "pie" ? "pie" : t.provider === "custom" ? "custom" : "pie", k = (t, e) => t.endpointMode ? t.endpointMode : e === "custom" ? "rootPost" : "synthesizePath",
|
|
50
|
+
}, b = (t) => t.transportMode === "custom" ? "custom" : t.transportMode === "pie" ? "pie" : t.provider === "custom" ? "custom" : "pie", k = (t, e) => t.endpointMode ? t.endpointMode : e === "custom" ? "rootPost" : "synthesizePath", L = (t, e) => t.endpointValidationMode ? t.endpointValidationMode : e === "custom" ? "none" : "voices", E = (t) => Number.isFinite(t) ? t < 1 ? "slow" : t > 1 ? "fast" : "medium" : "medium", B = (t) => {
|
|
51
51
|
const e = t.providerOptions || {};
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
return !Number.isFinite(r) || r <= 0.95 ? "slow" : r >= 1.5 ? "fast" : "medium";
|
|
55
|
-
}, B = (t) => {
|
|
52
|
+
return typeof e.speedRate == "string" ? e.speedRate : E(Number(t.rate ?? 1));
|
|
53
|
+
}, C = (t) => {
|
|
56
54
|
const e = [];
|
|
57
55
|
let r = 0;
|
|
58
56
|
const i = t.split(`
|
|
@@ -69,7 +67,7 @@ var I = (t) => {
|
|
|
69
67
|
} catch {
|
|
70
68
|
}
|
|
71
69
|
return e;
|
|
72
|
-
},
|
|
70
|
+
}, z = (t) => {
|
|
73
71
|
if (!Array.isArray(t)) return [];
|
|
74
72
|
const e = [];
|
|
75
73
|
let r = 0;
|
|
@@ -85,7 +83,7 @@ var I = (t) => {
|
|
|
85
83
|
});
|
|
86
84
|
}
|
|
87
85
|
return e.sort((i, o) => i.time !== o.time ? i.time - o.time : i.start !== o.start ? i.start - o.start : i.end - o.end);
|
|
88
|
-
},
|
|
86
|
+
}, x = {
|
|
89
87
|
pie: {
|
|
90
88
|
id: "pie",
|
|
91
89
|
resolveSynthesisUrl: (t) => {
|
|
@@ -129,7 +127,7 @@ var I = (t) => {
|
|
|
129
127
|
const r = e.providerOptions || {}, i = typeof r.lang_id == "string" ? r.lang_id : e.language || "en-US", o = typeof r.cache == "boolean" ? r.cache : !0;
|
|
130
128
|
return {
|
|
131
129
|
text: t,
|
|
132
|
-
speedRate:
|
|
130
|
+
speedRate: B(e),
|
|
133
131
|
lang_id: i,
|
|
134
132
|
cache: o
|
|
135
133
|
};
|
|
@@ -139,7 +137,7 @@ var I = (t) => {
|
|
|
139
137
|
if (e.includeAuthOnAssetFetch)
|
|
140
138
|
for (const [l, u] of Object.entries(r)) l.toLowerCase() === "authorization" && (s[l] = u);
|
|
141
139
|
let a = [];
|
|
142
|
-
const n =
|
|
140
|
+
const n = z(o.speechMarks);
|
|
143
141
|
if (n.length > 0) a = n;
|
|
144
142
|
else if (typeof o.word == "string" && o.word.length > 0) {
|
|
145
143
|
const l = T(o.word, e);
|
|
@@ -149,7 +147,7 @@ var I = (t) => {
|
|
|
149
147
|
signal: i,
|
|
150
148
|
redirect: "error"
|
|
151
149
|
});
|
|
152
|
-
y.ok && (a =
|
|
150
|
+
y.ok && (a = C(await y.text()));
|
|
153
151
|
}
|
|
154
152
|
}
|
|
155
153
|
const h = T(o.audioContent, e);
|
|
@@ -163,7 +161,7 @@ var I = (t) => {
|
|
|
163
161
|
};
|
|
164
162
|
}
|
|
165
163
|
}
|
|
166
|
-
},
|
|
164
|
+
}, N = class {
|
|
167
165
|
constructor(t, e) {
|
|
168
166
|
d(this, "config", void 0), d(this, "adapter", void 0), d(this, "currentAudio", null), d(this, "pausedState", !1), d(this, "wordTimings", []), d(this, "highlightInterval", null), d(this, "intentionallyStopped", !1), d(this, "activeSynthesisController", null), d(this, "synthesisRunId", 0), d(this, "telemetryReporter", void 0), d(this, "onWordBoundary", void 0), this.config = t, this.adapter = e, this.telemetryReporter = I(t);
|
|
169
167
|
}
|
|
@@ -390,9 +388,12 @@ var I = (t) => {
|
|
|
390
388
|
return this.pausedState;
|
|
391
389
|
}
|
|
392
390
|
updateSettings(t) {
|
|
393
|
-
t.rate !== void 0 && (this.config.rate = t.rate
|
|
391
|
+
t.rate !== void 0 && (this.config.rate = t.rate, this.config.providerOptions = {
|
|
392
|
+
...this.config.providerOptions || {},
|
|
393
|
+
speedRate: E(Number(t.rate))
|
|
394
|
+
}), t.pitch !== void 0 && (this.config.pitch = t.pitch), t.voice !== void 0 && (this.config.voice = t.voice);
|
|
394
395
|
}
|
|
395
|
-
},
|
|
396
|
+
}, F = class {
|
|
396
397
|
constructor() {
|
|
397
398
|
d(this, "providerId", "server-tts"), d(this, "providerName", "Server TTS"), d(this, "version", "1.0.0"), d(this, "config", null), d(this, "adapter", null), d(this, "telemetryReporter", void 0);
|
|
398
399
|
}
|
|
@@ -408,7 +409,7 @@ var I = (t) => {
|
|
|
408
409
|
if (!e.apiEndpoint) throw new Error("apiEndpoint is required for ServerTTSProvider");
|
|
409
410
|
this.config = e, this.telemetryReporter = I(e);
|
|
410
411
|
const r = b(e);
|
|
411
|
-
if (this.adapter =
|
|
412
|
+
if (this.adapter = x[r], e.validateEndpoint) {
|
|
412
413
|
const i = Date.now();
|
|
413
414
|
if (await this.emitTelemetry("pie-tool-backend-call-start", {
|
|
414
415
|
toolId: "tts",
|
|
@@ -430,17 +431,17 @@ var I = (t) => {
|
|
|
430
431
|
duration: Date.now() - i
|
|
431
432
|
});
|
|
432
433
|
}
|
|
433
|
-
return new
|
|
434
|
+
return new N(e, this.adapter);
|
|
434
435
|
}
|
|
435
436
|
async testAPIAvailability() {
|
|
436
437
|
if (!this.config || !this.adapter) return !1;
|
|
437
438
|
try {
|
|
438
439
|
const t = { ...this.config.headers };
|
|
439
440
|
this.config.authToken && (t.Authorization = `Bearer ${this.config.authToken}`);
|
|
440
|
-
const e = new AbortController(), r = setTimeout(() => e.abort(), 5e3), i =
|
|
441
|
+
const e = new AbortController(), r = setTimeout(() => e.abort(), 5e3), i = L(this.config, this.adapter.id);
|
|
441
442
|
if (i === "none")
|
|
442
443
|
return clearTimeout(r), !0;
|
|
443
|
-
const o = i === "voices" ?
|
|
444
|
+
const o = i === "voices" ? U(this.config) : this.adapter.resolveSynthesisUrl(this.config), s = i === "voices" ? "GET" : "OPTIONS";
|
|
444
445
|
try {
|
|
445
446
|
const a = await fetch(o, {
|
|
446
447
|
method: s,
|
|
@@ -478,8 +479,8 @@ var I = (t) => {
|
|
|
478
479
|
supportsVoiceSelection: !0,
|
|
479
480
|
supportsRateControl: !0,
|
|
480
481
|
supportsPitchControl: !1,
|
|
481
|
-
supportsSSML:
|
|
482
|
-
maxTextLength:
|
|
482
|
+
supportsSSML: O(this.config),
|
|
483
|
+
maxTextLength: M[t]
|
|
483
484
|
};
|
|
484
485
|
}
|
|
485
486
|
destroy() {
|
|
@@ -487,5 +488,5 @@ var I = (t) => {
|
|
|
487
488
|
}
|
|
488
489
|
};
|
|
489
490
|
export {
|
|
490
|
-
|
|
491
|
+
F as ServerTTSProvider
|
|
491
492
|
};
|