@pie-players/pie-section-player 0.2.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 +599 -0
- package/dist/index.d.ts +2 -0
- package/dist/pie-section-player.d.ts +2 -0
- package/dist/pie-section-player.d.ts.map +1 -0
- package/dist/pie-section-player.js +235068 -0
- package/dist/pie-section-player.js.map +1 -0
- package/dist/utils/z-index.d.ts +12 -0
- package/dist/utils/z-index.d.ts.map +1 -0
- package/package.json +62 -0
package/README.md
ADDED
|
@@ -0,0 +1,599 @@
|
|
|
1
|
+
# @pie-players/pie-section-player
|
|
2
|
+
|
|
3
|
+
A web component for rendering QTI 3.0 assessment sections with passages and items.
|
|
4
|
+
|
|
5
|
+
## Live Demos
|
|
6
|
+
|
|
7
|
+
🎯 **[View Interactive Demos](./demos/)** - Working examples with full code
|
|
8
|
+
|
|
9
|
+
- **[TTS Integration Demo](./demos/tts-integration-demo.html)** 🆕 - Assessment Toolkit TTS service integration
|
|
10
|
+
- **[Paired Passages Demo](./demos/paired-passages-urban-gardens.html)** - Complete QTI 3.0 paired passages with PIE elements
|
|
11
|
+
- **[Basic Demo](./demos/basic-demo.html)** - Simple introduction with one passage and three items
|
|
12
|
+
- **[Original Demo](./demo.html)** - Proof-of-concept from initial development
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
### Option 1: CDN (No Build Step)
|
|
17
|
+
|
|
18
|
+
Load directly from a CDN in your browser - no npm install or build step required:
|
|
19
|
+
|
|
20
|
+
```html
|
|
21
|
+
<!DOCTYPE html>
|
|
22
|
+
<html>
|
|
23
|
+
<head>
|
|
24
|
+
<script type="module">
|
|
25
|
+
// Import from jsdelivr CDN
|
|
26
|
+
import 'https://cdn.jsdelivr.net/npm/@pie-players/pie-section-player/dist/pie-section-player.js';
|
|
27
|
+
import 'https://cdn.jsdelivr.net/npm/@pie-players/pie-esm-player/dist/pie-esm-player.js';
|
|
28
|
+
</script>
|
|
29
|
+
</head>
|
|
30
|
+
<body>
|
|
31
|
+
<pie-section-player id="player"></pie-section-player>
|
|
32
|
+
|
|
33
|
+
<script type="module">
|
|
34
|
+
const player = document.getElementById('player');
|
|
35
|
+
player.section = { /* your section data */ };
|
|
36
|
+
player.mode = 'gather';
|
|
37
|
+
</script>
|
|
38
|
+
</body>
|
|
39
|
+
</html>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**Alternative CDNs:**
|
|
43
|
+
|
|
44
|
+
- **unpkg**: `https://unpkg.com/@pie-players/pie-section-player@latest/dist/pie-section-player.js`
|
|
45
|
+
- **esm.sh**: `https://esm.sh/@pie-players/pie-section-player` (auto-resolves dependencies)
|
|
46
|
+
|
|
47
|
+
### Option 2: NPM (For Build Tools)
|
|
48
|
+
|
|
49
|
+
Install via npm for use with build tools (Vite, Webpack, etc.):
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
npm install @pie-players/pie-section-player
|
|
53
|
+
# or
|
|
54
|
+
bun add @pie-players/pie-section-player
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**Note:** The section player requires the following peer dependencies for tool integration:
|
|
58
|
+
|
|
59
|
+
- `@pie-players/pie-tool-answer-eliminator` - Answer eliminator tool for test-taking strategies
|
|
60
|
+
- `@pie-players/pie-tool-tts-inline` - Inline TTS tool for accessibility
|
|
61
|
+
|
|
62
|
+
These are automatically resolved when using the section player as a library dependency.
|
|
63
|
+
|
|
64
|
+
## Usage
|
|
65
|
+
|
|
66
|
+
### As Web Component (Vanilla JS/HTML)
|
|
67
|
+
|
|
68
|
+
```html
|
|
69
|
+
<!DOCTYPE html>
|
|
70
|
+
<html>
|
|
71
|
+
<head>
|
|
72
|
+
<script type="module">
|
|
73
|
+
import '@pie-players/pie-section-player';
|
|
74
|
+
import '@pie-players/pie-esm-player'; // Required for rendering passages/items
|
|
75
|
+
</script>
|
|
76
|
+
</head>
|
|
77
|
+
<body>
|
|
78
|
+
<pie-section-player id="player"></pie-section-player>
|
|
79
|
+
|
|
80
|
+
<script type="module">
|
|
81
|
+
const player = document.getElementById('player');
|
|
82
|
+
|
|
83
|
+
// Set section data
|
|
84
|
+
player.section = {
|
|
85
|
+
identifier: 'section-1',
|
|
86
|
+
keepTogether: true, // Page mode: all items visible
|
|
87
|
+
rubricBlocks: [
|
|
88
|
+
{
|
|
89
|
+
view: 'candidate',
|
|
90
|
+
use: 'passage',
|
|
91
|
+
passage: {
|
|
92
|
+
id: 'passage-1',
|
|
93
|
+
name: 'Reading Passage',
|
|
94
|
+
config: {
|
|
95
|
+
markup: '<reading-passage id="p1"></reading-passage>',
|
|
96
|
+
elements: {
|
|
97
|
+
'reading-passage': '@pie-element/reading-passage@latest'
|
|
98
|
+
},
|
|
99
|
+
models: [{
|
|
100
|
+
id: 'p1',
|
|
101
|
+
element: 'reading-passage',
|
|
102
|
+
content: '<p>Your passage content...</p>'
|
|
103
|
+
}]
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
],
|
|
108
|
+
assessmentItemRefs: [
|
|
109
|
+
{
|
|
110
|
+
identifier: 'q1',
|
|
111
|
+
item: {
|
|
112
|
+
id: 'item-1',
|
|
113
|
+
name: 'Question 1',
|
|
114
|
+
config: {
|
|
115
|
+
markup: '<multiple-choice id="mc1"></multiple-choice>',
|
|
116
|
+
elements: {
|
|
117
|
+
'multiple-choice': '@pie-element/multiple-choice@latest'
|
|
118
|
+
},
|
|
119
|
+
models: [{
|
|
120
|
+
id: 'mc1',
|
|
121
|
+
element: 'multiple-choice',
|
|
122
|
+
prompt: 'What is 2 + 2?',
|
|
123
|
+
choices: [/*...*/]
|
|
124
|
+
}]
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
]
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
// Set mode
|
|
132
|
+
player.mode = 'gather'; // or 'view', 'evaluate', 'author'
|
|
133
|
+
|
|
134
|
+
// Listen to events
|
|
135
|
+
player.addEventListener('section-loaded', (e) => {
|
|
136
|
+
console.log('Section loaded:', e.detail);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
player.addEventListener('item-changed', (e) => {
|
|
140
|
+
console.log('Item changed:', e.detail);
|
|
141
|
+
});
|
|
142
|
+
</script>
|
|
143
|
+
</body>
|
|
144
|
+
</html>
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### As Svelte Component
|
|
148
|
+
|
|
149
|
+
```svelte
|
|
150
|
+
<script>
|
|
151
|
+
import PieSectionPlayer from '@pie-players/pie-section-player';
|
|
152
|
+
|
|
153
|
+
let section = {
|
|
154
|
+
identifier: 'section-1',
|
|
155
|
+
keepTogether: false, // Item mode: one at a time
|
|
156
|
+
assessmentItemRefs: [/* items */]
|
|
157
|
+
};
|
|
158
|
+
</script>
|
|
159
|
+
|
|
160
|
+
<PieSectionPlayer
|
|
161
|
+
{section}
|
|
162
|
+
mode="gather"
|
|
163
|
+
view="candidate"
|
|
164
|
+
bundleHost="https://cdn.pie.org"
|
|
165
|
+
/>
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### In React
|
|
169
|
+
|
|
170
|
+
```jsx
|
|
171
|
+
import '@pie-players/pie-section-player';
|
|
172
|
+
|
|
173
|
+
function AssessmentSection({ section }) {
|
|
174
|
+
const playerRef = useRef(null);
|
|
175
|
+
|
|
176
|
+
useEffect(() => {
|
|
177
|
+
if (playerRef.current) {
|
|
178
|
+
playerRef.current.section = section;
|
|
179
|
+
playerRef.current.mode = 'gather';
|
|
180
|
+
}
|
|
181
|
+
}, [section]);
|
|
182
|
+
|
|
183
|
+
return <pie-section-player ref={playerRef} />;
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Props/Attributes
|
|
188
|
+
|
|
189
|
+
| Prop | Type | Default | Description |
|
|
190
|
+
|------|------|---------|-------------|
|
|
191
|
+
| `section` | `QtiAssessmentSection` | `null` | Section data with passages and items |
|
|
192
|
+
| `mode` | `'gather' \| 'view' \| 'evaluate' \| 'author'` | `'gather'` | Player mode |
|
|
193
|
+
| `view` | `'candidate' \| 'scorer' \| 'author' \| ...` | `'candidate'` | Current view (filters rubricBlocks) |
|
|
194
|
+
| `item-sessions` | `Record<string, any>` | `{}` | Item sessions for restoration |
|
|
195
|
+
| `bundle-host` | `string` | `''` | CDN host for PIE bundles |
|
|
196
|
+
| `esm-cdn-url` | `string` | `'https://esm.sh'` | ESM CDN URL |
|
|
197
|
+
| `custom-classname` | `string` | `''` | Custom CSS class |
|
|
198
|
+
| `debug` | `string \| boolean` | `''` | Debug mode |
|
|
199
|
+
|
|
200
|
+
## Events
|
|
201
|
+
|
|
202
|
+
### `section-loaded`
|
|
203
|
+
|
|
204
|
+
Fired when the section is loaded and ready.
|
|
205
|
+
|
|
206
|
+
```javascript
|
|
207
|
+
player.addEventListener('section-loaded', (e) => {
|
|
208
|
+
console.log(e.detail);
|
|
209
|
+
// {
|
|
210
|
+
// sectionId: 'section-1',
|
|
211
|
+
// itemCount: 3,
|
|
212
|
+
// passageCount: 2,
|
|
213
|
+
// isPageMode: true
|
|
214
|
+
// }
|
|
215
|
+
});
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### `item-changed`
|
|
219
|
+
|
|
220
|
+
Fired when the current item changes (item mode only).
|
|
221
|
+
|
|
222
|
+
```javascript
|
|
223
|
+
player.addEventListener('item-changed', (e) => {
|
|
224
|
+
console.log(e.detail);
|
|
225
|
+
// {
|
|
226
|
+
// previousItemId: 'item-1',
|
|
227
|
+
// currentItemId: 'item-2',
|
|
228
|
+
// itemIndex: 1,
|
|
229
|
+
// totalItems: 3,
|
|
230
|
+
// timestamp: 1234567890
|
|
231
|
+
// }
|
|
232
|
+
});
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### `section-complete`
|
|
236
|
+
|
|
237
|
+
Fired when all items in the section are completed.
|
|
238
|
+
|
|
239
|
+
### `player-error`
|
|
240
|
+
|
|
241
|
+
Fired when an error occurs.
|
|
242
|
+
|
|
243
|
+
## Performance Optimization
|
|
244
|
+
|
|
245
|
+
The section player automatically optimizes element loading for sections with multiple items using the same PIE elements.
|
|
246
|
+
|
|
247
|
+
### Element Aggregation
|
|
248
|
+
|
|
249
|
+
**Problem**: When rendering multiple items that use the same PIE elements (e.g., multiple multiple-choice questions), the old approach would load the same element bundle multiple times.
|
|
250
|
+
|
|
251
|
+
**Solution**: The section player now:
|
|
252
|
+
1. Aggregates all unique elements from all items before rendering
|
|
253
|
+
2. Loads each element bundle once
|
|
254
|
+
3. Items initialize from the pre-loaded registry
|
|
255
|
+
|
|
256
|
+
**Benefits:**
|
|
257
|
+
- **50%+ faster loading** for sections with repeated elements
|
|
258
|
+
- **Fewer network requests** - one bundle request per unique element (not per item)
|
|
259
|
+
- **Automatic** - no configuration needed, works out of the box
|
|
260
|
+
- **Backward compatible** - existing code works without changes
|
|
261
|
+
|
|
262
|
+
### Example Performance
|
|
263
|
+
|
|
264
|
+
Section with 5 items (3 multiple-choice, 2 hotspot):
|
|
265
|
+
|
|
266
|
+
**Before**: 5 loader calls → 2 unique + 3 cached = ~550ms
|
|
267
|
+
**After**: 1 loader call → 2 unique = ~250ms
|
|
268
|
+
|
|
269
|
+
**50% faster load time**
|
|
270
|
+
|
|
271
|
+
### Element Version Conflicts
|
|
272
|
+
|
|
273
|
+
If items require different versions of the same element, an error is thrown:
|
|
274
|
+
|
|
275
|
+
```
|
|
276
|
+
Element version conflict: pie-multiple-choice requires both
|
|
277
|
+
@pie-element/multiple-choice@10.0.0 and @pie-element/multiple-choice@11.0.1
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
**Solution**: Normalize element versions across items in your content authoring system.
|
|
281
|
+
|
|
282
|
+
### Technical Details
|
|
283
|
+
|
|
284
|
+
For implementation details and architecture, see:
|
|
285
|
+
- [Element Loader Design](../../docs/architecture/ELEMENT_LOADER_DESIGN.md)
|
|
286
|
+
- [Generalized Loader Architecture](../../docs/architecture/GENERALIZED_LOADER_ARCHITECTURE.md)
|
|
287
|
+
|
|
288
|
+
## Rendering Modes
|
|
289
|
+
|
|
290
|
+
### Page Mode (`keepTogether: true`)
|
|
291
|
+
|
|
292
|
+
All items and passages are visible simultaneously. Ideal for:
|
|
293
|
+
- Paired passages
|
|
294
|
+
- Multi-item pages
|
|
295
|
+
- Print assessments
|
|
296
|
+
|
|
297
|
+
```javascript
|
|
298
|
+
section.keepTogether = true;
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### Item Mode (`keepTogether: false`)
|
|
302
|
+
|
|
303
|
+
One item at a time with navigation controls. Ideal for:
|
|
304
|
+
- Linear assessments
|
|
305
|
+
- Adaptive tests
|
|
306
|
+
- Item-by-item delivery
|
|
307
|
+
|
|
308
|
+
```javascript
|
|
309
|
+
section.keepTogether = false;
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
## Methods
|
|
313
|
+
|
|
314
|
+
### `navigateNext()`
|
|
315
|
+
|
|
316
|
+
Navigate to the next item (item mode only).
|
|
317
|
+
|
|
318
|
+
```javascript
|
|
319
|
+
player.navigateNext();
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
### `navigatePrevious()`
|
|
323
|
+
|
|
324
|
+
Navigate to the previous item (item mode only).
|
|
325
|
+
|
|
326
|
+
```javascript
|
|
327
|
+
player.navigatePrevious();
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
### `getNavigationState()`
|
|
331
|
+
|
|
332
|
+
Get the current navigation state.
|
|
333
|
+
|
|
334
|
+
```javascript
|
|
335
|
+
const state = player.getNavigationState();
|
|
336
|
+
// {
|
|
337
|
+
// currentIndex: 0,
|
|
338
|
+
// totalItems: 3,
|
|
339
|
+
// canNext: true,
|
|
340
|
+
// canPrevious: false,
|
|
341
|
+
// isLoading: false
|
|
342
|
+
// }
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
## Passage Handling
|
|
346
|
+
|
|
347
|
+
The section player extracts passages from two sources:
|
|
348
|
+
|
|
349
|
+
1. **Section-level passages** (QTI 3.0 style) - `rubricBlocks` where `use="passage"`
|
|
350
|
+
2. **Item-linked passages** (legacy PIE) - `item.passage`
|
|
351
|
+
|
|
352
|
+
Passages are automatically deduplicated by ID.
|
|
353
|
+
|
|
354
|
+
## Assessment Toolkit Integration
|
|
355
|
+
|
|
356
|
+
The section player integrates with the [PIE Assessment Toolkit](../assessment-toolkit/) for centralized service management via **ToolkitCoordinator**.
|
|
357
|
+
|
|
358
|
+
### Using ToolkitCoordinator (Recommended)
|
|
359
|
+
|
|
360
|
+
The **ToolkitCoordinator** provides a single entry point for all toolkit services, simplifying initialization:
|
|
361
|
+
|
|
362
|
+
```javascript
|
|
363
|
+
import { ToolkitCoordinator } from '@pie-players/pie-assessment-toolkit';
|
|
364
|
+
|
|
365
|
+
// Create coordinator with configuration
|
|
366
|
+
const coordinator = new ToolkitCoordinator({
|
|
367
|
+
assessmentId: 'my-assessment',
|
|
368
|
+
tools: {
|
|
369
|
+
tts: { enabled: true, defaultVoice: 'en-US' },
|
|
370
|
+
answerEliminator: { enabled: true }
|
|
371
|
+
},
|
|
372
|
+
accessibility: {
|
|
373
|
+
catalogs: assessment.accessibilityCatalogs || [],
|
|
374
|
+
language: 'en-US'
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
// Pass to section player as JavaScript property
|
|
379
|
+
const player = document.getElementById('player');
|
|
380
|
+
player.toolkitCoordinator = coordinator;
|
|
381
|
+
player.section = mySection;
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
**Benefits:**
|
|
385
|
+
- **Single initialization point**: One coordinator instead of 5+ services
|
|
386
|
+
- **Centralized configuration**: Tool settings in one place
|
|
387
|
+
- **Automatic service wiring**: Services work together automatically
|
|
388
|
+
- **Element-level tool state**: Answer eliminations, highlights tracked per element
|
|
389
|
+
- **State separation**: Tool state (ephemeral) separate from session data (persistent)
|
|
390
|
+
|
|
391
|
+
### What the Coordinator Provides
|
|
392
|
+
|
|
393
|
+
The coordinator owns and orchestrates all toolkit services:
|
|
394
|
+
|
|
395
|
+
```typescript
|
|
396
|
+
coordinator.ttsService // Text-to-speech service
|
|
397
|
+
coordinator.toolCoordinator // Tool visibility and z-index management
|
|
398
|
+
coordinator.highlightCoordinator // TTS and annotation highlights
|
|
399
|
+
coordinator.elementToolStateStore // Element-level tool state (answer eliminator, etc.)
|
|
400
|
+
coordinator.catalogResolver // QTI 3.0 accessibility catalogs for SSML
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
### Automatic Features
|
|
404
|
+
|
|
405
|
+
When using the coordinator, the section player automatically:
|
|
406
|
+
|
|
407
|
+
1. **Extracts services** from the coordinator
|
|
408
|
+
2. **Generates section ID** (from `section.identifier` or auto-generated)
|
|
409
|
+
3. **Passes IDs + services** through component hierarchy
|
|
410
|
+
4. **Extracts SSML** from embedded `<speak>` tags
|
|
411
|
+
5. **Manages catalog lifecycle** (add on item load, clear on navigation)
|
|
412
|
+
6. **Renders TTS tools** in passage/item headers
|
|
413
|
+
7. **Tracks element-level state** with global uniqueness
|
|
414
|
+
|
|
415
|
+
### Standalone Sections (No Coordinator)
|
|
416
|
+
|
|
417
|
+
If no coordinator is provided, the section player creates a default one:
|
|
418
|
+
|
|
419
|
+
```javascript
|
|
420
|
+
// No coordinator provided - section player creates default
|
|
421
|
+
const player = document.getElementById('player');
|
|
422
|
+
player.section = mySection;
|
|
423
|
+
|
|
424
|
+
// Internally creates:
|
|
425
|
+
// new ToolkitCoordinator({
|
|
426
|
+
// assessmentId: 'anon_...', // auto-generated
|
|
427
|
+
// tools: { tts: { enabled: true }, answerEliminator: { enabled: true } }
|
|
428
|
+
// })
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
### Advanced: Manual Service Integration (Legacy)
|
|
432
|
+
|
|
433
|
+
For backward compatibility or advanced scenarios, you can still pass services individually:
|
|
434
|
+
|
|
435
|
+
```javascript
|
|
436
|
+
import {
|
|
437
|
+
TTSService,
|
|
438
|
+
BrowserTTSProvider,
|
|
439
|
+
ToolCoordinator,
|
|
440
|
+
HighlightCoordinator,
|
|
441
|
+
AccessibilityCatalogResolver,
|
|
442
|
+
ElementToolStateStore
|
|
443
|
+
} from '@pie-players/pie-assessment-toolkit';
|
|
444
|
+
|
|
445
|
+
// Initialize each service
|
|
446
|
+
const ttsService = new TTSService();
|
|
447
|
+
const toolCoordinator = new ToolCoordinator();
|
|
448
|
+
const highlightCoordinator = new HighlightCoordinator();
|
|
449
|
+
const elementToolStateStore = new ElementToolStateStore();
|
|
450
|
+
const catalogResolver = new AccessibilityCatalogResolver([], 'en-US');
|
|
451
|
+
|
|
452
|
+
await ttsService.initialize(new BrowserTTSProvider());
|
|
453
|
+
ttsService.setCatalogResolver(catalogResolver);
|
|
454
|
+
|
|
455
|
+
// Pass services individually
|
|
456
|
+
const player = document.getElementById('player');
|
|
457
|
+
player.ttsService = ttsService;
|
|
458
|
+
player.toolCoordinator = toolCoordinator;
|
|
459
|
+
player.highlightCoordinator = highlightCoordinator;
|
|
460
|
+
player.elementToolStateStore = elementToolStateStore;
|
|
461
|
+
player.catalogResolver = catalogResolver;
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
**Note:** Using ToolkitCoordinator is recommended for most use cases.
|
|
465
|
+
|
|
466
|
+
### SSML Extraction
|
|
467
|
+
|
|
468
|
+
✅ **Automatic SSML Extraction**: The section player automatically extracts embedded `<speak>` tags from item and passage content, converting them into QTI 3.0 accessibility catalogs at runtime.
|
|
469
|
+
|
|
470
|
+
**Benefits:**
|
|
471
|
+
- Authors embed SSML directly in content (no separate catalog files)
|
|
472
|
+
- Proper pronunciation of technical terms and math expressions
|
|
473
|
+
- Emphasis and pacing control via SSML
|
|
474
|
+
- Automatic catalog generation and registration
|
|
475
|
+
|
|
476
|
+
**Example:**
|
|
477
|
+
|
|
478
|
+
Authors create content with embedded SSML:
|
|
479
|
+
```typescript
|
|
480
|
+
{
|
|
481
|
+
config: {
|
|
482
|
+
models: [{
|
|
483
|
+
prompt: `<div>
|
|
484
|
+
<speak>Solve <prosody rate="slow">x squared, plus two x</prosody>.</speak>
|
|
485
|
+
<p>Solve x² + 2x = 0</p>
|
|
486
|
+
</div>`
|
|
487
|
+
}]
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
At runtime, the section player:
|
|
493
|
+
1. Extracts SSML and generates catalog entry
|
|
494
|
+
2. Cleans visual markup (removes SSML tags)
|
|
495
|
+
3. Adds `data-catalog-id` attribute for TTS lookup
|
|
496
|
+
4. Registers catalog with AccessibilityCatalogResolver
|
|
497
|
+
|
|
498
|
+
**Result:** TTS uses proper math pronunciation while visual display shows clean HTML.
|
|
499
|
+
|
|
500
|
+
See [TTS-INTEGRATION.md](./TTS-INTEGRATION.md) for complete details.
|
|
501
|
+
|
|
502
|
+
### Element-Level Tool State
|
|
503
|
+
|
|
504
|
+
Tool state (answer eliminations, highlights, etc.) is tracked at the **element level** using globally unique composite keys:
|
|
505
|
+
|
|
506
|
+
**Format**: `${assessmentId}:${sectionId}:${itemId}:${elementId}`
|
|
507
|
+
|
|
508
|
+
**Example**: `"demo-assessment:section-1:question-1:mc1"`
|
|
509
|
+
|
|
510
|
+
**Benefits:**
|
|
511
|
+
- Each PIE element has independent tool state
|
|
512
|
+
- No cross-item contamination
|
|
513
|
+
- Persists across section navigation
|
|
514
|
+
- Separate from PIE session data (not sent to server)
|
|
515
|
+
|
|
516
|
+
### Service Flow
|
|
517
|
+
|
|
518
|
+
Services are passed through the component hierarchy with IDs:
|
|
519
|
+
|
|
520
|
+
```
|
|
521
|
+
SectionPlayer (coordinator → services + assessmentId + sectionId)
|
|
522
|
+
↓
|
|
523
|
+
PageModeLayout / ItemModeLayout (passes assessmentId + sectionId + services)
|
|
524
|
+
↓
|
|
525
|
+
PassageRenderer / ItemRenderer (uses item.id, passes assessmentId + sectionId + services)
|
|
526
|
+
↓
|
|
527
|
+
QuestionToolBar (generates globalElementId)
|
|
528
|
+
↓
|
|
529
|
+
Tool web components (use globalElementId for state)
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
### Demos
|
|
533
|
+
|
|
534
|
+
See the [section-demos](../../apps/section-demos/) for complete examples:
|
|
535
|
+
|
|
536
|
+
- **Three Questions Demo**: Element-level answer eliminator with ToolkitCoordinator
|
|
537
|
+
- **TTS Integration Demo**: Coordinator with TTS service
|
|
538
|
+
- **Paired Passages Demo**: Multi-section with cross-section state persistence
|
|
539
|
+
|
|
540
|
+
## Styling
|
|
541
|
+
|
|
542
|
+
The web component uses Shadow DOM mode `'none'`, so you can style it with global CSS:
|
|
543
|
+
|
|
544
|
+
```css
|
|
545
|
+
.pie-section-player {
|
|
546
|
+
max-width: 1200px;
|
|
547
|
+
margin: 0 auto;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
.pie-section-player .section-passages {
|
|
551
|
+
background: #f5f5f5;
|
|
552
|
+
padding: 1rem;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
.pie-section-player .item-container {
|
|
556
|
+
border: 1px solid #ddd;
|
|
557
|
+
margin-bottom: 1rem;
|
|
558
|
+
}
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
## CDN Usage
|
|
562
|
+
|
|
563
|
+
```html
|
|
564
|
+
<script type="module">
|
|
565
|
+
import 'https://cdn.jsdelivr.net/npm/@pie-players/pie-section-player/dist/pie-section-player.js';
|
|
566
|
+
</script>
|
|
567
|
+
|
|
568
|
+
<pie-section-player id="player"></pie-section-player>
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
## TypeScript
|
|
572
|
+
|
|
573
|
+
Full TypeScript support included:
|
|
574
|
+
|
|
575
|
+
```typescript
|
|
576
|
+
import type { QtiAssessmentSection } from '@pie-players/pie-players-shared/types';
|
|
577
|
+
import PieSectionPlayer from '@pie-players/pie-section-player';
|
|
578
|
+
|
|
579
|
+
const section: QtiAssessmentSection = {
|
|
580
|
+
identifier: 'section-1',
|
|
581
|
+
keepTogether: true,
|
|
582
|
+
// ...
|
|
583
|
+
};
|
|
584
|
+
|
|
585
|
+
const player = document.querySelector('pie-section-player') as PieSectionPlayer;
|
|
586
|
+
player.section = section;
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
## Browser Support
|
|
590
|
+
|
|
591
|
+
- Chrome/Edge 90+
|
|
592
|
+
- Firefox 88+
|
|
593
|
+
- Safari 14+
|
|
594
|
+
|
|
595
|
+
Requires ES2020+ support (native ES modules, optional chaining, nullish coalescing).
|
|
596
|
+
|
|
597
|
+
## License
|
|
598
|
+
|
|
599
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pie-section-player.d.ts","sourceRoot":"","sources":["../src/pie-section-player.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAIxE,OAAO,2BAA2B,CAAC;AAInC,OAAO,yCAAyC,CAAC;AACjD,OAAO,kCAAkC,CAAC;AAC1C,OAAO,yCAAyC,CAAC;AACjD,OAAO,kCAAkC,CAAC"}
|