@pie-players/pie-assessment-toolkit 0.3.59 → 0.3.61
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/dist/components/ItemToolBar.custom-element.js +3 -1
- package/dist/components/PieAssessmentToolkit.custom-element.js +130 -4
- package/dist/components/SectionToolBar.custom-element.js +3 -1
- package/dist/services/HighlightCoordinator.js +110 -0
- package/dist/services/HighlightCoordinator.js.map +1 -1
- package/dist/services/ToolCoordinator.d.ts +24 -0
- package/dist/services/ToolCoordinator.js +46 -3
- package/dist/services/ToolCoordinator.js.map +1 -1
- package/dist/services/interfaces.d.ts +7 -1
- package/dist/services/interfaces.js.map +1 -1
- package/dist/services/tool-context.js +2 -0
- package/dist/services/tool-context.js.map +1 -1
- package/package.json +7 -7
|
@@ -5994,9 +5994,11 @@ function hasChoiceInteraction(context) {
|
|
|
5994
5994
|
"pie-multiple-choice",
|
|
5995
5995
|
"pie-inline-choice",
|
|
5996
5996
|
"pie-select-text",
|
|
5997
|
+
"pie-ebsr",
|
|
5997
5998
|
"multiple-choice",
|
|
5998
5999
|
"inline-choice",
|
|
5999
|
-
"select-text"
|
|
6000
|
+
"select-text",
|
|
6001
|
+
"ebsr"
|
|
6000
6002
|
];
|
|
6001
6003
|
if (isElementContext(context)) {
|
|
6002
6004
|
const config = context.item.config;
|
|
@@ -17693,9 +17693,11 @@ function hasChoiceInteraction(context) {
|
|
|
17693
17693
|
"pie-multiple-choice",
|
|
17694
17694
|
"pie-inline-choice",
|
|
17695
17695
|
"pie-select-text",
|
|
17696
|
+
"pie-ebsr",
|
|
17696
17697
|
"multiple-choice",
|
|
17697
17698
|
"inline-choice",
|
|
17698
|
-
"select-text"
|
|
17699
|
+
"select-text",
|
|
17700
|
+
"ebsr"
|
|
17699
17701
|
];
|
|
17700
17702
|
if (isElementContext(context)) {
|
|
17701
17703
|
const config = context.item.config;
|
|
@@ -21124,6 +21126,116 @@ class HighlightCoordinator {
|
|
|
21124
21126
|
text-underline-offset: 2px;
|
|
21125
21127
|
color: inherit;
|
|
21126
21128
|
}
|
|
21129
|
+
|
|
21130
|
+
/* The blocks below cover annotation swatches, print, and reduced motion.
|
|
21131
|
+
They deliberately do NOT retune the TTS layers: applyAdaptiveTTSStyle()
|
|
21132
|
+
writes the pie-tts custom properties inline on documentElement on every
|
|
21133
|
+
paint and on theme change, so a media query that only varies a var()
|
|
21134
|
+
fallback for those can never take effect. Annotation colours have no such
|
|
21135
|
+
adaptive path -- they are fixed swatches a student chose -- so a media
|
|
21136
|
+
query is the only way to adjust them, and each keeps its var() so a host
|
|
21137
|
+
override still wins.
|
|
21138
|
+
|
|
21139
|
+
The names above are spelled without their leading dashes on purpose.
|
|
21140
|
+
check-theme-tokens scans comments, and its token pattern stops at the
|
|
21141
|
+
first non-alphanumeric character, so writing the wildcard form reads as
|
|
21142
|
+
consumption of a shorter token that is not in the registry and fails the
|
|
21143
|
+
check. Spell property names in full, or omit the dashes. */
|
|
21144
|
+
|
|
21145
|
+
@media (prefers-color-scheme: dark) {
|
|
21146
|
+
::highlight(annotation-yellow) {
|
|
21147
|
+
background-color: var(--pie-annotation-yellow-highlight, rgba(139, 117, 0, 0.6));
|
|
21148
|
+
}
|
|
21149
|
+
::highlight(annotation-green) {
|
|
21150
|
+
background-color: var(--pie-annotation-green-highlight, rgba(45, 92, 63, 0.6));
|
|
21151
|
+
}
|
|
21152
|
+
::highlight(annotation-blue) {
|
|
21153
|
+
background-color: var(--pie-annotation-blue-highlight, rgba(0, 102, 170, 0.6));
|
|
21154
|
+
}
|
|
21155
|
+
::highlight(annotation-pink) {
|
|
21156
|
+
background-color: var(--pie-annotation-pink-highlight, rgba(139, 51, 74, 0.6));
|
|
21157
|
+
}
|
|
21158
|
+
::highlight(annotation-orange) {
|
|
21159
|
+
background-color: var(--pie-annotation-orange-highlight, rgba(154, 99, 0, 0.6));
|
|
21160
|
+
}
|
|
21161
|
+
::highlight(annotation-underline) {
|
|
21162
|
+
text-decoration-color: var(--pie-primary, #4da6ff);
|
|
21163
|
+
}
|
|
21164
|
+
}
|
|
21165
|
+
|
|
21166
|
+
/* WCAG 2.2 SC 1.4.11 non-text contrast: a highlight is the only indication
|
|
21167
|
+
that text is annotated, so the swatches saturate and the underline
|
|
21168
|
+
thickens when the user asks for more contrast.
|
|
21169
|
+
|
|
21170
|
+
The value is "more", not "high". The stylesheet this was recovered from
|
|
21171
|
+
used prefers-contrast: high, which is not a valid value for the feature
|
|
21172
|
+
-- the keywords are no-preference, more, less, custom. An invalid query
|
|
21173
|
+
evaluates to "not all", so that block could never have matched in any
|
|
21174
|
+
browser even had the file been loaded. Verified with matchMedia under
|
|
21175
|
+
emulation: "high" stays false where "more" flips true. */
|
|
21176
|
+
@media (prefers-contrast: more) {
|
|
21177
|
+
::highlight(annotation-yellow) {
|
|
21178
|
+
background-color: var(--pie-annotation-yellow-highlight, rgba(255, 255, 0, 0.7));
|
|
21179
|
+
}
|
|
21180
|
+
::highlight(annotation-green) {
|
|
21181
|
+
background-color: var(--pie-annotation-green-highlight, rgba(0, 255, 127, 0.7));
|
|
21182
|
+
}
|
|
21183
|
+
::highlight(annotation-blue) {
|
|
21184
|
+
background-color: var(--pie-annotation-blue-highlight, rgba(0, 191, 255, 0.7));
|
|
21185
|
+
}
|
|
21186
|
+
::highlight(annotation-pink) {
|
|
21187
|
+
background-color: var(--pie-annotation-pink-highlight, rgba(255, 105, 180, 0.7));
|
|
21188
|
+
}
|
|
21189
|
+
::highlight(annotation-orange) {
|
|
21190
|
+
background-color: var(--pie-annotation-orange-highlight, rgba(255, 165, 0, 0.85));
|
|
21191
|
+
}
|
|
21192
|
+
::highlight(annotation-underline) {
|
|
21193
|
+
text-decoration-thickness: 3px;
|
|
21194
|
+
}
|
|
21195
|
+
}
|
|
21196
|
+
|
|
21197
|
+
@media print {
|
|
21198
|
+
/* TTS highlighting is a transient read-along cue, not content. */
|
|
21199
|
+
::highlight(tts-word),
|
|
21200
|
+
::highlight(tts-sentence),
|
|
21201
|
+
[data-pie-tts-word-element="true"],
|
|
21202
|
+
[data-pie-tts-sentence-element="true"] {
|
|
21203
|
+
background-color: transparent;
|
|
21204
|
+
text-decoration: none;
|
|
21205
|
+
text-shadow: none;
|
|
21206
|
+
border-bottom: none;
|
|
21207
|
+
}
|
|
21208
|
+
|
|
21209
|
+
/* Annotations are student work and must survive printing, but a fill
|
|
21210
|
+
that reads fine on screen can print as an illegible wash, so each
|
|
21211
|
+
becomes an underline that keeps its colour coding. */
|
|
21212
|
+
::highlight(annotation-yellow),
|
|
21213
|
+
::highlight(annotation-green),
|
|
21214
|
+
::highlight(annotation-blue),
|
|
21215
|
+
::highlight(annotation-pink),
|
|
21216
|
+
::highlight(annotation-orange) {
|
|
21217
|
+
background-color: transparent;
|
|
21218
|
+
border-bottom: 2px solid currentColor;
|
|
21219
|
+
}
|
|
21220
|
+
::highlight(annotation-yellow) { border-bottom-color: #ffeb3b; }
|
|
21221
|
+
::highlight(annotation-green) { border-bottom-color: #a6e1c5; }
|
|
21222
|
+
::highlight(annotation-blue) { border-bottom-color: #a7e0f6; }
|
|
21223
|
+
::highlight(annotation-pink) { border-bottom-color: #ff9fae; }
|
|
21224
|
+
::highlight(annotation-orange) { border-bottom-color: #ffa500; }
|
|
21225
|
+
::highlight(annotation-underline) {
|
|
21226
|
+
text-decoration-color: #000;
|
|
21227
|
+
border-bottom: none;
|
|
21228
|
+
}
|
|
21229
|
+
}
|
|
21230
|
+
|
|
21231
|
+
/* text-shadow is a direct property rather than a var(), so unlike the
|
|
21232
|
+
other TTS rules this one is not overridden by the adaptive path. */
|
|
21233
|
+
@media (prefers-reduced-motion: reduce) {
|
|
21234
|
+
::highlight(tts-word),
|
|
21235
|
+
[data-pie-tts-word-element="true"] {
|
|
21236
|
+
text-shadow: none;
|
|
21237
|
+
}
|
|
21238
|
+
}
|
|
21127
21239
|
`;
|
|
21128
21240
|
document.head.appendChild(style);
|
|
21129
21241
|
}
|
|
@@ -21427,6 +21539,7 @@ class ToolCoordinator {
|
|
|
21427
21539
|
tools = new Map;
|
|
21428
21540
|
layerCounters = new Map;
|
|
21429
21541
|
listeners = new Set;
|
|
21542
|
+
visibilityState = new Map;
|
|
21430
21543
|
constructor(config = {}) {
|
|
21431
21544
|
this.config = config;
|
|
21432
21545
|
this.layerCounters.set(ZIndexLayer.BASE, 0);
|
|
@@ -21450,13 +21563,14 @@ class ToolCoordinator {
|
|
|
21450
21563
|
log(`Tool ${id} is already registered`);
|
|
21451
21564
|
return;
|
|
21452
21565
|
}
|
|
21566
|
+
const isVisible = this.visibilityState.get(id) ?? false;
|
|
21453
21567
|
if (!element2) {
|
|
21454
21568
|
this.tools.set(id, {
|
|
21455
21569
|
id,
|
|
21456
21570
|
name,
|
|
21457
21571
|
element: null,
|
|
21458
21572
|
layer,
|
|
21459
|
-
isVisible
|
|
21573
|
+
isVisible,
|
|
21460
21574
|
baseZIndex: layer
|
|
21461
21575
|
});
|
|
21462
21576
|
log("Tool registered without element:", id);
|
|
@@ -21464,6 +21578,7 @@ class ToolCoordinator {
|
|
|
21464
21578
|
}
|
|
21465
21579
|
const baseZIndex = layer + this.getNextLayerOffset(layer);
|
|
21466
21580
|
element2.style.zIndex = String(baseZIndex);
|
|
21581
|
+
element2.style.display = isVisible ? "" : "none";
|
|
21467
21582
|
const mouseDownHandler = () => this.bringToFront(element2);
|
|
21468
21583
|
element2.addEventListener("mousedown", mouseDownHandler);
|
|
21469
21584
|
this.tools.set(id, {
|
|
@@ -21471,10 +21586,13 @@ class ToolCoordinator {
|
|
|
21471
21586
|
name,
|
|
21472
21587
|
element: element2,
|
|
21473
21588
|
layer,
|
|
21474
|
-
isVisible
|
|
21589
|
+
isVisible,
|
|
21475
21590
|
baseZIndex,
|
|
21476
21591
|
mouseDownHandler
|
|
21477
21592
|
});
|
|
21593
|
+
if (isVisible) {
|
|
21594
|
+
this.bringToFront(element2);
|
|
21595
|
+
}
|
|
21478
21596
|
log("Tool registered with element:", id);
|
|
21479
21597
|
}
|
|
21480
21598
|
unregisterTool(id) {
|
|
@@ -21486,6 +21604,10 @@ class ToolCoordinator {
|
|
|
21486
21604
|
}
|
|
21487
21605
|
this.tools.delete(id);
|
|
21488
21606
|
}
|
|
21607
|
+
releaseTool(id) {
|
|
21608
|
+
this.unregisterTool(id);
|
|
21609
|
+
this.visibilityState.delete(id);
|
|
21610
|
+
}
|
|
21489
21611
|
showTool(id) {
|
|
21490
21612
|
const tool = this.tools.get(id);
|
|
21491
21613
|
if (!tool) {
|
|
@@ -21497,6 +21619,7 @@ class ToolCoordinator {
|
|
|
21497
21619
|
this.bringToFront(tool.element);
|
|
21498
21620
|
}
|
|
21499
21621
|
tool.isVisible = true;
|
|
21622
|
+
this.visibilityState.set(id, true);
|
|
21500
21623
|
this.notifyListeners();
|
|
21501
21624
|
}
|
|
21502
21625
|
hideTool(id) {
|
|
@@ -21509,6 +21632,7 @@ class ToolCoordinator {
|
|
|
21509
21632
|
tool.element.style.display = "none";
|
|
21510
21633
|
}
|
|
21511
21634
|
tool.isVisible = false;
|
|
21635
|
+
this.visibilityState.set(id, false);
|
|
21512
21636
|
this.notifyListeners();
|
|
21513
21637
|
}
|
|
21514
21638
|
toggleTool(id) {
|
|
@@ -21527,7 +21651,9 @@ class ToolCoordinator {
|
|
|
21527
21651
|
}
|
|
21528
21652
|
isToolVisible(id) {
|
|
21529
21653
|
const tool = this.tools.get(id);
|
|
21530
|
-
|
|
21654
|
+
if (tool)
|
|
21655
|
+
return tool.isVisible;
|
|
21656
|
+
return this.visibilityState.get(id) ?? false;
|
|
21531
21657
|
}
|
|
21532
21658
|
bringToFront(element2) {
|
|
21533
21659
|
const tool = Array.from(this.tools.values()).find((t) => t.element === element2);
|
|
@@ -10191,9 +10191,11 @@ function hasChoiceInteraction(context) {
|
|
|
10191
10191
|
"pie-multiple-choice",
|
|
10192
10192
|
"pie-inline-choice",
|
|
10193
10193
|
"pie-select-text",
|
|
10194
|
+
"pie-ebsr",
|
|
10194
10195
|
"multiple-choice",
|
|
10195
10196
|
"inline-choice",
|
|
10196
|
-
"select-text"
|
|
10197
|
+
"select-text",
|
|
10198
|
+
"ebsr"
|
|
10197
10199
|
];
|
|
10198
10200
|
if (isElementContext(context)) {
|
|
10199
10201
|
const config = context.item.config;
|
|
@@ -339,6 +339,116 @@ export class HighlightCoordinator {
|
|
|
339
339
|
text-underline-offset: 2px;
|
|
340
340
|
color: inherit;
|
|
341
341
|
}
|
|
342
|
+
|
|
343
|
+
/* The blocks below cover annotation swatches, print, and reduced motion.
|
|
344
|
+
They deliberately do NOT retune the TTS layers: applyAdaptiveTTSStyle()
|
|
345
|
+
writes the pie-tts custom properties inline on documentElement on every
|
|
346
|
+
paint and on theme change, so a media query that only varies a var()
|
|
347
|
+
fallback for those can never take effect. Annotation colours have no such
|
|
348
|
+
adaptive path -- they are fixed swatches a student chose -- so a media
|
|
349
|
+
query is the only way to adjust them, and each keeps its var() so a host
|
|
350
|
+
override still wins.
|
|
351
|
+
|
|
352
|
+
The names above are spelled without their leading dashes on purpose.
|
|
353
|
+
check-theme-tokens scans comments, and its token pattern stops at the
|
|
354
|
+
first non-alphanumeric character, so writing the wildcard form reads as
|
|
355
|
+
consumption of a shorter token that is not in the registry and fails the
|
|
356
|
+
check. Spell property names in full, or omit the dashes. */
|
|
357
|
+
|
|
358
|
+
@media (prefers-color-scheme: dark) {
|
|
359
|
+
::highlight(annotation-yellow) {
|
|
360
|
+
background-color: var(--pie-annotation-yellow-highlight, rgba(139, 117, 0, 0.6));
|
|
361
|
+
}
|
|
362
|
+
::highlight(annotation-green) {
|
|
363
|
+
background-color: var(--pie-annotation-green-highlight, rgba(45, 92, 63, 0.6));
|
|
364
|
+
}
|
|
365
|
+
::highlight(annotation-blue) {
|
|
366
|
+
background-color: var(--pie-annotation-blue-highlight, rgba(0, 102, 170, 0.6));
|
|
367
|
+
}
|
|
368
|
+
::highlight(annotation-pink) {
|
|
369
|
+
background-color: var(--pie-annotation-pink-highlight, rgba(139, 51, 74, 0.6));
|
|
370
|
+
}
|
|
371
|
+
::highlight(annotation-orange) {
|
|
372
|
+
background-color: var(--pie-annotation-orange-highlight, rgba(154, 99, 0, 0.6));
|
|
373
|
+
}
|
|
374
|
+
::highlight(annotation-underline) {
|
|
375
|
+
text-decoration-color: var(--pie-primary, #4da6ff);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/* WCAG 2.2 SC 1.4.11 non-text contrast: a highlight is the only indication
|
|
380
|
+
that text is annotated, so the swatches saturate and the underline
|
|
381
|
+
thickens when the user asks for more contrast.
|
|
382
|
+
|
|
383
|
+
The value is "more", not "high". The stylesheet this was recovered from
|
|
384
|
+
used prefers-contrast: high, which is not a valid value for the feature
|
|
385
|
+
-- the keywords are no-preference, more, less, custom. An invalid query
|
|
386
|
+
evaluates to "not all", so that block could never have matched in any
|
|
387
|
+
browser even had the file been loaded. Verified with matchMedia under
|
|
388
|
+
emulation: "high" stays false where "more" flips true. */
|
|
389
|
+
@media (prefers-contrast: more) {
|
|
390
|
+
::highlight(annotation-yellow) {
|
|
391
|
+
background-color: var(--pie-annotation-yellow-highlight, rgba(255, 255, 0, 0.7));
|
|
392
|
+
}
|
|
393
|
+
::highlight(annotation-green) {
|
|
394
|
+
background-color: var(--pie-annotation-green-highlight, rgba(0, 255, 127, 0.7));
|
|
395
|
+
}
|
|
396
|
+
::highlight(annotation-blue) {
|
|
397
|
+
background-color: var(--pie-annotation-blue-highlight, rgba(0, 191, 255, 0.7));
|
|
398
|
+
}
|
|
399
|
+
::highlight(annotation-pink) {
|
|
400
|
+
background-color: var(--pie-annotation-pink-highlight, rgba(255, 105, 180, 0.7));
|
|
401
|
+
}
|
|
402
|
+
::highlight(annotation-orange) {
|
|
403
|
+
background-color: var(--pie-annotation-orange-highlight, rgba(255, 165, 0, 0.85));
|
|
404
|
+
}
|
|
405
|
+
::highlight(annotation-underline) {
|
|
406
|
+
text-decoration-thickness: 3px;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
@media print {
|
|
411
|
+
/* TTS highlighting is a transient read-along cue, not content. */
|
|
412
|
+
::highlight(tts-word),
|
|
413
|
+
::highlight(tts-sentence),
|
|
414
|
+
[data-pie-tts-word-element="true"],
|
|
415
|
+
[data-pie-tts-sentence-element="true"] {
|
|
416
|
+
background-color: transparent;
|
|
417
|
+
text-decoration: none;
|
|
418
|
+
text-shadow: none;
|
|
419
|
+
border-bottom: none;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/* Annotations are student work and must survive printing, but a fill
|
|
423
|
+
that reads fine on screen can print as an illegible wash, so each
|
|
424
|
+
becomes an underline that keeps its colour coding. */
|
|
425
|
+
::highlight(annotation-yellow),
|
|
426
|
+
::highlight(annotation-green),
|
|
427
|
+
::highlight(annotation-blue),
|
|
428
|
+
::highlight(annotation-pink),
|
|
429
|
+
::highlight(annotation-orange) {
|
|
430
|
+
background-color: transparent;
|
|
431
|
+
border-bottom: 2px solid currentColor;
|
|
432
|
+
}
|
|
433
|
+
::highlight(annotation-yellow) { border-bottom-color: #ffeb3b; }
|
|
434
|
+
::highlight(annotation-green) { border-bottom-color: #a6e1c5; }
|
|
435
|
+
::highlight(annotation-blue) { border-bottom-color: #a7e0f6; }
|
|
436
|
+
::highlight(annotation-pink) { border-bottom-color: #ff9fae; }
|
|
437
|
+
::highlight(annotation-orange) { border-bottom-color: #ffa500; }
|
|
438
|
+
::highlight(annotation-underline) {
|
|
439
|
+
text-decoration-color: #000;
|
|
440
|
+
border-bottom: none;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/* text-shadow is a direct property rather than a var(), so unlike the
|
|
445
|
+
other TTS rules this one is not overridden by the adaptive path. */
|
|
446
|
+
@media (prefers-reduced-motion: reduce) {
|
|
447
|
+
::highlight(tts-word),
|
|
448
|
+
[data-pie-tts-word-element="true"] {
|
|
449
|
+
text-shadow: none;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
342
452
|
`;
|
|
343
453
|
document.head.appendChild(style);
|
|
344
454
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HighlightCoordinator.js","sourceRoot":"","sources":["../../src/services/HighlightCoordinator.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * HighlightCoordinator\n *\n * Manages separate highlight layers for TTS (temporary) and annotations (persistent).\n * Uses CSS Custom Highlight API - zero DOM mutation, preserves accessibility.\n *\n * Features:\n * - Separate layers prevent TTS clearing from removing student annotations\n * - CSS Custom Highlight API (no DOM mutation)\n * - Full accessibility support (screen readers see original text)\n * - Configurable colors and styles\n * - Annotation persistence via RangeSerializer\n *\n * Part of PIE Assessment Toolkit.\n *\n * Browser Support:\n * - Chrome/Edge 105+\n * - Safari 17.2+\n * - Firefox: Behind flag (not recommended for production)\n *\n * @see https://developer.mozilla.org/en-US/docs/Web/API/CSS_Custom_Highlight_API\n */\n\nimport type { HighlightCoordinatorApi } from \"./interfaces.js\";\nimport { RangeSerializer, type SerializedRange } from \"./RangeSerializer.js\";\n\n/**\n * Highlight types\n */\nexport enum HighlightType {\n\tTTS_WORD = \"tts-word\",\n\tTTS_SENTENCE = \"tts-sentence\",\n\tANNOTATION = \"annotation\",\n}\n\n/**\n * Highlight colors for annotations\n */\nexport enum HighlightColor {\n\tYELLOW = \"yellow\",\n\tGREEN = \"green\",\n\tBLUE = \"blue\",\n\tPINK = \"pink\",\n\tORANGE = \"orange\",\n\tUNDERLINE = \"underline\",\n}\n\n/**\n * Annotation data\n */\nexport interface Annotation {\n\tid: string;\n\trange: Range;\n\ttype: HighlightType.ANNOTATION;\n\tcolor: HighlightColor;\n\ttimestamp: number;\n}\n\n/**\n * Configuration for HighlightCoordinator\n * Currently empty but allows future extension without breaking changes\n */\nexport type HighlightCoordinatorConfig = Record<string, never>;\n\nexport class HighlightCoordinator implements HighlightCoordinatorApi {\n\tprivate config: HighlightCoordinatorConfig;\n\tprivate ttsWordHighlight: Highlight | null = null;\n\tprivate ttsSentenceHighlight: Highlight | null = null;\n\tprivate ttsWordElementHighlights = new Set<Element>();\n\tprivate ttsSentenceElementHighlights = new Set<Element>();\n\tprivate annotations = new Map<string, Annotation>();\n\t// Shared highlights per color (one Highlight object per color, contains all ranges)\n\tprivate colorHighlights = new Map<HighlightColor, Highlight>();\n\tprivate nextAnnotationId = 1;\n\tprivate supported = false;\n\tprivate rangeSerializer: RangeSerializer;\n\tprivate themeObserver: MutationObserver | null = null;\n\tprivate explicitTTSColorOverride: { color: string; opacity: number } | null =\n\t\tnull;\n\n\tconstructor(config: HighlightCoordinatorConfig = {}) {\n\t\tthis.config = config;\n\t\tthis.rangeSerializer = new RangeSerializer();\n\n\t\t// SSR guard\n\t\tif (typeof CSS === \"undefined\" || !(\"highlights\" in CSS)) {\n\t\t\tconsole.warn(\n\t\t\t\t\"CSS Custom Highlight API not supported (SSR or unsupported browser)\",\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.supported = true;\n\t\tthis.initializeHighlights();\n\t\tthis.registerStyles();\n\t\tthis.applyAdaptiveTTSStyle();\n\t\tthis.setupThemeObservation();\n\t}\n\n\tprivate setupThemeObservation(): void {\n\t\tif (typeof document === \"undefined\") return;\n\t\tif (typeof MutationObserver === \"undefined\") return;\n\n\t\tconst refresh = () => this.applyAdaptiveTTSStyle();\n\t\tthis.themeObserver = new MutationObserver(refresh);\n\n\t\tthis.themeObserver.observe(document.documentElement, {\n\t\t\tattributes: true,\n\t\t\tattributeFilter: [\"style\", \"data-theme\", \"data-color-scheme\", \"class\"],\n\t\t});\n\n\t\tfor (const host of document.querySelectorAll(\"pie-theme\")) {\n\t\t\tthis.themeObserver.observe(host, {\n\t\t\t\tattributes: true,\n\t\t\t\tattributeFilter: [\n\t\t\t\t\t\"theme\",\n\t\t\t\t\t\"scheme\",\n\t\t\t\t\t\"provider\",\n\t\t\t\t\t\"variables\",\n\t\t\t\t\t\"style\",\n\t\t\t\t\t\"data-theme\",\n\t\t\t\t\t\"data-color-scheme\",\n\t\t\t\t],\n\t\t\t});\n\t\t}\n\t}\n\n\tprivate parseColor(\n\t\tinput: string | null | undefined,\n\t): [number, number, number] | null {\n\t\tif (!input) return null;\n\t\tconst value = input.trim();\n\t\tif (!value) return null;\n\n\t\tconst hexMatch = /^#([a-f\\d]{3}|[a-f\\d]{6})$/i.exec(value);\n\t\tif (hexMatch) {\n\t\t\tconst hex = hexMatch[1];\n\t\t\tif (hex.length === 3) {\n\t\t\t\treturn [\n\t\t\t\t\tparseInt(hex[0] + hex[0], 16),\n\t\t\t\t\tparseInt(hex[1] + hex[1], 16),\n\t\t\t\t\tparseInt(hex[2] + hex[2], 16),\n\t\t\t\t];\n\t\t\t}\n\t\t\treturn [\n\t\t\t\tparseInt(hex.slice(0, 2), 16),\n\t\t\t\tparseInt(hex.slice(2, 4), 16),\n\t\t\t\tparseInt(hex.slice(4, 6), 16),\n\t\t\t];\n\t\t}\n\n\t\tconst rgbMatch = /^rgba?\\((.+)\\)$/i.exec(value);\n\t\tif (rgbMatch) {\n\t\t\tconst normalized = rgbMatch[1].replace(/\\//g, \",\");\n\t\t\tconst parts = normalized\n\t\t\t\t.split(/[,\\s]+/)\n\t\t\t\t.map((part) => part.trim())\n\t\t\t\t.filter(Boolean);\n\t\t\tconst r = Number(parts[0]);\n\t\t\tconst g = Number(parts[1]);\n\t\t\tconst b = Number(parts[2]);\n\t\t\tif (Number.isFinite(r) && Number.isFinite(g) && Number.isFinite(b)) {\n\t\t\t\treturn [r, g, b];\n\t\t\t}\n\t\t}\n\n\t\t// Browser parser fallback (covers formats like oklch()).\n\t\tif (typeof document !== \"undefined\") {\n\t\t\tconst parserEl = document.createElement(\"span\");\n\t\t\tparserEl.style.color = value;\n\t\t\tif (typeof parserEl.style.color === \"string\" && parserEl.style.color) {\n\t\t\t\tdocument.body?.appendChild(parserEl);\n\t\t\t\tconst resolved =\n\t\t\t\t\ttypeof getComputedStyle === \"function\"\n\t\t\t\t\t\t? getComputedStyle(parserEl).color\n\t\t\t\t\t\t: \"\";\n\t\t\t\tparserEl.remove();\n\t\t\t\tconst normalizedResolved = resolved.trim();\n\t\t\t\t// Guard against recursive loops when computed style returns the same\n\t\t\t\t// unresolved function syntax (observed with certain color formats).\n\t\t\t\tif (normalizedResolved && normalizedResolved !== value) {\n\t\t\t\t\treturn this.parseColor(resolved);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn null;\n\t}\n\n\tprivate relativeLuminance([r, g, b]: [number, number, number]): number {\n\t\tconst toLinear = (channel: number) => {\n\t\t\tconst n = channel / 255;\n\t\t\treturn n <= 0.03928 ? n / 12.92 : ((n + 0.055) / 1.055) ** 2.4;\n\t\t};\n\t\tconst lr = toLinear(r);\n\t\tconst lg = toLinear(g);\n\t\tconst lb = toLinear(b);\n\t\treturn 0.2126 * lr + 0.7152 * lg + 0.0722 * lb;\n\t}\n\n\tprivate contrastRatio(\n\t\ta: [number, number, number],\n\t\tb: [number, number, number],\n\t): number {\n\t\tconst la = this.relativeLuminance(a);\n\t\tconst lb = this.relativeLuminance(b);\n\t\tconst lighter = Math.max(la, lb);\n\t\tconst darker = Math.min(la, lb);\n\t\treturn (lighter + 0.05) / (darker + 0.05);\n\t}\n\n\tprivate blend(\n\t\tfg: [number, number, number],\n\t\tbg: [number, number, number],\n\t\talpha: number,\n\t): [number, number, number] {\n\t\treturn [\n\t\t\tMath.round(fg[0] * alpha + bg[0] * (1 - alpha)),\n\t\t\tMath.round(fg[1] * alpha + bg[1] * (1 - alpha)),\n\t\t\tMath.round(fg[2] * alpha + bg[2] * (1 - alpha)),\n\t\t];\n\t}\n\n\tprivate resolveAdaptiveTTSStyle(sourceEl?: Element | null): {\n\t\twordHighlight: string;\n\t\tsentenceHighlight: string;\n\t\twordUnderline: string;\n\t\twordShadow: string;\n\t} {\n\t\tconst fallbackMissing: [number, number, number] = [255, 235, 59];\n\t\tconst fallbackText: [number, number, number] = [17, 24, 39];\n\t\tconst fallbackBackground: [number, number, number] = [255, 255, 255];\n\n\t\tconst target =\n\t\t\tsourceEl ||\n\t\t\t(typeof document !== \"undefined\" ? document.documentElement : null);\n\t\tconst computed =\n\t\t\ttarget && typeof getComputedStyle === \"function\"\n\t\t\t\t? getComputedStyle(target)\n\t\t\t\t: null;\n\n\t\tconst background =\n\t\t\tthis.parseColor(\n\t\t\t\tcomputed?.getPropertyValue(\"--pie-background\") ||\n\t\t\t\t\tcomputed?.backgroundColor,\n\t\t\t) || fallbackBackground;\n\t\tconst text =\n\t\t\tthis.parseColor(computed?.getPropertyValue(\"--pie-text\")) || fallbackText;\n\t\tconst accent = this.explicitTTSColorOverride\n\t\t\t? this.parseColor(this.explicitTTSColorOverride.color) || fallbackMissing\n\t\t\t: this.parseColor(computed?.getPropertyValue(\"--pie-missing\")) ||\n\t\t\t\tfallbackMissing;\n\n\t\tconst opacityCandidates = this.explicitTTSColorOverride\n\t\t\t? [Math.max(0.3, Math.min(0.95, this.explicitTTSColorOverride.opacity))]\n\t\t\t: [0.8, 0.72, 0.68, 0.62, 0.56, 0.5];\n\n\t\tlet selectedOpacity = opacityCandidates[opacityCandidates.length - 1];\n\t\tlet bestScore = -Infinity;\n\n\t\tfor (const opacity of opacityCandidates) {\n\t\t\tconst blended = this.blend(accent, background, opacity);\n\t\t\tconst backgroundDelta = this.contrastRatio(blended, background);\n\t\t\tconst textContrast = this.contrastRatio(blended, text);\n\t\t\tconst score = backgroundDelta * 1.2 + textContrast * 0.8;\n\t\t\tif (backgroundDelta >= 1.25 && textContrast >= 2.4) {\n\t\t\t\tselectedOpacity = opacity;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (score > bestScore) {\n\t\t\t\tbestScore = score;\n\t\t\t\tselectedOpacity = opacity;\n\t\t\t}\n\t\t}\n\n\t\tconst sentenceOpacity = Math.max(\n\t\t\t0.24,\n\t\t\tMath.min(0.85, selectedOpacity * 0.55),\n\t\t);\n\t\tconst underlineOpacity = Math.max(\n\t\t\t0.55,\n\t\t\tMath.min(0.95, selectedOpacity + 0.2),\n\t\t);\n\t\tconst shadowOpacity = Math.max(0.22, Math.min(0.6, selectedOpacity * 0.45));\n\n\t\tconst underlineColor = text;\n\t\tconst underlineBlend = this.blend(\n\t\t\tunderlineColor,\n\t\t\tbackground,\n\t\t\tunderlineOpacity,\n\t\t);\n\t\tconst underlineDelta = this.contrastRatio(underlineBlend, background);\n\t\tconst fallbackUnderline: [number, number, number] =\n\t\t\tthis.relativeLuminance(background) > 0.45 ? [0, 0, 0] : [255, 255, 255];\n\t\tconst finalUnderline =\n\t\t\tunderlineDelta >= 1.35 ? underlineColor : fallbackUnderline;\n\n\t\tconst wordHighlight = `rgba(${accent[0]}, ${accent[1]}, ${accent[2]}, ${selectedOpacity})`;\n\t\tconst sentenceHighlight = `rgba(${accent[0]}, ${accent[1]}, ${accent[2]}, ${sentenceOpacity})`;\n\t\tconst wordUnderline = `rgba(${finalUnderline[0]}, ${finalUnderline[1]}, ${finalUnderline[2]}, ${underlineOpacity})`;\n\t\tconst wordShadow = `rgba(${finalUnderline[0]}, ${finalUnderline[1]}, ${finalUnderline[2]}, ${shadowOpacity})`;\n\n\t\treturn {\n\t\t\twordHighlight,\n\t\t\tsentenceHighlight,\n\t\t\twordUnderline,\n\t\t\twordShadow,\n\t\t};\n\t}\n\n\tprivate applyAdaptiveTTSStyle(sourceEl?: Element | null): void {\n\t\tif (typeof document === \"undefined\") return;\n\t\tconst vars = this.resolveAdaptiveTTSStyle(sourceEl);\n\t\tdocument.documentElement.style.setProperty(\n\t\t\t\"--pie-tts-word-highlight\",\n\t\t\tvars.wordHighlight,\n\t\t);\n\t\tdocument.documentElement.style.setProperty(\n\t\t\t\"--pie-tts-sentence-highlight\",\n\t\t\tvars.sentenceHighlight,\n\t\t);\n\t\tdocument.documentElement.style.setProperty(\n\t\t\t\"--pie-tts-line-highlight\",\n\t\t\tvars.sentenceHighlight,\n\t\t);\n\t\tdocument.documentElement.style.setProperty(\n\t\t\t\"--pie-tts-word-underline\",\n\t\t\tvars.wordUnderline,\n\t\t);\n\t\tdocument.documentElement.style.setProperty(\n\t\t\t\"--pie-tts-word-shadow\",\n\t\t\tvars.wordShadow,\n\t\t);\n\t}\n\n\t/**\n\t * Initialize CSS Custom Highlights\n\t */\n\tprivate initializeHighlights(): void {\n\t\tif (!this.supported) return;\n\n\t\t// Create highlight registries for TTS\n\t\tthis.ttsWordHighlight = new Highlight();\n\t\tthis.ttsSentenceHighlight = new Highlight();\n\n\t\t// Register TTS highlights with CSS\n\t\tCSS.highlights.set(\"tts-word\", this.ttsWordHighlight);\n\t\tCSS.highlights.set(\"tts-sentence\", this.ttsSentenceHighlight);\n\n\t\t// Create shared highlights for each annotation color\n\t\tfor (const color of Object.values(HighlightColor)) {\n\t\t\tconst highlight = new Highlight();\n\t\t\tthis.colorHighlights.set(color, highlight);\n\t\t\tCSS.highlights.set(`annotation-${color}`, highlight);\n\t\t}\n\t}\n\n\t/**\n\t * Register CSS styles for highlights\n\t */\n\tprivate registerStyles(): void {\n\t\tif (!this.supported) return;\n\t\tif (typeof document === \"undefined\") return; // SSR guard\n\n\t\t// Check if styles already exist\n\t\tif (document.getElementById(\"pie-highlight-styles\")) return;\n\n\t\tconst style = document.createElement(\"style\");\n\t\tstyle.id = \"pie-highlight-styles\";\n\t\tstyle.textContent = `\n /* TTS highlights - temporary */\n ::highlight(tts-word) {\n background-color: var(--pie-tts-word-highlight, color-mix(in srgb, var(--pie-missing, #ffeb3b) 68%, transparent));\n text-decoration: underline 2px solid var(--pie-tts-word-underline, color-mix(in srgb, var(--pie-text, #111827) 70%, transparent));\n text-underline-offset: 2px;\n text-shadow: 0 0 1px var(--pie-tts-word-shadow, color-mix(in srgb, var(--pie-text, #111827) 35%, transparent));\n color: inherit;\n }\n\n /* tts-sentence registry id: coarse read-along band (visual line boxes in layout) */\n ::highlight(tts-sentence) {\n background-color: var(--pie-tts-line-highlight, var(--pie-tts-sentence-highlight, color-mix(in srgb, var(--pie-missing, #ffeb3b) 38%, transparent)));\n color: inherit;\n }\n\n [data-pie-tts-sentence-element=\"true\"] {\n background-color: var(--pie-tts-line-highlight, var(--pie-tts-sentence-highlight, color-mix(in srgb, var(--pie-missing, #ffeb3b) 38%, transparent)));\n border-radius: 0.12em;\n box-decoration-break: clone;\n -webkit-box-decoration-break: clone;\n }\n\n [data-pie-tts-word-element=\"true\"] {\n background-color: var(--pie-tts-word-highlight, color-mix(in srgb, var(--pie-missing, #ffeb3b) 68%, transparent));\n text-decoration: underline 2px solid var(--pie-tts-word-underline, color-mix(in srgb, var(--pie-text, #111827) 70%, transparent));\n text-underline-offset: 2px;\n text-shadow: 0 0 1px var(--pie-tts-word-shadow, color-mix(in srgb, var(--pie-text, #111827) 35%, transparent));\n border-bottom: 2px solid var(--pie-tts-word-underline, color-mix(in srgb, var(--pie-text, #111827) 70%, transparent));\n padding-bottom: 1px;\n border-radius: 0.12em;\n box-decoration-break: clone;\n -webkit-box-decoration-break: clone;\n }\n\n /* Annotation highlights - persistent */\n ::highlight(annotation-yellow) {\n background-color: var(--pie-annotation-yellow-highlight, rgba(253, 233, 149, 0.5));\n color: inherit;\n }\n\n ::highlight(annotation-green) {\n background-color: var(--pie-annotation-green-highlight, rgba(166, 225, 197, 0.5));\n color: inherit;\n }\n\n ::highlight(annotation-blue) {\n background-color: var(--pie-annotation-blue-highlight, rgba(167, 224, 246, 0.5));\n color: inherit;\n }\n\n ::highlight(annotation-pink) {\n background-color: var(--pie-annotation-pink-highlight, rgba(255, 159, 174, 0.5));\n color: inherit;\n }\n\n ::highlight(annotation-orange) {\n background-color: var(--pie-annotation-orange-highlight, rgba(255, 165, 0, 0.5));\n color: inherit;\n }\n\n ::highlight(annotation-underline) {\n background-color: transparent;\n text-decoration: underline 2px solid var(--pie-primary, #0066cc);\n text-underline-offset: 2px;\n color: inherit;\n }\n `;\n\t\tdocument.head.appendChild(style);\n\t}\n\n\t/**\n\t * Highlight a word for TTS (temporary)\n\t *\n\t * @param textNode Text node containing the word\n\t * @param startOffset Start position in text node\n\t * @param endOffset End position in text node\n\t */\n\thighlightTTSWord(\n\t\ttextNode: Text,\n\t\tstartOffset: number,\n\t\tendOffset: number,\n\t): void {\n\t\tif (!this.ttsWordHighlight) return;\n\t\tthis.applyAdaptiveTTSStyle(textNode.parentElement);\n\n\t\t// Clear previous word highlight\n\t\tthis.clearTTSWord();\n\n\t\t// Create range for word\n\t\tconst range = document.createRange();\n\t\trange.setStart(textNode, startOffset);\n\t\trange.setEnd(textNode, endOffset);\n\n\t\t// Add to highlight\n\t\tthis.ttsWordHighlight.add(range);\n\t}\n\n\t/**\n\t * Highlight a single element as the active TTS word (temporary).\n\t *\n\t * Unlike {@link highlightTTSWord} (which paints a CSS range over a text node)\n\t * this marks the element itself via `data-pie-tts-word-element`. It is used\n\t * for atomic targets that have no direct text node to range over — most\n\t * notably MathJax CHTML tokens (e.g. `<mjx-mi><mjx-c/></mjx-mi>`, whose glyph\n\t * lives in a font-driven pseudo-element a CSS range cannot paint) and\n\t * whole-expression fallbacks.\n\t *\n\t * The supplied element is painted exactly; we deliberately do NOT walk up to\n\t * a containing `<math>` / `<mjx-container>`. That escalation is what made\n\t * MathJax-rendered math highlight as a full block while native MathML tracked\n\t * per-token: a resolved single token must paint as a token, and only a\n\t * genuine expression fallback (where the element already is the container)\n\t * should paint the whole expression.\n\t */\n\thighlightTTSWordElement(element: Element): void {\n\t\tif (!this.ttsWordHighlight) return;\n\t\tthis.applyAdaptiveTTSStyle(element);\n\n\t\t// Clear previous word highlight (CSS range and any element attributes)\n\t\tthis.clearTTSWord();\n\n\t\telement.setAttribute(\"data-pie-tts-word-element\", \"true\");\n\t\tthis.ttsWordElementHighlights.add(element);\n\t}\n\n\t/**\n\t * Highlight the coarse TTS read-along band (temporary).\n\t * Usually one Range per visual line; CSS layer `tts-sentence` for compatibility.\n\t *\n\t * @param ranges Line (or sentence) ranges to paint with `--pie-tts-line-highlight`\n\t */\n\thighlightTTSSentence(ranges: Range[]): void {\n\t\tif (!this.ttsSentenceHighlight) return;\n\t\tconst source = ranges[0]?.startContainer;\n\t\tconst sourceElement =\n\t\t\tsource?.nodeType === Node.ELEMENT_NODE\n\t\t\t\t? (source as Element)\n\t\t\t\t: source?.parentElement;\n\t\tthis.applyAdaptiveTTSStyle(sourceElement);\n\n\t\t// Clear previous sentence highlight\n\t\tthis.clearTTSSentence();\n\n\t\t// Add all ranges\n\t\tfor (const range of ranges) {\n\t\t\tthis.ttsSentenceHighlight.add(range);\n\t\t}\n\t\tthis.highlightTTSElementFallbacks(ranges);\n\t}\n\n\t/**\n\t * Highlight sentence/block elements for TTS (background layer).\n\t *\n\t * @param elements Visible elements to paint with `--pie-tts-line-highlight`\n\t */\n\thighlightTTSSentenceElements(elements: Element[]): void {\n\t\tif (!this.ttsSentenceHighlight) return;\n\t\tthis.applyAdaptiveTTSStyle(elements[0] || null);\n\n\t\tthis.clearTTSSentence();\n\n\t\tfor (const element of elements) {\n\t\t\telement.setAttribute(\"data-pie-tts-sentence-element\", \"true\");\n\t\t\tthis.ttsSentenceElementHighlights.add(element);\n\t\t}\n\t}\n\n\t// The read-along (sentence) band legitimately covers whole equations and\n\t// replaced elements, so it escalates to them.\n\tprivate static readonly SENTENCE_FALLBACK_SELECTOR =\n\t\t\"math, mjx-container, svg, img, canvas, [role='img']\";\n\n\t// The word layer must NOT escalate a multi-node range to the enclosing\n\t// `<math>` / `<mjx-container>`: that is what made an equation flash as a\n\t// whole block whenever a spoken word's visible range crossed several math\n\t// glyph nodes. Math is tracked per token (or held / region) by the highlight\n\t// pipeline instead. Replaced elements (svg/img/canvas) still need the element\n\t// fallback because a CSS range cannot paint them.\n\tprivate static readonly WORD_FALLBACK_SELECTOR =\n\t\t\"svg, img, canvas, [role='img']\";\n\n\tprivate highlightTTSElementFallbacks(ranges: Range[]): void {\n\t\tif (typeof Element === \"undefined\") return;\n\t\tthis.highlightElementFallbacks(\n\t\t\tranges,\n\t\t\t\"data-pie-tts-sentence-element\",\n\t\t\tthis.ttsSentenceElementHighlights,\n\t\t\tHighlightCoordinator.SENTENCE_FALLBACK_SELECTOR,\n\t\t);\n\t}\n\n\tprivate highlightTTSWordElementFallbacks(ranges: Range[]): void {\n\t\tif (typeof Element === \"undefined\") return;\n\t\tthis.highlightElementFallbacks(\n\t\t\tranges,\n\t\t\t\"data-pie-tts-word-element\",\n\t\t\tthis.ttsWordElementHighlights,\n\t\t\tHighlightCoordinator.WORD_FALLBACK_SELECTOR,\n\t\t);\n\t}\n\n\tprivate highlightElementFallbacks(\n\t\tranges: Range[],\n\t\tattributeName: string,\n\t\ttrackedElements: Set<Element>,\n\t\tselector: string,\n\t): void {\n\t\tfor (const range of ranges) {\n\t\t\tconst root =\n\t\t\t\trange.commonAncestorContainer.nodeType === Node.ELEMENT_NODE\n\t\t\t\t\t? (range.commonAncestorContainer as Element)\n\t\t\t\t\t: range.commonAncestorContainer.parentElement;\n\t\t\tif (!root) continue;\n\t\t\tconst candidates: Element[] = [];\n\t\t\tconst rangeStartElement =\n\t\t\t\trange.startContainer.nodeType === Node.ELEMENT_NODE\n\t\t\t\t\t? (range.startContainer as Element)\n\t\t\t\t\t: range.startContainer.parentElement;\n\t\t\tconst rangeEndElement =\n\t\t\t\trange.endContainer.nodeType === Node.ELEMENT_NODE\n\t\t\t\t\t? (range.endContainer as Element)\n\t\t\t\t\t: range.endContainer.parentElement;\n\t\t\tfor (const element of [rangeStartElement, rangeEndElement]) {\n\t\t\t\tlet current: Element | null = element;\n\t\t\t\twhile (current) {\n\t\t\t\t\tif (current.matches?.(selector)) candidates.push(current);\n\t\t\t\t\tcurrent = current.parentElement;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (root.matches?.(selector)) {\n\t\t\t\tcandidates.push(root);\n\t\t\t}\n\t\t\tcandidates.push(...Array.from(root.querySelectorAll(selector)));\n\t\t\tfor (const element of new Set(candidates)) {\n\t\t\t\ttry {\n\t\t\t\t\tif (\n\t\t\t\t\t\t!range.intersectsNode(element) &&\n\t\t\t\t\t\t!element.contains(rangeStartElement) &&\n\t\t\t\t\t\t!element.contains(rangeEndElement)\n\t\t\t\t\t) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t} catch {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\telement.setAttribute(attributeName, \"true\");\n\t\t\t\ttrackedElements.add(element);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Clear TTS word highlight\n\t */\n\tclearTTSWord(): void {\n\t\tif (!this.ttsWordHighlight) return;\n\t\tthis.ttsWordHighlight.clear();\n\t\tfor (const element of this.ttsWordElementHighlights) {\n\t\t\telement.removeAttribute(\"data-pie-tts-word-element\");\n\t\t}\n\t\tthis.ttsWordElementHighlights.clear();\n\t}\n\n\t/**\n\t * Clear TTS sentence highlight\n\t */\n\tclearTTSSentence(): void {\n\t\tif (!this.ttsSentenceHighlight) return;\n\t\tthis.ttsSentenceHighlight.clear();\n\t\tfor (const element of this.ttsSentenceElementHighlights) {\n\t\t\telement.removeAttribute(\"data-pie-tts-sentence-element\");\n\t\t}\n\t\tthis.ttsSentenceElementHighlights.clear();\n\t}\n\n\t/**\n\t * Clear all TTS highlights\n\t */\n\tclearTTS(): void {\n\t\tthis.clearTTSWord();\n\t\tthis.clearTTSSentence();\n\t}\n\n\t/**\n\t * Add an annotation highlight (persistent)\n\t *\n\t * @param range Text range to annotate\n\t * @param color Highlight color\n\t * @returns Annotation ID for later removal\n\t */\n\taddAnnotation(\n\t\trange: Range,\n\t\tcolor: HighlightColor = HighlightColor.YELLOW,\n\t): string {\n\t\tconst id = `annotation-${this.nextAnnotationId++}`;\n\n\t\t// Clone the range to store\n\t\tconst clonedRange = range.cloneRange();\n\n\t\t// Store annotation data\n\t\tconst annotation: Annotation = {\n\t\t\tid,\n\t\t\trange: clonedRange,\n\t\t\ttype: HighlightType.ANNOTATION,\n\t\t\tcolor,\n\t\t\ttimestamp: Date.now(),\n\t\t};\n\t\tthis.annotations.set(id, annotation);\n\n\t\t// Add the SAME range object to the shared color highlight\n\t\t// This ensures we can later delete it by reference\n\t\tconst colorHighlight = this.colorHighlights.get(color);\n\t\tif (colorHighlight) {\n\t\t\tcolorHighlight.add(clonedRange);\n\t\t}\n\n\t\treturn id;\n\t}\n\n\t/**\n\t * Remove an annotation\n\t *\n\t * @param id Annotation ID\n\t */\n\tremoveAnnotation(id: string): void {\n\t\tconst annotation = this.annotations.get(id);\n\t\tif (!annotation) {\n\t\t\tconsole.warn(`[HighlightCoordinator] Annotation ${id} not found`);\n\t\t\treturn;\n\t\t}\n\n\t\tconsole.log(\n\t\t\t`[HighlightCoordinator] Removing annotation ${id} (color: ${annotation.color})`,\n\t\t);\n\n\t\t// Remove range from the shared color highlight\n\t\tconst colorHighlight = this.colorHighlights.get(annotation.color);\n\t\tif (colorHighlight) {\n\t\t\tconst deleted = colorHighlight.delete(annotation.range);\n\t\t\tconsole.log(\n\t\t\t\t`[HighlightCoordinator] Highlight.delete() returned:`,\n\t\t\t\tdeleted,\n\t\t\t);\n\t\t}\n\n\t\t// Clean up\n\t\tthis.annotations.delete(id);\n\t\tconsole.log(\n\t\t\t`[HighlightCoordinator] Annotation ${id} removed from map. Remaining:`,\n\t\t\tthis.annotations.size,\n\t\t);\n\t}\n\n\t/**\n\t * Remove all annotations\n\t */\n\tclearAnnotations(): void {\n\t\t// Clear all color highlights\n\t\tfor (const colorHighlight of this.colorHighlights.values()) {\n\t\t\tcolorHighlight.clear();\n\t\t}\n\n\t\t// Clear annotation data\n\t\tthis.annotations.clear();\n\t}\n\n\t/**\n\t * Get all annotations\n\t */\n\tgetAnnotations(): Annotation[] {\n\t\treturn Array.from(this.annotations.values());\n\t}\n\n\t/**\n\t * Get annotation by ID\n\t */\n\tgetAnnotation(id: string): Annotation | null {\n\t\treturn this.annotations.get(id) ?? null;\n\t}\n\n\t/**\n\t * Change annotation color\n\t *\n\t * @param id Annotation ID\n\t * @param newColor New color\n\t */\n\tchangeAnnotationColor(id: string, newColor: HighlightColor): void {\n\t\tconst annotation = this.annotations.get(id);\n\t\tif (!annotation) return;\n\n\t\tconst oldColor = annotation.color;\n\t\tif (oldColor === newColor) return;\n\n\t\t// Remove from old color highlight\n\t\tconst oldColorHighlight = this.colorHighlights.get(oldColor);\n\t\tif (oldColorHighlight) {\n\t\t\toldColorHighlight.delete(annotation.range);\n\t\t}\n\n\t\t// Update color\n\t\tannotation.color = newColor;\n\n\t\t// Add to new color highlight\n\t\tconst newColorHighlight = this.colorHighlights.get(newColor);\n\t\tif (newColorHighlight) {\n\t\t\tnewColorHighlight.add(annotation.range);\n\t\t}\n\t}\n\n\t/**\n\t * Export annotations as serializable data for persistence.\n\t * Uses RangeSerializer for robust DOM range serialization.\n\t *\n\t * @param root Root element for serialization (typically document.body or content container)\n\t * @returns Array of serialized annotations\n\t */\n\texportAnnotations(\n\t\troot: Element = document.body,\n\t): Array<\n\t\tSerializedRange & { id: string; color: HighlightColor; timestamp: number }\n\t> {\n\t\treturn this.getAnnotations().map((annotation) => ({\n\t\t\t...this.rangeSerializer.serialize(annotation.range, root),\n\t\t\tid: annotation.id,\n\t\t\tcolor: annotation.color,\n\t\t\ttimestamp: annotation.timestamp,\n\t\t}));\n\t}\n\n\t/**\n\t * Import annotations from serialized data.\n\t * Restores annotations after page navigation or refresh.\n\t *\n\t * @param data Array of serialized annotations\n\t * @param root Root element for deserialization (same as used in export)\n\t * @returns Number of successfully restored annotations\n\t */\n\timportAnnotations(\n\t\tdata: Array<\n\t\t\tSerializedRange & {\n\t\t\t\tid?: string;\n\t\t\t\tcolor: HighlightColor;\n\t\t\t\ttimestamp?: number;\n\t\t\t}\n\t\t>,\n\t\troot: Element = document.body,\n\t): number {\n\t\tlet restored = 0;\n\n\t\tfor (const item of data) {\n\t\t\tconst range = this.rangeSerializer.deserialize(item, root);\n\t\t\tif (range) {\n\t\t\t\tthis.addAnnotation(range, item.color);\n\t\t\t\trestored++;\n\t\t\t}\n\t\t}\n\n\t\treturn restored;\n\t}\n\n\t// ============================================================================\n\t// HighlightCoordinatorApi interface implementation\n\t// ============================================================================\n\n\t/**\n\t * Highlight a text range (interface method)\n\t *\n\t * Generic method that adapts to the specific highlight type.\n\t * For more control, use the specific methods like highlightTTSWord().\n\t */\n\thighlightRange(\n\t\trange: Range,\n\t\ttype: HighlightType,\n\t\tcolor: HighlightColor = HighlightColor.YELLOW,\n\t): void {\n\t\tif (!this.supported) return;\n\n\t\tswitch (type) {\n\t\t\tcase HighlightType.TTS_WORD:\n\t\t\tcase HighlightType.TTS_SENTENCE: {\n\t\t\t\t// For TTS, add to appropriate highlight\n\t\t\t\tconst highlight =\n\t\t\t\t\ttype === HighlightType.TTS_WORD\n\t\t\t\t\t\t? this.ttsWordHighlight\n\t\t\t\t\t\t: this.ttsSentenceHighlight;\n\t\t\t\tif (highlight) {\n\t\t\t\t\thighlight.clear();\n\t\t\t\t\thighlight.add(range);\n\t\t\t\t\tif (type === HighlightType.TTS_WORD) {\n\t\t\t\t\t\tfor (const element of this.ttsWordElementHighlights) {\n\t\t\t\t\t\t\telement.removeAttribute(\"data-pie-tts-word-element\");\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.ttsWordElementHighlights.clear();\n\t\t\t\t\t\tthis.highlightTTSWordElementFallbacks([range]);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase HighlightType.ANNOTATION:\n\t\t\t\t// For annotations, use the existing method\n\t\t\t\tthis.addAnnotation(range, color);\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\t/**\n\t * Clear highlights of a specific type (interface method)\n\t */\n\tclearHighlights(type: HighlightType): void {\n\t\tif (!this.supported) return;\n\n\t\tswitch (type) {\n\t\t\tcase HighlightType.TTS_WORD:\n\t\t\t\tthis.clearTTSWord();\n\t\t\t\tbreak;\n\t\t\tcase HighlightType.TTS_SENTENCE:\n\t\t\t\tthis.clearTTSSentence();\n\t\t\t\tbreak;\n\t\t\tcase HighlightType.ANNOTATION:\n\t\t\t\tthis.clearAnnotations();\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\t/**\n\t * Clear all highlights (interface method)\n\t */\n\tclearAll(): void {\n\t\tif (!this.supported) return;\n\t\tthis.clearTTS();\n\t\tthis.clearAnnotations();\n\t}\n\n\t/**\n\t * Check if CSS Highlight API is supported (interface method)\n\t */\n\tisSupported(): boolean {\n\t\treturn this.supported;\n\t}\n\n\t/**\n\t * Update TTS highlight style dynamically\n\t *\n\t * @param color CSS color value (e.g., '#ffeb3b')\n\t * @param opacity Opacity value (0.0 to 1.0)\n\t */\n\tupdateTTSHighlightStyle(color: string, opacity: number): void {\n\t\tif (!this.supported) return;\n\t\tif (typeof document === \"undefined\") return;\n\n\t\tconst styleEl = document.getElementById(\"pie-highlight-styles\");\n\t\tif (!styleEl) return;\n\n\t\t// Convert hex to rgba\n\t\tconst hexToRgb = (hex: string) => {\n\t\t\tconst result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex);\n\t\t\treturn result\n\t\t\t\t? {\n\t\t\t\t\t\tr: parseInt(result[1], 16),\n\t\t\t\t\t\tg: parseInt(result[2], 16),\n\t\t\t\t\t\tb: parseInt(result[3], 16),\n\t\t\t\t\t}\n\t\t\t\t: null;\n\t\t};\n\n\t\tconst rgb = hexToRgb(color);\n\t\tif (!rgb) return;\n\n\t\tthis.explicitTTSColorOverride = {\n\t\t\tcolor: `rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`,\n\t\t\topacity: Math.max(0.2, Math.min(0.95, opacity)),\n\t\t};\n\t\tthis.applyAdaptiveTTSStyle();\n\t}\n\n\t/**\n\t * Cleanup - remove all highlights\n\t */\n\tdestroy(): void {\n\t\tif (!this.supported) return;\n\n\t\tthis.clearTTS();\n\t\tthis.clearAnnotations();\n\n\t\t// Remove style element\n\t\tif (typeof document !== \"undefined\") {\n\t\t\tconst styleEl = document.getElementById(\"pie-highlight-styles\");\n\t\t\tif (styleEl) {\n\t\t\t\tstyleEl.remove();\n\t\t\t}\n\t\t}\n\t\tthis.themeObserver?.disconnect();\n\t\tthis.themeObserver = null;\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"HighlightCoordinator.js","sourceRoot":"","sources":["../../src/services/HighlightCoordinator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAGH,OAAO,EAAE,eAAe,EAAwB,MAAM,sBAAsB,CAAC;AAE7E;;GAEG;AACH,MAAM,CAAN,IAAY,aAIX;AAJD,WAAY,aAAa;IACxB,sCAAqB,CAAA;IACrB,8CAA6B,CAAA;IAC7B,0CAAyB,CAAA;AAC1B,CAAC,EAJW,aAAa,KAAb,aAAa,QAIxB;AAED;;GAEG;AACH,MAAM,CAAN,IAAY,cAOX;AAPD,WAAY,cAAc;IACzB,mCAAiB,CAAA;IACjB,iCAAe,CAAA;IACf,+BAAa,CAAA;IACb,+BAAa,CAAA;IACb,mCAAiB,CAAA;IACjB,yCAAuB,CAAA;AACxB,CAAC,EAPW,cAAc,KAAd,cAAc,QAOzB;AAmBD,MAAM,OAAO,oBAAoB;IACxB,MAAM,CAA6B;IACnC,gBAAgB,GAAqB,IAAI,CAAC;IAC1C,oBAAoB,GAAqB,IAAI,CAAC;IAC9C,wBAAwB,GAAG,IAAI,GAAG,EAAW,CAAC;IAC9C,4BAA4B,GAAG,IAAI,GAAG,EAAW,CAAC;IAClD,WAAW,GAAG,IAAI,GAAG,EAAsB,CAAC;IACpD,oFAAoF;IAC5E,eAAe,GAAG,IAAI,GAAG,EAA6B,CAAC;IACvD,gBAAgB,GAAG,CAAC,CAAC;IACrB,SAAS,GAAG,KAAK,CAAC;IAClB,eAAe,CAAkB;IACjC,aAAa,GAA4B,IAAI,CAAC;IAC9C,wBAAwB,GAC/B,IAAI,CAAC;IAEN,YAAY,SAAqC,EAAE;QAClD,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,eAAe,GAAG,IAAI,eAAe,EAAE,CAAC;QAE7C,YAAY;QACZ,IAAI,OAAO,GAAG,KAAK,WAAW,IAAI,CAAC,CAAC,YAAY,IAAI,GAAG,CAAC,EAAE,CAAC;YAC1D,OAAO,CAAC,IAAI,CACX,qEAAqE,CACrE,CAAC;YACF,OAAO;QACR,CAAC;QAED,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;QACtB,IAAI,CAAC,oBAAoB,EAAE,CAAC;QAC5B,IAAI,CAAC,cAAc,EAAE,CAAC;QACtB,IAAI,CAAC,qBAAqB,EAAE,CAAC;QAC7B,IAAI,CAAC,qBAAqB,EAAE,CAAC;IAC9B,CAAC;IAEO,qBAAqB;QAC5B,IAAI,OAAO,QAAQ,KAAK,WAAW;YAAE,OAAO;QAC5C,IAAI,OAAO,gBAAgB,KAAK,WAAW;YAAE,OAAO;QAEpD,MAAM,OAAO,GAAG,GAAG,EAAE,CAAC,IAAI,CAAC,qBAAqB,EAAE,CAAC;QACnD,IAAI,CAAC,aAAa,GAAG,IAAI,gBAAgB,CAAC,OAAO,CAAC,CAAC;QAEnD,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,QAAQ,CAAC,eAAe,EAAE;YACpD,UAAU,EAAE,IAAI;YAChB,eAAe,EAAE,CAAC,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,OAAO,CAAC;SACtE,CAAC,CAAC;QAEH,KAAK,MAAM,IAAI,IAAI,QAAQ,CAAC,gBAAgB,CAAC,WAAW,CAAC,EAAE,CAAC;YAC3D,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,IAAI,EAAE;gBAChC,UAAU,EAAE,IAAI;gBAChB,eAAe,EAAE;oBAChB,OAAO;oBACP,QAAQ;oBACR,UAAU;oBACV,WAAW;oBACX,OAAO;oBACP,YAAY;oBACZ,mBAAmB;iBACnB;aACD,CAAC,CAAC;QACJ,CAAC;IACF,CAAC;IAEO,UAAU,CACjB,KAAgC;QAEhC,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC;QACxB,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;QAC3B,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC;QAExB,MAAM,QAAQ,GAAG,6BAA6B,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC3D,IAAI,QAAQ,EAAE,CAAC;YACd,MAAM,GAAG,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;YACxB,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACtB,OAAO;oBACN,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;oBAC7B,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;oBAC7B,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;iBAC7B,CAAC;YACH,CAAC;YACD,OAAO;gBACN,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC;gBAC7B,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC;gBAC7B,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC;aAC7B,CAAC;QACH,CAAC;QAED,MAAM,QAAQ,GAAG,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAChD,IAAI,QAAQ,EAAE,CAAC;YACd,MAAM,UAAU,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;YACnD,MAAM,KAAK,GAAG,UAAU;iBACtB,KAAK,CAAC,QAAQ,CAAC;iBACf,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;iBAC1B,MAAM,CAAC,OAAO,CAAC,CAAC;YAClB,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;YAC3B,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;YAC3B,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;YAC3B,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;gBACpE,OAAO,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;YAClB,CAAC;QACF,CAAC;QAED,yDAAyD;QACzD,IAAI,OAAO,QAAQ,KAAK,WAAW,EAAE,CAAC;YACrC,MAAM,QAAQ,GAAG,QAAQ,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;YAChD,QAAQ,CAAC,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC;YAC7B,IAAI,OAAO,QAAQ,CAAC,KAAK,CAAC,KAAK,KAAK,QAAQ,IAAI,QAAQ,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;gBACtE,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC,QAAQ,CAAC,CAAC;gBACrC,MAAM,QAAQ,GACb,OAAO,gBAAgB,KAAK,UAAU;oBACrC,CAAC,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAC,KAAK;oBAClC,CAAC,CAAC,EAAE,CAAC;gBACP,QAAQ,CAAC,MAAM,EAAE,CAAC;gBAClB,MAAM,kBAAkB,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC;gBAC3C,qEAAqE;gBACrE,oEAAoE;gBACpE,IAAI,kBAAkB,IAAI,kBAAkB,KAAK,KAAK,EAAE,CAAC;oBACxD,OAAO,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;gBAClC,CAAC;YACF,CAAC;QACF,CAAC;QAED,OAAO,IAAI,CAAC;IACb,CAAC;IAEO,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAA2B;QAC5D,MAAM,QAAQ,GAAG,CAAC,OAAe,EAAE,EAAE;YACpC,MAAM,CAAC,GAAG,OAAO,GAAG,GAAG,CAAC;YACxB,OAAO,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,GAAG,KAAK,CAAC,IAAI,GAAG,CAAC;QAChE,CAAC,CAAC;QACF,MAAM,EAAE,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;QACvB,MAAM,EAAE,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;QACvB,MAAM,EAAE,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;QACvB,OAAO,MAAM,GAAG,EAAE,GAAG,MAAM,GAAG,EAAE,GAAG,MAAM,GAAG,EAAE,CAAC;IAChD,CAAC;IAEO,aAAa,CACpB,CAA2B,EAC3B,CAA2B;QAE3B,MAAM,EAAE,GAAG,IAAI,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC;QACrC,MAAM,EAAE,GAAG,IAAI,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC;QACrC,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;QACjC,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;QAChC,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC3C,CAAC;IAEO,KAAK,CACZ,EAA4B,EAC5B,EAA4B,EAC5B,KAAa;QAEb,OAAO;YACN,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC;YAC/C,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC;YAC/C,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC;SAC/C,CAAC;IACH,CAAC;IAEO,uBAAuB,CAAC,QAAyB;QAMxD,MAAM,eAAe,GAA6B,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;QACjE,MAAM,YAAY,GAA6B,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;QAC5D,MAAM,kBAAkB,GAA6B,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;QAErE,MAAM,MAAM,GACX,QAAQ;YACR,CAAC,OAAO,QAAQ,KAAK,WAAW,CAAC,CAAC,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACrE,MAAM,QAAQ,GACb,MAAM,IAAI,OAAO,gBAAgB,KAAK,UAAU;YAC/C,CAAC,CAAC,gBAAgB,CAAC,MAAM,CAAC;YAC1B,CAAC,CAAC,IAAI,CAAC;QAET,MAAM,UAAU,GACf,IAAI,CAAC,UAAU,CACd,QAAQ,EAAE,gBAAgB,CAAC,kBAAkB,CAAC;YAC7C,QAAQ,EAAE,eAAe,CAC1B,IAAI,kBAAkB,CAAC;QACzB,MAAM,IAAI,GACT,IAAI,CAAC,UAAU,CAAC,QAAQ,EAAE,gBAAgB,CAAC,YAAY,CAAC,CAAC,IAAI,YAAY,CAAC;QAC3E,MAAM,MAAM,GAAG,IAAI,CAAC,wBAAwB;YAC3C,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,wBAAwB,CAAC,KAAK,CAAC,IAAI,eAAe;YACzE,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,EAAE,gBAAgB,CAAC,eAAe,CAAC,CAAC;gBAC7D,eAAe,CAAC;QAElB,MAAM,iBAAiB,GAAG,IAAI,CAAC,wBAAwB;YACtD,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,wBAAwB,CAAC,OAAO,CAAC,CAAC,CAAC;YACxE,CAAC,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC;QAEtC,IAAI,eAAe,GAAG,iBAAiB,CAAC,iBAAiB,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QACtE,IAAI,SAAS,GAAG,CAAC,QAAQ,CAAC;QAE1B,KAAK,MAAM,OAAO,IAAI,iBAAiB,EAAE,CAAC;YACzC,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,UAAU,EAAE,OAAO,CAAC,CAAC;YACxD,MAAM,eAAe,GAAG,IAAI,CAAC,aAAa,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;YAChE,MAAM,YAAY,GAAG,IAAI,CAAC,aAAa,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;YACvD,MAAM,KAAK,GAAG,eAAe,GAAG,GAAG,GAAG,YAAY,GAAG,GAAG,CAAC;YACzD,IAAI,eAAe,IAAI,IAAI,IAAI,YAAY,IAAI,GAAG,EAAE,CAAC;gBACpD,eAAe,GAAG,OAAO,CAAC;gBAC1B,MAAM;YACP,CAAC;YACD,IAAI,KAAK,GAAG,SAAS,EAAE,CAAC;gBACvB,SAAS,GAAG,KAAK,CAAC;gBAClB,eAAe,GAAG,OAAO,CAAC;YAC3B,CAAC;QACF,CAAC;QAED,MAAM,eAAe,GAAG,IAAI,CAAC,GAAG,CAC/B,IAAI,EACJ,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,eAAe,GAAG,IAAI,CAAC,CACtC,CAAC;QACF,MAAM,gBAAgB,GAAG,IAAI,CAAC,GAAG,CAChC,IAAI,EACJ,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,eAAe,GAAG,GAAG,CAAC,CACrC,CAAC;QACF,MAAM,aAAa,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,eAAe,GAAG,IAAI,CAAC,CAAC,CAAC;QAE5E,MAAM,cAAc,GAAG,IAAI,CAAC;QAC5B,MAAM,cAAc,GAAG,IAAI,CAAC,KAAK,CAChC,cAAc,EACd,UAAU,EACV,gBAAgB,CAChB,CAAC;QACF,MAAM,cAAc,GAAG,IAAI,CAAC,aAAa,CAAC,cAAc,EAAE,UAAU,CAAC,CAAC;QACtE,MAAM,iBAAiB,GACtB,IAAI,CAAC,iBAAiB,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;QACzE,MAAM,cAAc,GACnB,cAAc,IAAI,IAAI,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,iBAAiB,CAAC;QAE7D,MAAM,aAAa,GAAG,QAAQ,MAAM,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC,CAAC,CAAC,KAAK,eAAe,GAAG,CAAC;QAC3F,MAAM,iBAAiB,GAAG,QAAQ,MAAM,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC,CAAC,CAAC,KAAK,eAAe,GAAG,CAAC;QAC/F,MAAM,aAAa,GAAG,QAAQ,cAAc,CAAC,CAAC,CAAC,KAAK,cAAc,CAAC,CAAC,CAAC,KAAK,cAAc,CAAC,CAAC,CAAC,KAAK,gBAAgB,GAAG,CAAC;QACpH,MAAM,UAAU,GAAG,QAAQ,cAAc,CAAC,CAAC,CAAC,KAAK,cAAc,CAAC,CAAC,CAAC,KAAK,cAAc,CAAC,CAAC,CAAC,KAAK,aAAa,GAAG,CAAC;QAE9G,OAAO;YACN,aAAa;YACb,iBAAiB;YACjB,aAAa;YACb,UAAU;SACV,CAAC;IACH,CAAC;IAEO,qBAAqB,CAAC,QAAyB;QACtD,IAAI,OAAO,QAAQ,KAAK,WAAW;YAAE,OAAO;QAC5C,MAAM,IAAI,GAAG,IAAI,CAAC,uBAAuB,CAAC,QAAQ,CAAC,CAAC;QACpD,QAAQ,CAAC,eAAe,CAAC,KAAK,CAAC,WAAW,CACzC,0BAA0B,EAC1B,IAAI,CAAC,aAAa,CAClB,CAAC;QACF,QAAQ,CAAC,eAAe,CAAC,KAAK,CAAC,WAAW,CACzC,8BAA8B,EAC9B,IAAI,CAAC,iBAAiB,CACtB,CAAC;QACF,QAAQ,CAAC,eAAe,CAAC,KAAK,CAAC,WAAW,CACzC,0BAA0B,EAC1B,IAAI,CAAC,iBAAiB,CACtB,CAAC;QACF,QAAQ,CAAC,eAAe,CAAC,KAAK,CAAC,WAAW,CACzC,0BAA0B,EAC1B,IAAI,CAAC,aAAa,CAClB,CAAC;QACF,QAAQ,CAAC,eAAe,CAAC,KAAK,CAAC,WAAW,CACzC,uBAAuB,EACvB,IAAI,CAAC,UAAU,CACf,CAAC;IACH,CAAC;IAED;;OAEG;IACK,oBAAoB;QAC3B,IAAI,CAAC,IAAI,CAAC,SAAS;YAAE,OAAO;QAE5B,sCAAsC;QACtC,IAAI,CAAC,gBAAgB,GAAG,IAAI,SAAS,EAAE,CAAC;QACxC,IAAI,CAAC,oBAAoB,GAAG,IAAI,SAAS,EAAE,CAAC;QAE5C,mCAAmC;QACnC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,UAAU,EAAE,IAAI,CAAC,gBAAgB,CAAC,CAAC;QACtD,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,cAAc,EAAE,IAAI,CAAC,oBAAoB,CAAC,CAAC;QAE9D,qDAAqD;QACrD,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC,EAAE,CAAC;YACnD,MAAM,SAAS,GAAG,IAAI,SAAS,EAAE,CAAC;YAClC,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;YAC3C,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,cAAc,KAAK,EAAE,EAAE,SAAS,CAAC,CAAC;QACtD,CAAC;IACF,CAAC;IAED;;OAEG;IACK,cAAc;QACrB,IAAI,CAAC,IAAI,CAAC,SAAS;YAAE,OAAO;QAC5B,IAAI,OAAO,QAAQ,KAAK,WAAW;YAAE,OAAO,CAAC,YAAY;QAEzD,gCAAgC;QAChC,IAAI,QAAQ,CAAC,cAAc,CAAC,sBAAsB,CAAC;YAAE,OAAO;QAE5D,MAAM,KAAK,GAAG,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;QAC9C,KAAK,CAAC,EAAE,GAAG,sBAAsB,CAAC;QAClC,KAAK,CAAC,WAAW,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAiLjB,CAAC;QACJ,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;IAClC,CAAC;IAED;;;;;;OAMG;IACH,gBAAgB,CACf,QAAc,EACd,WAAmB,EACnB,SAAiB;QAEjB,IAAI,CAAC,IAAI,CAAC,gBAAgB;YAAE,OAAO;QACnC,IAAI,CAAC,qBAAqB,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC;QAEnD,gCAAgC;QAChC,IAAI,CAAC,YAAY,EAAE,CAAC;QAEpB,wBAAwB;QACxB,MAAM,KAAK,GAAG,QAAQ,CAAC,WAAW,EAAE,CAAC;QACrC,KAAK,CAAC,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;QACtC,KAAK,CAAC,MAAM,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;QAElC,mBAAmB;QACnB,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IAClC,CAAC;IAED;;;;;;;;;;;;;;;;OAgBG;IACH,uBAAuB,CAAC,OAAgB;QACvC,IAAI,CAAC,IAAI,CAAC,gBAAgB;YAAE,OAAO;QACnC,IAAI,CAAC,qBAAqB,CAAC,OAAO,CAAC,CAAC;QAEpC,uEAAuE;QACvE,IAAI,CAAC,YAAY,EAAE,CAAC;QAEpB,OAAO,CAAC,YAAY,CAAC,2BAA2B,EAAE,MAAM,CAAC,CAAC;QAC1D,IAAI,CAAC,wBAAwB,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IAC5C,CAAC;IAED;;;;;OAKG;IACH,oBAAoB,CAAC,MAAe;QACnC,IAAI,CAAC,IAAI,CAAC,oBAAoB;YAAE,OAAO;QACvC,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,EAAE,cAAc,CAAC;QACzC,MAAM,aAAa,GAClB,MAAM,EAAE,QAAQ,KAAK,IAAI,CAAC,YAAY;YACrC,CAAC,CAAE,MAAkB;YACrB,CAAC,CAAC,MAAM,EAAE,aAAa,CAAC;QAC1B,IAAI,CAAC,qBAAqB,CAAC,aAAa,CAAC,CAAC;QAE1C,oCAAoC;QACpC,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAExB,iBAAiB;QACjB,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC5B,IAAI,CAAC,oBAAoB,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACtC,CAAC;QACD,IAAI,CAAC,4BAA4B,CAAC,MAAM,CAAC,CAAC;IAC3C,CAAC;IAED;;;;OAIG;IACH,4BAA4B,CAAC,QAAmB;QAC/C,IAAI,CAAC,IAAI,CAAC,oBAAoB;YAAE,OAAO;QACvC,IAAI,CAAC,qBAAqB,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC;QAEhD,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAExB,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;YAChC,OAAO,CAAC,YAAY,CAAC,+BAA+B,EAAE,MAAM,CAAC,CAAC;YAC9D,IAAI,CAAC,4BAA4B,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAChD,CAAC;IACF,CAAC;IAED,yEAAyE;IACzE,8CAA8C;IACtC,MAAM,CAAU,0BAA0B,GACjD,qDAAqD,CAAC;IAEvD,uEAAuE;IACvE,yEAAyE;IACzE,0EAA0E;IAC1E,6EAA6E;IAC7E,8EAA8E;IAC9E,kDAAkD;IAC1C,MAAM,CAAU,sBAAsB,GAC7C,gCAAgC,CAAC;IAE1B,4BAA4B,CAAC,MAAe;QACnD,IAAI,OAAO,OAAO,KAAK,WAAW;YAAE,OAAO;QAC3C,IAAI,CAAC,yBAAyB,CAC7B,MAAM,EACN,+BAA+B,EAC/B,IAAI,CAAC,4BAA4B,EACjC,oBAAoB,CAAC,0BAA0B,CAC/C,CAAC;IACH,CAAC;IAEO,gCAAgC,CAAC,MAAe;QACvD,IAAI,OAAO,OAAO,KAAK,WAAW;YAAE,OAAO;QAC3C,IAAI,CAAC,yBAAyB,CAC7B,MAAM,EACN,2BAA2B,EAC3B,IAAI,CAAC,wBAAwB,EAC7B,oBAAoB,CAAC,sBAAsB,CAC3C,CAAC;IACH,CAAC;IAEO,yBAAyB,CAChC,MAAe,EACf,aAAqB,EACrB,eAA6B,EAC7B,QAAgB;QAEhB,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC5B,MAAM,IAAI,GACT,KAAK,CAAC,uBAAuB,CAAC,QAAQ,KAAK,IAAI,CAAC,YAAY;gBAC3D,CAAC,CAAE,KAAK,CAAC,uBAAmC;gBAC5C,CAAC,CAAC,KAAK,CAAC,uBAAuB,CAAC,aAAa,CAAC;YAChD,IAAI,CAAC,IAAI;gBAAE,SAAS;YACpB,MAAM,UAAU,GAAc,EAAE,CAAC;YACjC,MAAM,iBAAiB,GACtB,KAAK,CAAC,cAAc,CAAC,QAAQ,KAAK,IAAI,CAAC,YAAY;gBAClD,CAAC,CAAE,KAAK,CAAC,cAA0B;gBACnC,CAAC,CAAC,KAAK,CAAC,cAAc,CAAC,aAAa,CAAC;YACvC,MAAM,eAAe,GACpB,KAAK,CAAC,YAAY,CAAC,QAAQ,KAAK,IAAI,CAAC,YAAY;gBAChD,CAAC,CAAE,KAAK,CAAC,YAAwB;gBACjC,CAAC,CAAC,KAAK,CAAC,YAAY,CAAC,aAAa,CAAC;YACrC,KAAK,MAAM,OAAO,IAAI,CAAC,iBAAiB,EAAE,eAAe,CAAC,EAAE,CAAC;gBAC5D,IAAI,OAAO,GAAmB,OAAO,CAAC;gBACtC,OAAO,OAAO,EAAE,CAAC;oBAChB,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC,QAAQ,CAAC;wBAAE,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;oBAC1D,OAAO,GAAG,OAAO,CAAC,aAAa,CAAC;gBACjC,CAAC;YACF,CAAC;YACD,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAC9B,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACvB,CAAC;YACD,UAAU,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;YAChE,KAAK,MAAM,OAAO,IAAI,IAAI,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC;gBAC3C,IAAI,CAAC;oBACJ,IACC,CAAC,KAAK,CAAC,cAAc,CAAC,OAAO,CAAC;wBAC9B,CAAC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAC;wBACpC,CAAC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAC,EACjC,CAAC;wBACF,SAAS;oBACV,CAAC;gBACF,CAAC;gBAAC,MAAM,CAAC;oBACR,SAAS;gBACV,CAAC;gBACD,OAAO,CAAC,YAAY,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;gBAC5C,eAAe,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YAC9B,CAAC;QACF,CAAC;IACF,CAAC;IAED;;OAEG;IACH,YAAY;QACX,IAAI,CAAC,IAAI,CAAC,gBAAgB;YAAE,OAAO;QACnC,IAAI,CAAC,gBAAgB,CAAC,KAAK,EAAE,CAAC;QAC9B,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,wBAAwB,EAAE,CAAC;YACrD,OAAO,CAAC,eAAe,CAAC,2BAA2B,CAAC,CAAC;QACtD,CAAC;QACD,IAAI,CAAC,wBAAwB,CAAC,KAAK,EAAE,CAAC;IACvC,CAAC;IAED;;OAEG;IACH,gBAAgB;QACf,IAAI,CAAC,IAAI,CAAC,oBAAoB;YAAE,OAAO;QACvC,IAAI,CAAC,oBAAoB,CAAC,KAAK,EAAE,CAAC;QAClC,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,4BAA4B,EAAE,CAAC;YACzD,OAAO,CAAC,eAAe,CAAC,+BAA+B,CAAC,CAAC;QAC1D,CAAC;QACD,IAAI,CAAC,4BAA4B,CAAC,KAAK,EAAE,CAAC;IAC3C,CAAC;IAED;;OAEG;IACH,QAAQ;QACP,IAAI,CAAC,YAAY,EAAE,CAAC;QACpB,IAAI,CAAC,gBAAgB,EAAE,CAAC;IACzB,CAAC;IAED;;;;;;OAMG;IACH,aAAa,CACZ,KAAY,EACZ,QAAwB,cAAc,CAAC,MAAM;QAE7C,MAAM,EAAE,GAAG,cAAc,IAAI,CAAC,gBAAgB,EAAE,EAAE,CAAC;QAEnD,2BAA2B;QAC3B,MAAM,WAAW,GAAG,KAAK,CAAC,UAAU,EAAE,CAAC;QAEvC,wBAAwB;QACxB,MAAM,UAAU,GAAe;YAC9B,EAAE;YACF,KAAK,EAAE,WAAW;YAClB,IAAI,EAAE,aAAa,CAAC,UAAU;YAC9B,KAAK;YACL,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;SACrB,CAAC;QACF,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,EAAE,UAAU,CAAC,CAAC;QAErC,0DAA0D;QAC1D,mDAAmD;QACnD,MAAM,cAAc,GAAG,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACvD,IAAI,cAAc,EAAE,CAAC;YACpB,cAAc,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;QACjC,CAAC;QAED,OAAO,EAAE,CAAC;IACX,CAAC;IAED;;;;OAIG;IACH,gBAAgB,CAAC,EAAU;QAC1B,MAAM,UAAU,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC5C,IAAI,CAAC,UAAU,EAAE,CAAC;YACjB,OAAO,CAAC,IAAI,CAAC,qCAAqC,EAAE,YAAY,CAAC,CAAC;YAClE,OAAO;QACR,CAAC;QAED,OAAO,CAAC,GAAG,CACV,8CAA8C,EAAE,YAAY,UAAU,CAAC,KAAK,GAAG,CAC/E,CAAC;QAEF,+CAA+C;QAC/C,MAAM,cAAc,GAAG,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;QAClE,IAAI,cAAc,EAAE,CAAC;YACpB,MAAM,OAAO,GAAG,cAAc,CAAC,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;YACxD,OAAO,CAAC,GAAG,CACV,qDAAqD,EACrD,OAAO,CACP,CAAC;QACH,CAAC;QAED,WAAW;QACX,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAC5B,OAAO,CAAC,GAAG,CACV,qCAAqC,EAAE,+BAA+B,EACtE,IAAI,CAAC,WAAW,CAAC,IAAI,CACrB,CAAC;IACH,CAAC;IAED;;OAEG;IACH,gBAAgB;QACf,6BAA6B;QAC7B,KAAK,MAAM,cAAc,IAAI,IAAI,CAAC,eAAe,CAAC,MAAM,EAAE,EAAE,CAAC;YAC5D,cAAc,CAAC,KAAK,EAAE,CAAC;QACxB,CAAC;QAED,wBAAwB;QACxB,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,CAAC;IAC1B,CAAC;IAED;;OAEG;IACH,cAAc;QACb,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,CAAC,CAAC;IAC9C,CAAC;IAED;;OAEG;IACH,aAAa,CAAC,EAAU;QACvB,OAAO,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,IAAI,CAAC;IACzC,CAAC;IAED;;;;;OAKG;IACH,qBAAqB,CAAC,EAAU,EAAE,QAAwB;QACzD,MAAM,UAAU,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC5C,IAAI,CAAC,UAAU;YAAE,OAAO;QAExB,MAAM,QAAQ,GAAG,UAAU,CAAC,KAAK,CAAC;QAClC,IAAI,QAAQ,KAAK,QAAQ;YAAE,OAAO;QAElC,kCAAkC;QAClC,MAAM,iBAAiB,GAAG,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC7D,IAAI,iBAAiB,EAAE,CAAC;YACvB,iBAAiB,CAAC,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;QAC5C,CAAC;QAED,eAAe;QACf,UAAU,CAAC,KAAK,GAAG,QAAQ,CAAC;QAE5B,6BAA6B;QAC7B,MAAM,iBAAiB,GAAG,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC7D,IAAI,iBAAiB,EAAE,CAAC;YACvB,iBAAiB,CAAC,GAAG,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;QACzC,CAAC;IACF,CAAC;IAED;;;;;;OAMG;IACH,iBAAiB,CAChB,OAAgB,QAAQ,CAAC,IAAI;QAI7B,OAAO,IAAI,CAAC,cAAc,EAAE,CAAC,GAAG,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;YACjD,GAAG,IAAI,CAAC,eAAe,CAAC,SAAS,CAAC,UAAU,CAAC,KAAK,EAAE,IAAI,CAAC;YACzD,EAAE,EAAE,UAAU,CAAC,EAAE;YACjB,KAAK,EAAE,UAAU,CAAC,KAAK;YACvB,SAAS,EAAE,UAAU,CAAC,SAAS;SAC/B,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;;;;;OAOG;IACH,iBAAiB,CAChB,IAMC,EACD,OAAgB,QAAQ,CAAC,IAAI;QAE7B,IAAI,QAAQ,GAAG,CAAC,CAAC;QAEjB,KAAK,MAAM,IAAI,IAAI,IAAI,EAAE,CAAC;YACzB,MAAM,KAAK,GAAG,IAAI,CAAC,eAAe,CAAC,WAAW,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;YAC3D,IAAI,KAAK,EAAE,CAAC;gBACX,IAAI,CAAC,aAAa,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;gBACtC,QAAQ,EAAE,CAAC;YACZ,CAAC;QACF,CAAC;QAED,OAAO,QAAQ,CAAC;IACjB,CAAC;IAED,+EAA+E;IAC/E,mDAAmD;IACnD,+EAA+E;IAE/E;;;;;OAKG;IACH,cAAc,CACb,KAAY,EACZ,IAAmB,EACnB,QAAwB,cAAc,CAAC,MAAM;QAE7C,IAAI,CAAC,IAAI,CAAC,SAAS;YAAE,OAAO;QAE5B,QAAQ,IAAI,EAAE,CAAC;YACd,KAAK,aAAa,CAAC,QAAQ,CAAC;YAC5B,KAAK,aAAa,CAAC,YAAY,CAAC,CAAC,CAAC;gBACjC,wCAAwC;gBACxC,MAAM,SAAS,GACd,IAAI,KAAK,aAAa,CAAC,QAAQ;oBAC9B,CAAC,CAAC,IAAI,CAAC,gBAAgB;oBACvB,CAAC,CAAC,IAAI,CAAC,oBAAoB,CAAC;gBAC9B,IAAI,SAAS,EAAE,CAAC;oBACf,SAAS,CAAC,KAAK,EAAE,CAAC;oBAClB,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;oBACrB,IAAI,IAAI,KAAK,aAAa,CAAC,QAAQ,EAAE,CAAC;wBACrC,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,wBAAwB,EAAE,CAAC;4BACrD,OAAO,CAAC,eAAe,CAAC,2BAA2B,CAAC,CAAC;wBACtD,CAAC;wBACD,IAAI,CAAC,wBAAwB,CAAC,KAAK,EAAE,CAAC;wBACtC,IAAI,CAAC,gCAAgC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;oBAChD,CAAC;gBACF,CAAC;gBACD,MAAM;YACP,CAAC;YACD,KAAK,aAAa,CAAC,UAAU;gBAC5B,2CAA2C;gBAC3C,IAAI,CAAC,aAAa,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;gBACjC,MAAM;QACR,CAAC;IACF,CAAC;IAED;;OAEG;IACH,eAAe,CAAC,IAAmB;QAClC,IAAI,CAAC,IAAI,CAAC,SAAS;YAAE,OAAO;QAE5B,QAAQ,IAAI,EAAE,CAAC;YACd,KAAK,aAAa,CAAC,QAAQ;gBAC1B,IAAI,CAAC,YAAY,EAAE,CAAC;gBACpB,MAAM;YACP,KAAK,aAAa,CAAC,YAAY;gBAC9B,IAAI,CAAC,gBAAgB,EAAE,CAAC;gBACxB,MAAM;YACP,KAAK,aAAa,CAAC,UAAU;gBAC5B,IAAI,CAAC,gBAAgB,EAAE,CAAC;gBACxB,MAAM;QACR,CAAC;IACF,CAAC;IAED;;OAEG;IACH,QAAQ;QACP,IAAI,CAAC,IAAI,CAAC,SAAS;YAAE,OAAO;QAC5B,IAAI,CAAC,QAAQ,EAAE,CAAC;QAChB,IAAI,CAAC,gBAAgB,EAAE,CAAC;IACzB,CAAC;IAED;;OAEG;IACH,WAAW;QACV,OAAO,IAAI,CAAC,SAAS,CAAC;IACvB,CAAC;IAED;;;;;OAKG;IACH,uBAAuB,CAAC,KAAa,EAAE,OAAe;QACrD,IAAI,CAAC,IAAI,CAAC,SAAS;YAAE,OAAO;QAC5B,IAAI,OAAO,QAAQ,KAAK,WAAW;YAAE,OAAO;QAE5C,MAAM,OAAO,GAAG,QAAQ,CAAC,cAAc,CAAC,sBAAsB,CAAC,CAAC;QAChE,IAAI,CAAC,OAAO;YAAE,OAAO;QAErB,sBAAsB;QACtB,MAAM,QAAQ,GAAG,CAAC,GAAW,EAAE,EAAE;YAChC,MAAM,MAAM,GAAG,2CAA2C,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACrE,OAAO,MAAM;gBACZ,CAAC,CAAC;oBACA,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;oBAC1B,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;oBAC1B,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;iBAC1B;gBACF,CAAC,CAAC,IAAI,CAAC;QACT,CAAC,CAAC;QAEF,MAAM,GAAG,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;QAC5B,IAAI,CAAC,GAAG;YAAE,OAAO;QAEjB,IAAI,CAAC,wBAAwB,GAAG;YAC/B,KAAK,EAAE,OAAO,GAAG,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG;YAC1C,OAAO,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;SAC/C,CAAC;QACF,IAAI,CAAC,qBAAqB,EAAE,CAAC;IAC9B,CAAC;IAED;;OAEG;IACH,OAAO;QACN,IAAI,CAAC,IAAI,CAAC,SAAS;YAAE,OAAO;QAE5B,IAAI,CAAC,QAAQ,EAAE,CAAC;QAChB,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAExB,uBAAuB;QACvB,IAAI,OAAO,QAAQ,KAAK,WAAW,EAAE,CAAC;YACrC,MAAM,OAAO,GAAG,QAAQ,CAAC,cAAc,CAAC,sBAAsB,CAAC,CAAC;YAChE,IAAI,OAAO,EAAE,CAAC;gBACb,OAAO,CAAC,MAAM,EAAE,CAAC;YAClB,CAAC;QACF,CAAC;QACD,IAAI,CAAC,aAAa,EAAE,UAAU,EAAE,CAAC;QACjC,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC;IAC3B,CAAC","sourcesContent":["/**\n * HighlightCoordinator\n *\n * Manages separate highlight layers for TTS (temporary) and annotations (persistent).\n * Uses CSS Custom Highlight API - zero DOM mutation, preserves accessibility.\n *\n * Features:\n * - Separate layers prevent TTS clearing from removing student annotations\n * - CSS Custom Highlight API (no DOM mutation)\n * - Full accessibility support (screen readers see original text)\n * - Configurable colors and styles\n * - Annotation persistence via RangeSerializer\n *\n * Part of PIE Assessment Toolkit.\n *\n * Browser Support:\n * - Chrome/Edge 105+\n * - Safari 17.2+\n * - Firefox: Behind flag (not recommended for production)\n *\n * @see https://developer.mozilla.org/en-US/docs/Web/API/CSS_Custom_Highlight_API\n */\n\nimport type { HighlightCoordinatorApi } from \"./interfaces.js\";\nimport { RangeSerializer, type SerializedRange } from \"./RangeSerializer.js\";\n\n/**\n * Highlight types\n */\nexport enum HighlightType {\n\tTTS_WORD = \"tts-word\",\n\tTTS_SENTENCE = \"tts-sentence\",\n\tANNOTATION = \"annotation\",\n}\n\n/**\n * Highlight colors for annotations\n */\nexport enum HighlightColor {\n\tYELLOW = \"yellow\",\n\tGREEN = \"green\",\n\tBLUE = \"blue\",\n\tPINK = \"pink\",\n\tORANGE = \"orange\",\n\tUNDERLINE = \"underline\",\n}\n\n/**\n * Annotation data\n */\nexport interface Annotation {\n\tid: string;\n\trange: Range;\n\ttype: HighlightType.ANNOTATION;\n\tcolor: HighlightColor;\n\ttimestamp: number;\n}\n\n/**\n * Configuration for HighlightCoordinator\n * Currently empty but allows future extension without breaking changes\n */\nexport type HighlightCoordinatorConfig = Record<string, never>;\n\nexport class HighlightCoordinator implements HighlightCoordinatorApi {\n\tprivate config: HighlightCoordinatorConfig;\n\tprivate ttsWordHighlight: Highlight | null = null;\n\tprivate ttsSentenceHighlight: Highlight | null = null;\n\tprivate ttsWordElementHighlights = new Set<Element>();\n\tprivate ttsSentenceElementHighlights = new Set<Element>();\n\tprivate annotations = new Map<string, Annotation>();\n\t// Shared highlights per color (one Highlight object per color, contains all ranges)\n\tprivate colorHighlights = new Map<HighlightColor, Highlight>();\n\tprivate nextAnnotationId = 1;\n\tprivate supported = false;\n\tprivate rangeSerializer: RangeSerializer;\n\tprivate themeObserver: MutationObserver | null = null;\n\tprivate explicitTTSColorOverride: { color: string; opacity: number } | null =\n\t\tnull;\n\n\tconstructor(config: HighlightCoordinatorConfig = {}) {\n\t\tthis.config = config;\n\t\tthis.rangeSerializer = new RangeSerializer();\n\n\t\t// SSR guard\n\t\tif (typeof CSS === \"undefined\" || !(\"highlights\" in CSS)) {\n\t\t\tconsole.warn(\n\t\t\t\t\"CSS Custom Highlight API not supported (SSR or unsupported browser)\",\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.supported = true;\n\t\tthis.initializeHighlights();\n\t\tthis.registerStyles();\n\t\tthis.applyAdaptiveTTSStyle();\n\t\tthis.setupThemeObservation();\n\t}\n\n\tprivate setupThemeObservation(): void {\n\t\tif (typeof document === \"undefined\") return;\n\t\tif (typeof MutationObserver === \"undefined\") return;\n\n\t\tconst refresh = () => this.applyAdaptiveTTSStyle();\n\t\tthis.themeObserver = new MutationObserver(refresh);\n\n\t\tthis.themeObserver.observe(document.documentElement, {\n\t\t\tattributes: true,\n\t\t\tattributeFilter: [\"style\", \"data-theme\", \"data-color-scheme\", \"class\"],\n\t\t});\n\n\t\tfor (const host of document.querySelectorAll(\"pie-theme\")) {\n\t\t\tthis.themeObserver.observe(host, {\n\t\t\t\tattributes: true,\n\t\t\t\tattributeFilter: [\n\t\t\t\t\t\"theme\",\n\t\t\t\t\t\"scheme\",\n\t\t\t\t\t\"provider\",\n\t\t\t\t\t\"variables\",\n\t\t\t\t\t\"style\",\n\t\t\t\t\t\"data-theme\",\n\t\t\t\t\t\"data-color-scheme\",\n\t\t\t\t],\n\t\t\t});\n\t\t}\n\t}\n\n\tprivate parseColor(\n\t\tinput: string | null | undefined,\n\t): [number, number, number] | null {\n\t\tif (!input) return null;\n\t\tconst value = input.trim();\n\t\tif (!value) return null;\n\n\t\tconst hexMatch = /^#([a-f\\d]{3}|[a-f\\d]{6})$/i.exec(value);\n\t\tif (hexMatch) {\n\t\t\tconst hex = hexMatch[1];\n\t\t\tif (hex.length === 3) {\n\t\t\t\treturn [\n\t\t\t\t\tparseInt(hex[0] + hex[0], 16),\n\t\t\t\t\tparseInt(hex[1] + hex[1], 16),\n\t\t\t\t\tparseInt(hex[2] + hex[2], 16),\n\t\t\t\t];\n\t\t\t}\n\t\t\treturn [\n\t\t\t\tparseInt(hex.slice(0, 2), 16),\n\t\t\t\tparseInt(hex.slice(2, 4), 16),\n\t\t\t\tparseInt(hex.slice(4, 6), 16),\n\t\t\t];\n\t\t}\n\n\t\tconst rgbMatch = /^rgba?\\((.+)\\)$/i.exec(value);\n\t\tif (rgbMatch) {\n\t\t\tconst normalized = rgbMatch[1].replace(/\\//g, \",\");\n\t\t\tconst parts = normalized\n\t\t\t\t.split(/[,\\s]+/)\n\t\t\t\t.map((part) => part.trim())\n\t\t\t\t.filter(Boolean);\n\t\t\tconst r = Number(parts[0]);\n\t\t\tconst g = Number(parts[1]);\n\t\t\tconst b = Number(parts[2]);\n\t\t\tif (Number.isFinite(r) && Number.isFinite(g) && Number.isFinite(b)) {\n\t\t\t\treturn [r, g, b];\n\t\t\t}\n\t\t}\n\n\t\t// Browser parser fallback (covers formats like oklch()).\n\t\tif (typeof document !== \"undefined\") {\n\t\t\tconst parserEl = document.createElement(\"span\");\n\t\t\tparserEl.style.color = value;\n\t\t\tif (typeof parserEl.style.color === \"string\" && parserEl.style.color) {\n\t\t\t\tdocument.body?.appendChild(parserEl);\n\t\t\t\tconst resolved =\n\t\t\t\t\ttypeof getComputedStyle === \"function\"\n\t\t\t\t\t\t? getComputedStyle(parserEl).color\n\t\t\t\t\t\t: \"\";\n\t\t\t\tparserEl.remove();\n\t\t\t\tconst normalizedResolved = resolved.trim();\n\t\t\t\t// Guard against recursive loops when computed style returns the same\n\t\t\t\t// unresolved function syntax (observed with certain color formats).\n\t\t\t\tif (normalizedResolved && normalizedResolved !== value) {\n\t\t\t\t\treturn this.parseColor(resolved);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn null;\n\t}\n\n\tprivate relativeLuminance([r, g, b]: [number, number, number]): number {\n\t\tconst toLinear = (channel: number) => {\n\t\t\tconst n = channel / 255;\n\t\t\treturn n <= 0.03928 ? n / 12.92 : ((n + 0.055) / 1.055) ** 2.4;\n\t\t};\n\t\tconst lr = toLinear(r);\n\t\tconst lg = toLinear(g);\n\t\tconst lb = toLinear(b);\n\t\treturn 0.2126 * lr + 0.7152 * lg + 0.0722 * lb;\n\t}\n\n\tprivate contrastRatio(\n\t\ta: [number, number, number],\n\t\tb: [number, number, number],\n\t): number {\n\t\tconst la = this.relativeLuminance(a);\n\t\tconst lb = this.relativeLuminance(b);\n\t\tconst lighter = Math.max(la, lb);\n\t\tconst darker = Math.min(la, lb);\n\t\treturn (lighter + 0.05) / (darker + 0.05);\n\t}\n\n\tprivate blend(\n\t\tfg: [number, number, number],\n\t\tbg: [number, number, number],\n\t\talpha: number,\n\t): [number, number, number] {\n\t\treturn [\n\t\t\tMath.round(fg[0] * alpha + bg[0] * (1 - alpha)),\n\t\t\tMath.round(fg[1] * alpha + bg[1] * (1 - alpha)),\n\t\t\tMath.round(fg[2] * alpha + bg[2] * (1 - alpha)),\n\t\t];\n\t}\n\n\tprivate resolveAdaptiveTTSStyle(sourceEl?: Element | null): {\n\t\twordHighlight: string;\n\t\tsentenceHighlight: string;\n\t\twordUnderline: string;\n\t\twordShadow: string;\n\t} {\n\t\tconst fallbackMissing: [number, number, number] = [255, 235, 59];\n\t\tconst fallbackText: [number, number, number] = [17, 24, 39];\n\t\tconst fallbackBackground: [number, number, number] = [255, 255, 255];\n\n\t\tconst target =\n\t\t\tsourceEl ||\n\t\t\t(typeof document !== \"undefined\" ? document.documentElement : null);\n\t\tconst computed =\n\t\t\ttarget && typeof getComputedStyle === \"function\"\n\t\t\t\t? getComputedStyle(target)\n\t\t\t\t: null;\n\n\t\tconst background =\n\t\t\tthis.parseColor(\n\t\t\t\tcomputed?.getPropertyValue(\"--pie-background\") ||\n\t\t\t\t\tcomputed?.backgroundColor,\n\t\t\t) || fallbackBackground;\n\t\tconst text =\n\t\t\tthis.parseColor(computed?.getPropertyValue(\"--pie-text\")) || fallbackText;\n\t\tconst accent = this.explicitTTSColorOverride\n\t\t\t? this.parseColor(this.explicitTTSColorOverride.color) || fallbackMissing\n\t\t\t: this.parseColor(computed?.getPropertyValue(\"--pie-missing\")) ||\n\t\t\t\tfallbackMissing;\n\n\t\tconst opacityCandidates = this.explicitTTSColorOverride\n\t\t\t? [Math.max(0.3, Math.min(0.95, this.explicitTTSColorOverride.opacity))]\n\t\t\t: [0.8, 0.72, 0.68, 0.62, 0.56, 0.5];\n\n\t\tlet selectedOpacity = opacityCandidates[opacityCandidates.length - 1];\n\t\tlet bestScore = -Infinity;\n\n\t\tfor (const opacity of opacityCandidates) {\n\t\t\tconst blended = this.blend(accent, background, opacity);\n\t\t\tconst backgroundDelta = this.contrastRatio(blended, background);\n\t\t\tconst textContrast = this.contrastRatio(blended, text);\n\t\t\tconst score = backgroundDelta * 1.2 + textContrast * 0.8;\n\t\t\tif (backgroundDelta >= 1.25 && textContrast >= 2.4) {\n\t\t\t\tselectedOpacity = opacity;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (score > bestScore) {\n\t\t\t\tbestScore = score;\n\t\t\t\tselectedOpacity = opacity;\n\t\t\t}\n\t\t}\n\n\t\tconst sentenceOpacity = Math.max(\n\t\t\t0.24,\n\t\t\tMath.min(0.85, selectedOpacity * 0.55),\n\t\t);\n\t\tconst underlineOpacity = Math.max(\n\t\t\t0.55,\n\t\t\tMath.min(0.95, selectedOpacity + 0.2),\n\t\t);\n\t\tconst shadowOpacity = Math.max(0.22, Math.min(0.6, selectedOpacity * 0.45));\n\n\t\tconst underlineColor = text;\n\t\tconst underlineBlend = this.blend(\n\t\t\tunderlineColor,\n\t\t\tbackground,\n\t\t\tunderlineOpacity,\n\t\t);\n\t\tconst underlineDelta = this.contrastRatio(underlineBlend, background);\n\t\tconst fallbackUnderline: [number, number, number] =\n\t\t\tthis.relativeLuminance(background) > 0.45 ? [0, 0, 0] : [255, 255, 255];\n\t\tconst finalUnderline =\n\t\t\tunderlineDelta >= 1.35 ? underlineColor : fallbackUnderline;\n\n\t\tconst wordHighlight = `rgba(${accent[0]}, ${accent[1]}, ${accent[2]}, ${selectedOpacity})`;\n\t\tconst sentenceHighlight = `rgba(${accent[0]}, ${accent[1]}, ${accent[2]}, ${sentenceOpacity})`;\n\t\tconst wordUnderline = `rgba(${finalUnderline[0]}, ${finalUnderline[1]}, ${finalUnderline[2]}, ${underlineOpacity})`;\n\t\tconst wordShadow = `rgba(${finalUnderline[0]}, ${finalUnderline[1]}, ${finalUnderline[2]}, ${shadowOpacity})`;\n\n\t\treturn {\n\t\t\twordHighlight,\n\t\t\tsentenceHighlight,\n\t\t\twordUnderline,\n\t\t\twordShadow,\n\t\t};\n\t}\n\n\tprivate applyAdaptiveTTSStyle(sourceEl?: Element | null): void {\n\t\tif (typeof document === \"undefined\") return;\n\t\tconst vars = this.resolveAdaptiveTTSStyle(sourceEl);\n\t\tdocument.documentElement.style.setProperty(\n\t\t\t\"--pie-tts-word-highlight\",\n\t\t\tvars.wordHighlight,\n\t\t);\n\t\tdocument.documentElement.style.setProperty(\n\t\t\t\"--pie-tts-sentence-highlight\",\n\t\t\tvars.sentenceHighlight,\n\t\t);\n\t\tdocument.documentElement.style.setProperty(\n\t\t\t\"--pie-tts-line-highlight\",\n\t\t\tvars.sentenceHighlight,\n\t\t);\n\t\tdocument.documentElement.style.setProperty(\n\t\t\t\"--pie-tts-word-underline\",\n\t\t\tvars.wordUnderline,\n\t\t);\n\t\tdocument.documentElement.style.setProperty(\n\t\t\t\"--pie-tts-word-shadow\",\n\t\t\tvars.wordShadow,\n\t\t);\n\t}\n\n\t/**\n\t * Initialize CSS Custom Highlights\n\t */\n\tprivate initializeHighlights(): void {\n\t\tif (!this.supported) return;\n\n\t\t// Create highlight registries for TTS\n\t\tthis.ttsWordHighlight = new Highlight();\n\t\tthis.ttsSentenceHighlight = new Highlight();\n\n\t\t// Register TTS highlights with CSS\n\t\tCSS.highlights.set(\"tts-word\", this.ttsWordHighlight);\n\t\tCSS.highlights.set(\"tts-sentence\", this.ttsSentenceHighlight);\n\n\t\t// Create shared highlights for each annotation color\n\t\tfor (const color of Object.values(HighlightColor)) {\n\t\t\tconst highlight = new Highlight();\n\t\t\tthis.colorHighlights.set(color, highlight);\n\t\t\tCSS.highlights.set(`annotation-${color}`, highlight);\n\t\t}\n\t}\n\n\t/**\n\t * Register CSS styles for highlights\n\t */\n\tprivate registerStyles(): void {\n\t\tif (!this.supported) return;\n\t\tif (typeof document === \"undefined\") return; // SSR guard\n\n\t\t// Check if styles already exist\n\t\tif (document.getElementById(\"pie-highlight-styles\")) return;\n\n\t\tconst style = document.createElement(\"style\");\n\t\tstyle.id = \"pie-highlight-styles\";\n\t\tstyle.textContent = `\n /* TTS highlights - temporary */\n ::highlight(tts-word) {\n background-color: var(--pie-tts-word-highlight, color-mix(in srgb, var(--pie-missing, #ffeb3b) 68%, transparent));\n text-decoration: underline 2px solid var(--pie-tts-word-underline, color-mix(in srgb, var(--pie-text, #111827) 70%, transparent));\n text-underline-offset: 2px;\n text-shadow: 0 0 1px var(--pie-tts-word-shadow, color-mix(in srgb, var(--pie-text, #111827) 35%, transparent));\n color: inherit;\n }\n\n /* tts-sentence registry id: coarse read-along band (visual line boxes in layout) */\n ::highlight(tts-sentence) {\n background-color: var(--pie-tts-line-highlight, var(--pie-tts-sentence-highlight, color-mix(in srgb, var(--pie-missing, #ffeb3b) 38%, transparent)));\n color: inherit;\n }\n\n [data-pie-tts-sentence-element=\"true\"] {\n background-color: var(--pie-tts-line-highlight, var(--pie-tts-sentence-highlight, color-mix(in srgb, var(--pie-missing, #ffeb3b) 38%, transparent)));\n border-radius: 0.12em;\n box-decoration-break: clone;\n -webkit-box-decoration-break: clone;\n }\n\n [data-pie-tts-word-element=\"true\"] {\n background-color: var(--pie-tts-word-highlight, color-mix(in srgb, var(--pie-missing, #ffeb3b) 68%, transparent));\n text-decoration: underline 2px solid var(--pie-tts-word-underline, color-mix(in srgb, var(--pie-text, #111827) 70%, transparent));\n text-underline-offset: 2px;\n text-shadow: 0 0 1px var(--pie-tts-word-shadow, color-mix(in srgb, var(--pie-text, #111827) 35%, transparent));\n border-bottom: 2px solid var(--pie-tts-word-underline, color-mix(in srgb, var(--pie-text, #111827) 70%, transparent));\n padding-bottom: 1px;\n border-radius: 0.12em;\n box-decoration-break: clone;\n -webkit-box-decoration-break: clone;\n }\n\n /* Annotation highlights - persistent */\n ::highlight(annotation-yellow) {\n background-color: var(--pie-annotation-yellow-highlight, rgba(253, 233, 149, 0.5));\n color: inherit;\n }\n\n ::highlight(annotation-green) {\n background-color: var(--pie-annotation-green-highlight, rgba(166, 225, 197, 0.5));\n color: inherit;\n }\n\n ::highlight(annotation-blue) {\n background-color: var(--pie-annotation-blue-highlight, rgba(167, 224, 246, 0.5));\n color: inherit;\n }\n\n ::highlight(annotation-pink) {\n background-color: var(--pie-annotation-pink-highlight, rgba(255, 159, 174, 0.5));\n color: inherit;\n }\n\n ::highlight(annotation-orange) {\n background-color: var(--pie-annotation-orange-highlight, rgba(255, 165, 0, 0.5));\n color: inherit;\n }\n\n ::highlight(annotation-underline) {\n background-color: transparent;\n text-decoration: underline 2px solid var(--pie-primary, #0066cc);\n text-underline-offset: 2px;\n color: inherit;\n }\n\n /* The blocks below cover annotation swatches, print, and reduced motion.\n They deliberately do NOT retune the TTS layers: applyAdaptiveTTSStyle()\n writes the pie-tts custom properties inline on documentElement on every\n paint and on theme change, so a media query that only varies a var()\n fallback for those can never take effect. Annotation colours have no such\n adaptive path -- they are fixed swatches a student chose -- so a media\n query is the only way to adjust them, and each keeps its var() so a host\n override still wins.\n\n The names above are spelled without their leading dashes on purpose.\n check-theme-tokens scans comments, and its token pattern stops at the\n first non-alphanumeric character, so writing the wildcard form reads as\n consumption of a shorter token that is not in the registry and fails the\n check. Spell property names in full, or omit the dashes. */\n\n @media (prefers-color-scheme: dark) {\n ::highlight(annotation-yellow) {\n background-color: var(--pie-annotation-yellow-highlight, rgba(139, 117, 0, 0.6));\n }\n ::highlight(annotation-green) {\n background-color: var(--pie-annotation-green-highlight, rgba(45, 92, 63, 0.6));\n }\n ::highlight(annotation-blue) {\n background-color: var(--pie-annotation-blue-highlight, rgba(0, 102, 170, 0.6));\n }\n ::highlight(annotation-pink) {\n background-color: var(--pie-annotation-pink-highlight, rgba(139, 51, 74, 0.6));\n }\n ::highlight(annotation-orange) {\n background-color: var(--pie-annotation-orange-highlight, rgba(154, 99, 0, 0.6));\n }\n ::highlight(annotation-underline) {\n text-decoration-color: var(--pie-primary, #4da6ff);\n }\n }\n\n /* WCAG 2.2 SC 1.4.11 non-text contrast: a highlight is the only indication\n that text is annotated, so the swatches saturate and the underline\n thickens when the user asks for more contrast.\n\n The value is \"more\", not \"high\". The stylesheet this was recovered from\n used prefers-contrast: high, which is not a valid value for the feature\n -- the keywords are no-preference, more, less, custom. An invalid query\n evaluates to \"not all\", so that block could never have matched in any\n browser even had the file been loaded. Verified with matchMedia under\n emulation: \"high\" stays false where \"more\" flips true. */\n @media (prefers-contrast: more) {\n ::highlight(annotation-yellow) {\n background-color: var(--pie-annotation-yellow-highlight, rgba(255, 255, 0, 0.7));\n }\n ::highlight(annotation-green) {\n background-color: var(--pie-annotation-green-highlight, rgba(0, 255, 127, 0.7));\n }\n ::highlight(annotation-blue) {\n background-color: var(--pie-annotation-blue-highlight, rgba(0, 191, 255, 0.7));\n }\n ::highlight(annotation-pink) {\n background-color: var(--pie-annotation-pink-highlight, rgba(255, 105, 180, 0.7));\n }\n ::highlight(annotation-orange) {\n background-color: var(--pie-annotation-orange-highlight, rgba(255, 165, 0, 0.85));\n }\n ::highlight(annotation-underline) {\n text-decoration-thickness: 3px;\n }\n }\n\n @media print {\n /* TTS highlighting is a transient read-along cue, not content. */\n ::highlight(tts-word),\n ::highlight(tts-sentence),\n [data-pie-tts-word-element=\"true\"],\n [data-pie-tts-sentence-element=\"true\"] {\n background-color: transparent;\n text-decoration: none;\n text-shadow: none;\n border-bottom: none;\n }\n\n /* Annotations are student work and must survive printing, but a fill\n that reads fine on screen can print as an illegible wash, so each\n becomes an underline that keeps its colour coding. */\n ::highlight(annotation-yellow),\n ::highlight(annotation-green),\n ::highlight(annotation-blue),\n ::highlight(annotation-pink),\n ::highlight(annotation-orange) {\n background-color: transparent;\n border-bottom: 2px solid currentColor;\n }\n ::highlight(annotation-yellow) { border-bottom-color: #ffeb3b; }\n ::highlight(annotation-green) { border-bottom-color: #a6e1c5; }\n ::highlight(annotation-blue) { border-bottom-color: #a7e0f6; }\n ::highlight(annotation-pink) { border-bottom-color: #ff9fae; }\n ::highlight(annotation-orange) { border-bottom-color: #ffa500; }\n ::highlight(annotation-underline) {\n text-decoration-color: #000;\n border-bottom: none;\n }\n }\n\n /* text-shadow is a direct property rather than a var(), so unlike the\n other TTS rules this one is not overridden by the adaptive path. */\n @media (prefers-reduced-motion: reduce) {\n ::highlight(tts-word),\n [data-pie-tts-word-element=\"true\"] {\n text-shadow: none;\n }\n }\n `;\n\t\tdocument.head.appendChild(style);\n\t}\n\n\t/**\n\t * Highlight a word for TTS (temporary)\n\t *\n\t * @param textNode Text node containing the word\n\t * @param startOffset Start position in text node\n\t * @param endOffset End position in text node\n\t */\n\thighlightTTSWord(\n\t\ttextNode: Text,\n\t\tstartOffset: number,\n\t\tendOffset: number,\n\t): void {\n\t\tif (!this.ttsWordHighlight) return;\n\t\tthis.applyAdaptiveTTSStyle(textNode.parentElement);\n\n\t\t// Clear previous word highlight\n\t\tthis.clearTTSWord();\n\n\t\t// Create range for word\n\t\tconst range = document.createRange();\n\t\trange.setStart(textNode, startOffset);\n\t\trange.setEnd(textNode, endOffset);\n\n\t\t// Add to highlight\n\t\tthis.ttsWordHighlight.add(range);\n\t}\n\n\t/**\n\t * Highlight a single element as the active TTS word (temporary).\n\t *\n\t * Unlike {@link highlightTTSWord} (which paints a CSS range over a text node)\n\t * this marks the element itself via `data-pie-tts-word-element`. It is used\n\t * for atomic targets that have no direct text node to range over — most\n\t * notably MathJax CHTML tokens (e.g. `<mjx-mi><mjx-c/></mjx-mi>`, whose glyph\n\t * lives in a font-driven pseudo-element a CSS range cannot paint) and\n\t * whole-expression fallbacks.\n\t *\n\t * The supplied element is painted exactly; we deliberately do NOT walk up to\n\t * a containing `<math>` / `<mjx-container>`. That escalation is what made\n\t * MathJax-rendered math highlight as a full block while native MathML tracked\n\t * per-token: a resolved single token must paint as a token, and only a\n\t * genuine expression fallback (where the element already is the container)\n\t * should paint the whole expression.\n\t */\n\thighlightTTSWordElement(element: Element): void {\n\t\tif (!this.ttsWordHighlight) return;\n\t\tthis.applyAdaptiveTTSStyle(element);\n\n\t\t// Clear previous word highlight (CSS range and any element attributes)\n\t\tthis.clearTTSWord();\n\n\t\telement.setAttribute(\"data-pie-tts-word-element\", \"true\");\n\t\tthis.ttsWordElementHighlights.add(element);\n\t}\n\n\t/**\n\t * Highlight the coarse TTS read-along band (temporary).\n\t * Usually one Range per visual line; CSS layer `tts-sentence` for compatibility.\n\t *\n\t * @param ranges Line (or sentence) ranges to paint with `--pie-tts-line-highlight`\n\t */\n\thighlightTTSSentence(ranges: Range[]): void {\n\t\tif (!this.ttsSentenceHighlight) return;\n\t\tconst source = ranges[0]?.startContainer;\n\t\tconst sourceElement =\n\t\t\tsource?.nodeType === Node.ELEMENT_NODE\n\t\t\t\t? (source as Element)\n\t\t\t\t: source?.parentElement;\n\t\tthis.applyAdaptiveTTSStyle(sourceElement);\n\n\t\t// Clear previous sentence highlight\n\t\tthis.clearTTSSentence();\n\n\t\t// Add all ranges\n\t\tfor (const range of ranges) {\n\t\t\tthis.ttsSentenceHighlight.add(range);\n\t\t}\n\t\tthis.highlightTTSElementFallbacks(ranges);\n\t}\n\n\t/**\n\t * Highlight sentence/block elements for TTS (background layer).\n\t *\n\t * @param elements Visible elements to paint with `--pie-tts-line-highlight`\n\t */\n\thighlightTTSSentenceElements(elements: Element[]): void {\n\t\tif (!this.ttsSentenceHighlight) return;\n\t\tthis.applyAdaptiveTTSStyle(elements[0] || null);\n\n\t\tthis.clearTTSSentence();\n\n\t\tfor (const element of elements) {\n\t\t\telement.setAttribute(\"data-pie-tts-sentence-element\", \"true\");\n\t\t\tthis.ttsSentenceElementHighlights.add(element);\n\t\t}\n\t}\n\n\t// The read-along (sentence) band legitimately covers whole equations and\n\t// replaced elements, so it escalates to them.\n\tprivate static readonly SENTENCE_FALLBACK_SELECTOR =\n\t\t\"math, mjx-container, svg, img, canvas, [role='img']\";\n\n\t// The word layer must NOT escalate a multi-node range to the enclosing\n\t// `<math>` / `<mjx-container>`: that is what made an equation flash as a\n\t// whole block whenever a spoken word's visible range crossed several math\n\t// glyph nodes. Math is tracked per token (or held / region) by the highlight\n\t// pipeline instead. Replaced elements (svg/img/canvas) still need the element\n\t// fallback because a CSS range cannot paint them.\n\tprivate static readonly WORD_FALLBACK_SELECTOR =\n\t\t\"svg, img, canvas, [role='img']\";\n\n\tprivate highlightTTSElementFallbacks(ranges: Range[]): void {\n\t\tif (typeof Element === \"undefined\") return;\n\t\tthis.highlightElementFallbacks(\n\t\t\tranges,\n\t\t\t\"data-pie-tts-sentence-element\",\n\t\t\tthis.ttsSentenceElementHighlights,\n\t\t\tHighlightCoordinator.SENTENCE_FALLBACK_SELECTOR,\n\t\t);\n\t}\n\n\tprivate highlightTTSWordElementFallbacks(ranges: Range[]): void {\n\t\tif (typeof Element === \"undefined\") return;\n\t\tthis.highlightElementFallbacks(\n\t\t\tranges,\n\t\t\t\"data-pie-tts-word-element\",\n\t\t\tthis.ttsWordElementHighlights,\n\t\t\tHighlightCoordinator.WORD_FALLBACK_SELECTOR,\n\t\t);\n\t}\n\n\tprivate highlightElementFallbacks(\n\t\tranges: Range[],\n\t\tattributeName: string,\n\t\ttrackedElements: Set<Element>,\n\t\tselector: string,\n\t): void {\n\t\tfor (const range of ranges) {\n\t\t\tconst root =\n\t\t\t\trange.commonAncestorContainer.nodeType === Node.ELEMENT_NODE\n\t\t\t\t\t? (range.commonAncestorContainer as Element)\n\t\t\t\t\t: range.commonAncestorContainer.parentElement;\n\t\t\tif (!root) continue;\n\t\t\tconst candidates: Element[] = [];\n\t\t\tconst rangeStartElement =\n\t\t\t\trange.startContainer.nodeType === Node.ELEMENT_NODE\n\t\t\t\t\t? (range.startContainer as Element)\n\t\t\t\t\t: range.startContainer.parentElement;\n\t\t\tconst rangeEndElement =\n\t\t\t\trange.endContainer.nodeType === Node.ELEMENT_NODE\n\t\t\t\t\t? (range.endContainer as Element)\n\t\t\t\t\t: range.endContainer.parentElement;\n\t\t\tfor (const element of [rangeStartElement, rangeEndElement]) {\n\t\t\t\tlet current: Element | null = element;\n\t\t\t\twhile (current) {\n\t\t\t\t\tif (current.matches?.(selector)) candidates.push(current);\n\t\t\t\t\tcurrent = current.parentElement;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (root.matches?.(selector)) {\n\t\t\t\tcandidates.push(root);\n\t\t\t}\n\t\t\tcandidates.push(...Array.from(root.querySelectorAll(selector)));\n\t\t\tfor (const element of new Set(candidates)) {\n\t\t\t\ttry {\n\t\t\t\t\tif (\n\t\t\t\t\t\t!range.intersectsNode(element) &&\n\t\t\t\t\t\t!element.contains(rangeStartElement) &&\n\t\t\t\t\t\t!element.contains(rangeEndElement)\n\t\t\t\t\t) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t} catch {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\telement.setAttribute(attributeName, \"true\");\n\t\t\t\ttrackedElements.add(element);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Clear TTS word highlight\n\t */\n\tclearTTSWord(): void {\n\t\tif (!this.ttsWordHighlight) return;\n\t\tthis.ttsWordHighlight.clear();\n\t\tfor (const element of this.ttsWordElementHighlights) {\n\t\t\telement.removeAttribute(\"data-pie-tts-word-element\");\n\t\t}\n\t\tthis.ttsWordElementHighlights.clear();\n\t}\n\n\t/**\n\t * Clear TTS sentence highlight\n\t */\n\tclearTTSSentence(): void {\n\t\tif (!this.ttsSentenceHighlight) return;\n\t\tthis.ttsSentenceHighlight.clear();\n\t\tfor (const element of this.ttsSentenceElementHighlights) {\n\t\t\telement.removeAttribute(\"data-pie-tts-sentence-element\");\n\t\t}\n\t\tthis.ttsSentenceElementHighlights.clear();\n\t}\n\n\t/**\n\t * Clear all TTS highlights\n\t */\n\tclearTTS(): void {\n\t\tthis.clearTTSWord();\n\t\tthis.clearTTSSentence();\n\t}\n\n\t/**\n\t * Add an annotation highlight (persistent)\n\t *\n\t * @param range Text range to annotate\n\t * @param color Highlight color\n\t * @returns Annotation ID for later removal\n\t */\n\taddAnnotation(\n\t\trange: Range,\n\t\tcolor: HighlightColor = HighlightColor.YELLOW,\n\t): string {\n\t\tconst id = `annotation-${this.nextAnnotationId++}`;\n\n\t\t// Clone the range to store\n\t\tconst clonedRange = range.cloneRange();\n\n\t\t// Store annotation data\n\t\tconst annotation: Annotation = {\n\t\t\tid,\n\t\t\trange: clonedRange,\n\t\t\ttype: HighlightType.ANNOTATION,\n\t\t\tcolor,\n\t\t\ttimestamp: Date.now(),\n\t\t};\n\t\tthis.annotations.set(id, annotation);\n\n\t\t// Add the SAME range object to the shared color highlight\n\t\t// This ensures we can later delete it by reference\n\t\tconst colorHighlight = this.colorHighlights.get(color);\n\t\tif (colorHighlight) {\n\t\t\tcolorHighlight.add(clonedRange);\n\t\t}\n\n\t\treturn id;\n\t}\n\n\t/**\n\t * Remove an annotation\n\t *\n\t * @param id Annotation ID\n\t */\n\tremoveAnnotation(id: string): void {\n\t\tconst annotation = this.annotations.get(id);\n\t\tif (!annotation) {\n\t\t\tconsole.warn(`[HighlightCoordinator] Annotation ${id} not found`);\n\t\t\treturn;\n\t\t}\n\n\t\tconsole.log(\n\t\t\t`[HighlightCoordinator] Removing annotation ${id} (color: ${annotation.color})`,\n\t\t);\n\n\t\t// Remove range from the shared color highlight\n\t\tconst colorHighlight = this.colorHighlights.get(annotation.color);\n\t\tif (colorHighlight) {\n\t\t\tconst deleted = colorHighlight.delete(annotation.range);\n\t\t\tconsole.log(\n\t\t\t\t`[HighlightCoordinator] Highlight.delete() returned:`,\n\t\t\t\tdeleted,\n\t\t\t);\n\t\t}\n\n\t\t// Clean up\n\t\tthis.annotations.delete(id);\n\t\tconsole.log(\n\t\t\t`[HighlightCoordinator] Annotation ${id} removed from map. Remaining:`,\n\t\t\tthis.annotations.size,\n\t\t);\n\t}\n\n\t/**\n\t * Remove all annotations\n\t */\n\tclearAnnotations(): void {\n\t\t// Clear all color highlights\n\t\tfor (const colorHighlight of this.colorHighlights.values()) {\n\t\t\tcolorHighlight.clear();\n\t\t}\n\n\t\t// Clear annotation data\n\t\tthis.annotations.clear();\n\t}\n\n\t/**\n\t * Get all annotations\n\t */\n\tgetAnnotations(): Annotation[] {\n\t\treturn Array.from(this.annotations.values());\n\t}\n\n\t/**\n\t * Get annotation by ID\n\t */\n\tgetAnnotation(id: string): Annotation | null {\n\t\treturn this.annotations.get(id) ?? null;\n\t}\n\n\t/**\n\t * Change annotation color\n\t *\n\t * @param id Annotation ID\n\t * @param newColor New color\n\t */\n\tchangeAnnotationColor(id: string, newColor: HighlightColor): void {\n\t\tconst annotation = this.annotations.get(id);\n\t\tif (!annotation) return;\n\n\t\tconst oldColor = annotation.color;\n\t\tif (oldColor === newColor) return;\n\n\t\t// Remove from old color highlight\n\t\tconst oldColorHighlight = this.colorHighlights.get(oldColor);\n\t\tif (oldColorHighlight) {\n\t\t\toldColorHighlight.delete(annotation.range);\n\t\t}\n\n\t\t// Update color\n\t\tannotation.color = newColor;\n\n\t\t// Add to new color highlight\n\t\tconst newColorHighlight = this.colorHighlights.get(newColor);\n\t\tif (newColorHighlight) {\n\t\t\tnewColorHighlight.add(annotation.range);\n\t\t}\n\t}\n\n\t/**\n\t * Export annotations as serializable data for persistence.\n\t * Uses RangeSerializer for robust DOM range serialization.\n\t *\n\t * @param root Root element for serialization (typically document.body or content container)\n\t * @returns Array of serialized annotations\n\t */\n\texportAnnotations(\n\t\troot: Element = document.body,\n\t): Array<\n\t\tSerializedRange & { id: string; color: HighlightColor; timestamp: number }\n\t> {\n\t\treturn this.getAnnotations().map((annotation) => ({\n\t\t\t...this.rangeSerializer.serialize(annotation.range, root),\n\t\t\tid: annotation.id,\n\t\t\tcolor: annotation.color,\n\t\t\ttimestamp: annotation.timestamp,\n\t\t}));\n\t}\n\n\t/**\n\t * Import annotations from serialized data.\n\t * Restores annotations after page navigation or refresh.\n\t *\n\t * @param data Array of serialized annotations\n\t * @param root Root element for deserialization (same as used in export)\n\t * @returns Number of successfully restored annotations\n\t */\n\timportAnnotations(\n\t\tdata: Array<\n\t\t\tSerializedRange & {\n\t\t\t\tid?: string;\n\t\t\t\tcolor: HighlightColor;\n\t\t\t\ttimestamp?: number;\n\t\t\t}\n\t\t>,\n\t\troot: Element = document.body,\n\t): number {\n\t\tlet restored = 0;\n\n\t\tfor (const item of data) {\n\t\t\tconst range = this.rangeSerializer.deserialize(item, root);\n\t\t\tif (range) {\n\t\t\t\tthis.addAnnotation(range, item.color);\n\t\t\t\trestored++;\n\t\t\t}\n\t\t}\n\n\t\treturn restored;\n\t}\n\n\t// ============================================================================\n\t// HighlightCoordinatorApi interface implementation\n\t// ============================================================================\n\n\t/**\n\t * Highlight a text range (interface method)\n\t *\n\t * Generic method that adapts to the specific highlight type.\n\t * For more control, use the specific methods like highlightTTSWord().\n\t */\n\thighlightRange(\n\t\trange: Range,\n\t\ttype: HighlightType,\n\t\tcolor: HighlightColor = HighlightColor.YELLOW,\n\t): void {\n\t\tif (!this.supported) return;\n\n\t\tswitch (type) {\n\t\t\tcase HighlightType.TTS_WORD:\n\t\t\tcase HighlightType.TTS_SENTENCE: {\n\t\t\t\t// For TTS, add to appropriate highlight\n\t\t\t\tconst highlight =\n\t\t\t\t\ttype === HighlightType.TTS_WORD\n\t\t\t\t\t\t? this.ttsWordHighlight\n\t\t\t\t\t\t: this.ttsSentenceHighlight;\n\t\t\t\tif (highlight) {\n\t\t\t\t\thighlight.clear();\n\t\t\t\t\thighlight.add(range);\n\t\t\t\t\tif (type === HighlightType.TTS_WORD) {\n\t\t\t\t\t\tfor (const element of this.ttsWordElementHighlights) {\n\t\t\t\t\t\t\telement.removeAttribute(\"data-pie-tts-word-element\");\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.ttsWordElementHighlights.clear();\n\t\t\t\t\t\tthis.highlightTTSWordElementFallbacks([range]);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase HighlightType.ANNOTATION:\n\t\t\t\t// For annotations, use the existing method\n\t\t\t\tthis.addAnnotation(range, color);\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\t/**\n\t * Clear highlights of a specific type (interface method)\n\t */\n\tclearHighlights(type: HighlightType): void {\n\t\tif (!this.supported) return;\n\n\t\tswitch (type) {\n\t\t\tcase HighlightType.TTS_WORD:\n\t\t\t\tthis.clearTTSWord();\n\t\t\t\tbreak;\n\t\t\tcase HighlightType.TTS_SENTENCE:\n\t\t\t\tthis.clearTTSSentence();\n\t\t\t\tbreak;\n\t\t\tcase HighlightType.ANNOTATION:\n\t\t\t\tthis.clearAnnotations();\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\t/**\n\t * Clear all highlights (interface method)\n\t */\n\tclearAll(): void {\n\t\tif (!this.supported) return;\n\t\tthis.clearTTS();\n\t\tthis.clearAnnotations();\n\t}\n\n\t/**\n\t * Check if CSS Highlight API is supported (interface method)\n\t */\n\tisSupported(): boolean {\n\t\treturn this.supported;\n\t}\n\n\t/**\n\t * Update TTS highlight style dynamically\n\t *\n\t * @param color CSS color value (e.g., '#ffeb3b')\n\t * @param opacity Opacity value (0.0 to 1.0)\n\t */\n\tupdateTTSHighlightStyle(color: string, opacity: number): void {\n\t\tif (!this.supported) return;\n\t\tif (typeof document === \"undefined\") return;\n\n\t\tconst styleEl = document.getElementById(\"pie-highlight-styles\");\n\t\tif (!styleEl) return;\n\n\t\t// Convert hex to rgba\n\t\tconst hexToRgb = (hex: string) => {\n\t\t\tconst result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex);\n\t\t\treturn result\n\t\t\t\t? {\n\t\t\t\t\t\tr: parseInt(result[1], 16),\n\t\t\t\t\t\tg: parseInt(result[2], 16),\n\t\t\t\t\t\tb: parseInt(result[3], 16),\n\t\t\t\t\t}\n\t\t\t\t: null;\n\t\t};\n\n\t\tconst rgb = hexToRgb(color);\n\t\tif (!rgb) return;\n\n\t\tthis.explicitTTSColorOverride = {\n\t\t\tcolor: `rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`,\n\t\t\topacity: Math.max(0.2, Math.min(0.95, opacity)),\n\t\t};\n\t\tthis.applyAdaptiveTTSStyle();\n\t}\n\n\t/**\n\t * Cleanup - remove all highlights\n\t */\n\tdestroy(): void {\n\t\tif (!this.supported) return;\n\n\t\tthis.clearTTS();\n\t\tthis.clearAnnotations();\n\n\t\t// Remove style element\n\t\tif (typeof document !== \"undefined\") {\n\t\t\tconst styleEl = document.getElementById(\"pie-highlight-styles\");\n\t\t\tif (styleEl) {\n\t\t\t\tstyleEl.remove();\n\t\t\t}\n\t\t}\n\t\tthis.themeObserver?.disconnect();\n\t\tthis.themeObserver = null;\n\t}\n}\n"]}
|
|
@@ -33,6 +33,15 @@ export declare class ToolCoordinator implements ToolCoordinatorApi {
|
|
|
33
33
|
private tools;
|
|
34
34
|
private layerCounters;
|
|
35
35
|
private listeners;
|
|
36
|
+
/**
|
|
37
|
+
* Activation (on/off) state keyed by tool id, kept independent of the
|
|
38
|
+
* element registration lifecycle. A tool's DOM element can be unregistered
|
|
39
|
+
* and re-registered as the item re-renders (e.g. a model change re-mounts
|
|
40
|
+
* the toolbar overlay); when that happens the on/off state must survive so
|
|
41
|
+
* that only an explicit toggle — the toolbar button — turns a tool off.
|
|
42
|
+
* Cleared on genuine teardown via {@link releaseTool}.
|
|
43
|
+
*/
|
|
44
|
+
private visibilityState;
|
|
36
45
|
constructor(config?: ToolCoordinatorConfig);
|
|
37
46
|
/**
|
|
38
47
|
* Subscribe to tool state changes
|
|
@@ -55,9 +64,24 @@ export declare class ToolCoordinator implements ToolCoordinatorApi {
|
|
|
55
64
|
/**
|
|
56
65
|
* Unregister a tool
|
|
57
66
|
*
|
|
67
|
+
* Detaches the element binding (listeners, registration) but intentionally
|
|
68
|
+
* preserves the tool's activation state in {@link visibilityState}, so a
|
|
69
|
+
* subsequent re-registration of the same id (e.g. after an item re-render)
|
|
70
|
+
* restores whether the tool was on or off. Use {@link releaseTool} to also
|
|
71
|
+
* discard the activation state on genuine teardown.
|
|
72
|
+
*
|
|
58
73
|
* @param id Tool identifier
|
|
59
74
|
*/
|
|
60
75
|
unregisterTool(id: string): void;
|
|
76
|
+
/**
|
|
77
|
+
* Fully release a tool: unregister its element binding AND discard its
|
|
78
|
+
* preserved activation state. Call this on genuine teardown (e.g. leaving
|
|
79
|
+
* the item/section that owns the tool) rather than {@link unregisterTool},
|
|
80
|
+
* which keeps the on/off state alive across element re-registration.
|
|
81
|
+
*
|
|
82
|
+
* @param id Tool identifier
|
|
83
|
+
*/
|
|
84
|
+
releaseTool(id: string): void;
|
|
61
85
|
/**
|
|
62
86
|
* Show a tool
|
|
63
87
|
*
|
|
@@ -30,6 +30,15 @@ export class ToolCoordinator {
|
|
|
30
30
|
tools = new Map();
|
|
31
31
|
layerCounters = new Map();
|
|
32
32
|
listeners = new Set();
|
|
33
|
+
/**
|
|
34
|
+
* Activation (on/off) state keyed by tool id, kept independent of the
|
|
35
|
+
* element registration lifecycle. A tool's DOM element can be unregistered
|
|
36
|
+
* and re-registered as the item re-renders (e.g. a model change re-mounts
|
|
37
|
+
* the toolbar overlay); when that happens the on/off state must survive so
|
|
38
|
+
* that only an explicit toggle — the toolbar button — turns a tool off.
|
|
39
|
+
* Cleared on genuine teardown via {@link releaseTool}.
|
|
40
|
+
*/
|
|
41
|
+
visibilityState = new Map();
|
|
33
42
|
constructor(config = {}) {
|
|
34
43
|
this.config = config;
|
|
35
44
|
// Initialize layer counters
|
|
@@ -69,6 +78,10 @@ export class ToolCoordinator {
|
|
|
69
78
|
log(`Tool ${id} is already registered`);
|
|
70
79
|
return;
|
|
71
80
|
}
|
|
81
|
+
// Restore prior on/off state. A re-registration (e.g. after an item
|
|
82
|
+
// re-render unmounts and re-mounts the tool element) must preserve the
|
|
83
|
+
// tool's activation state so only an explicit toggle can turn it off.
|
|
84
|
+
const isVisible = this.visibilityState.get(id) ?? false;
|
|
72
85
|
// If no element provided, create a placeholder registration
|
|
73
86
|
if (!element) {
|
|
74
87
|
this.tools.set(id, {
|
|
@@ -76,7 +89,7 @@ export class ToolCoordinator {
|
|
|
76
89
|
name,
|
|
77
90
|
element: null,
|
|
78
91
|
layer,
|
|
79
|
-
isVisible
|
|
92
|
+
isVisible,
|
|
80
93
|
baseZIndex: layer,
|
|
81
94
|
});
|
|
82
95
|
log("Tool registered without element:", id);
|
|
@@ -86,6 +99,8 @@ export class ToolCoordinator {
|
|
|
86
99
|
const baseZIndex = layer + this.getNextLayerOffset(layer);
|
|
87
100
|
// Apply z-index
|
|
88
101
|
element.style.zIndex = String(baseZIndex);
|
|
102
|
+
// Reflect restored visibility onto the freshly registered element.
|
|
103
|
+
element.style.display = isVisible ? "" : "none";
|
|
89
104
|
// Create and store event handler to enable proper cleanup
|
|
90
105
|
const mouseDownHandler = () => this.bringToFront(element);
|
|
91
106
|
element.addEventListener("mousedown", mouseDownHandler);
|
|
@@ -95,15 +110,24 @@ export class ToolCoordinator {
|
|
|
95
110
|
name,
|
|
96
111
|
element,
|
|
97
112
|
layer,
|
|
98
|
-
isVisible
|
|
113
|
+
isVisible,
|
|
99
114
|
baseZIndex,
|
|
100
115
|
mouseDownHandler,
|
|
101
116
|
});
|
|
117
|
+
if (isVisible) {
|
|
118
|
+
this.bringToFront(element);
|
|
119
|
+
}
|
|
102
120
|
log("Tool registered with element:", id);
|
|
103
121
|
}
|
|
104
122
|
/**
|
|
105
123
|
* Unregister a tool
|
|
106
124
|
*
|
|
125
|
+
* Detaches the element binding (listeners, registration) but intentionally
|
|
126
|
+
* preserves the tool's activation state in {@link visibilityState}, so a
|
|
127
|
+
* subsequent re-registration of the same id (e.g. after an item re-render)
|
|
128
|
+
* restores whether the tool was on or off. Use {@link releaseTool} to also
|
|
129
|
+
* discard the activation state on genuine teardown.
|
|
130
|
+
*
|
|
107
131
|
* @param id Tool identifier
|
|
108
132
|
*/
|
|
109
133
|
unregisterTool(id) {
|
|
@@ -116,6 +140,18 @@ export class ToolCoordinator {
|
|
|
116
140
|
}
|
|
117
141
|
this.tools.delete(id);
|
|
118
142
|
}
|
|
143
|
+
/**
|
|
144
|
+
* Fully release a tool: unregister its element binding AND discard its
|
|
145
|
+
* preserved activation state. Call this on genuine teardown (e.g. leaving
|
|
146
|
+
* the item/section that owns the tool) rather than {@link unregisterTool},
|
|
147
|
+
* which keeps the on/off state alive across element re-registration.
|
|
148
|
+
*
|
|
149
|
+
* @param id Tool identifier
|
|
150
|
+
*/
|
|
151
|
+
releaseTool(id) {
|
|
152
|
+
this.unregisterTool(id);
|
|
153
|
+
this.visibilityState.delete(id);
|
|
154
|
+
}
|
|
119
155
|
/**
|
|
120
156
|
* Show a tool
|
|
121
157
|
*
|
|
@@ -132,6 +168,7 @@ export class ToolCoordinator {
|
|
|
132
168
|
this.bringToFront(tool.element);
|
|
133
169
|
}
|
|
134
170
|
tool.isVisible = true;
|
|
171
|
+
this.visibilityState.set(id, true);
|
|
135
172
|
this.notifyListeners();
|
|
136
173
|
}
|
|
137
174
|
/**
|
|
@@ -149,6 +186,7 @@ export class ToolCoordinator {
|
|
|
149
186
|
tool.element.style.display = "none";
|
|
150
187
|
}
|
|
151
188
|
tool.isVisible = false;
|
|
189
|
+
this.visibilityState.set(id, false);
|
|
152
190
|
this.notifyListeners();
|
|
153
191
|
}
|
|
154
192
|
/**
|
|
@@ -179,7 +217,12 @@ export class ToolCoordinator {
|
|
|
179
217
|
*/
|
|
180
218
|
isToolVisible(id) {
|
|
181
219
|
const tool = this.tools.get(id);
|
|
182
|
-
|
|
220
|
+
if (tool)
|
|
221
|
+
return tool.isVisible;
|
|
222
|
+
// No live element registration (e.g. mid re-render, between unmount and
|
|
223
|
+
// re-mount): fall back to the preserved activation state so the tool
|
|
224
|
+
// doesn't read as "off" during the gap.
|
|
225
|
+
return this.visibilityState.get(id) ?? false;
|
|
183
226
|
}
|
|
184
227
|
/**
|
|
185
228
|
* Bring element to front of its layer
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ToolCoordinator.js","sourceRoot":"","sources":["../../src/services/ToolCoordinator.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * ToolCoordinator\n *\n * Manages z-index layering and visibility for floating tools.\n * Prevents conflicts between calculator, ruler, protractor, etc.\n *\n * Features:\n * - Centralized z-index management with defined layers\n * - Tool visibility state tracking\n * - Bring-to-front on interaction\n * - Framework-agnostic (works with any DOM element)\n *\n * Part of PIE Assessment Toolkit.\n */\n\nimport { createLogger } from \"../utils/logger.js\";\nimport type { ToolCoordinatorApi, ToolState } from \"./interfaces.js\";\n\nconst log = createLogger(\"ToolCoordinator\");\n\n/**\n * Z-index layers for assessment components\n */\nexport enum ZIndexLayer {\n\tBASE = 0, // PIE content, player chrome (0-999)\n\tTOOL = 1000, // Non-modal tools (ruler, protractor) (1000-1999)\n\tMODAL = 2000, // Modal tools (calculator) (2000-2999)\n\tCONTROL = 3000, // Drag handles, resize controls (3000-3999)\n\tHIGHLIGHT = 4000, // TTS and annotation highlights (4000-4999)\n}\n\n/**\n * Tool registration info\n */\ninterface ToolRegistration {\n\tid: string;\n\tname: string;\n\telement: HTMLElement | null;\n\tlayer: ZIndexLayer;\n\tisVisible: boolean;\n\tbaseZIndex: number;\n\tmouseDownHandler?: (e: MouseEvent) => void;\n}\n\n/**\n * Configuration for ToolCoordinator\n * Currently empty but allows future extension without breaking changes\n */\nexport type ToolCoordinatorConfig = Record<string, never>;\n\nexport class ToolCoordinator implements ToolCoordinatorApi {\n\tprivate config: ToolCoordinatorConfig;\n\tprivate tools = new Map<string, ToolRegistration>();\n\tprivate layerCounters = new Map<ZIndexLayer, number>();\n\tprivate listeners = new Set<() => void>();\n\n\tconstructor(config: ToolCoordinatorConfig = {}) {\n\t\tthis.config = config;\n\t\t// Initialize layer counters\n\t\tthis.layerCounters.set(ZIndexLayer.BASE, 0);\n\t\tthis.layerCounters.set(ZIndexLayer.TOOL, 0);\n\t\tthis.layerCounters.set(ZIndexLayer.MODAL, 0);\n\t\tthis.layerCounters.set(ZIndexLayer.CONTROL, 0);\n\t\tthis.layerCounters.set(ZIndexLayer.HIGHLIGHT, 0);\n\t}\n\n\t/**\n\t * Subscribe to tool state changes\n\t * Returns unsubscribe function\n\t */\n\tsubscribe(listener: () => void): () => void {\n\t\tthis.listeners.add(listener);\n\t\treturn () => {\n\t\t\tthis.listeners.delete(listener);\n\t\t};\n\t}\n\n\t/**\n\t * Notify all listeners of state change\n\t */\n\tprivate notifyListeners(): void {\n\t\tthis.listeners.forEach((listener) => listener());\n\t}\n\n\t/**\n\t * Register a tool with the coordinator\n\t *\n\t * @param id Unique tool identifier\n\t * @param name Display name\n\t * @param element DOM element for the tool (optional)\n\t * @param layer Z-index layer (defaults to MODAL)\n\t */\n\tregisterTool(\n\t\tid: string,\n\t\tname: string,\n\t\telement?: HTMLElement,\n\t\tlayer: ZIndexLayer = ZIndexLayer.MODAL,\n\t): void {\n\t\tlog(\"registerTool called:\", { id, name, hasElement: !!element, layer });\n\n\t\tif (this.tools.has(id)) {\n\t\t\tlog(`Tool ${id} is already registered`);\n\t\t\treturn;\n\t\t}\n\n\t\t// If no element provided, create a placeholder registration\n\t\tif (!element) {\n\t\t\tthis.tools.set(id, {\n\t\t\t\tid,\n\t\t\t\tname,\n\t\t\t\telement: null,\n\t\t\t\tlayer,\n\t\t\t\tisVisible: false,\n\t\t\t\tbaseZIndex: layer,\n\t\t\t});\n\t\t\tlog(\"Tool registered without element:\", id);\n\t\t\treturn;\n\t\t}\n\n\t\t// Calculate base z-index for this layer\n\t\tconst baseZIndex = layer + this.getNextLayerOffset(layer);\n\n\t\t// Apply z-index\n\t\telement.style.zIndex = String(baseZIndex);\n\n\t\t// Create and store event handler to enable proper cleanup\n\t\tconst mouseDownHandler = () => this.bringToFront(element);\n\t\telement.addEventListener(\"mousedown\", mouseDownHandler);\n\n\t\t// Register tool with handler reference\n\t\tthis.tools.set(id, {\n\t\t\tid,\n\t\t\tname,\n\t\t\telement,\n\t\t\tlayer,\n\t\t\tisVisible: false,\n\t\t\tbaseZIndex,\n\t\t\tmouseDownHandler,\n\t\t});\n\n\t\tlog(\"Tool registered with element:\", id);\n\t}\n\n\t/**\n\t * Unregister a tool\n\t *\n\t * @param id Tool identifier\n\t */\n\tunregisterTool(id: string): void {\n\t\tconst tool = this.tools.get(id);\n\t\tif (!tool) return;\n\n\t\t// Remove event listeners using stored handler reference\n\t\tif (tool.element && tool.mouseDownHandler) {\n\t\t\ttool.element.removeEventListener(\"mousedown\", tool.mouseDownHandler);\n\t\t}\n\n\t\tthis.tools.delete(id);\n\t}\n\n\t/**\n\t * Show a tool\n\t *\n\t * @param id Tool identifier\n\t */\n\tshowTool(id: string): void {\n\t\tconst tool = this.tools.get(id);\n\t\tif (!tool) {\n\t\t\tlog(`Tool ${id} not found`);\n\t\t\treturn;\n\t\t}\n\n\t\tif (tool.element) {\n\t\t\ttool.element.style.display = \"\";\n\t\t\tthis.bringToFront(tool.element);\n\t\t}\n\t\ttool.isVisible = true;\n\t\tthis.notifyListeners();\n\t}\n\n\t/**\n\t * Hide a tool\n\t *\n\t * @param id Tool identifier\n\t */\n\thideTool(id: string): void {\n\t\tconst tool = this.tools.get(id);\n\t\tif (!tool) {\n\t\t\tlog(`Tool ${id} not found`);\n\t\t\treturn;\n\t\t}\n\n\t\tif (tool.element) {\n\t\t\ttool.element.style.display = \"none\";\n\t\t}\n\t\ttool.isVisible = false;\n\t\tthis.notifyListeners();\n\t}\n\n\t/**\n\t * Toggle tool visibility\n\t *\n\t * @param id Tool identifier\n\t */\n\ttoggleTool(id: string): void {\n\t\tlog(\"toggleTool called for:\", id);\n\t\tconst tool = this.tools.get(id);\n\t\tif (!tool) {\n\t\t\tlog(\n\t\t\t\t`Tool ${id} not found. Registered tools:`,\n\t\t\t\tArray.from(this.tools.keys()),\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tlog(\"Tool found, current visibility:\", tool.isVisible);\n\t\tif (tool.isVisible) {\n\t\t\tthis.hideTool(id);\n\t\t} else {\n\t\t\tthis.showTool(id);\n\t\t}\n\t}\n\n\t/**\n\t * Check if tool is visible\n\t *\n\t * @param id Tool identifier\n\t * @returns true if tool is visible\n\t */\n\tisToolVisible(id: string): boolean {\n\t\tconst tool = this.tools.get(id);\n\t\treturn tool?.isVisible ?? false;\n\t}\n\n\t/**\n\t * Bring element to front of its layer\n\t *\n\t * @param element DOM element to bring forward\n\t */\n\tbringToFront(element: HTMLElement): void {\n\t\t// Find tool registration\n\t\tconst tool = Array.from(this.tools.values()).find(\n\t\t\t(t) => t.element === element,\n\t\t);\n\t\tif (!tool) return;\n\n\t\t// Calculate new z-index (highest in layer + 1)\n\t\tconst maxZIndexInLayer = this.getMaxZIndexInLayer(tool.layer);\n\t\tconst newZIndex = Math.max(maxZIndexInLayer + 1, tool.layer + 1);\n\n\t\t// Update z-index\n\t\telement.style.zIndex = String(newZIndex);\n\t\ttool.baseZIndex = newZIndex;\n\t}\n\n\t/**\n\t * Get all registered tool IDs\n\t */\n\tgetRegisteredTools(): string[] {\n\t\treturn Array.from(this.tools.keys());\n\t}\n\n\t/**\n\t * Get tool element by ID\n\t */\n\tgetToolElement(id: string): HTMLElement | null {\n\t\treturn this.tools.get(id)?.element ?? null;\n\t}\n\n\t/**\n\t * Get next offset within a layer\n\t */\n\tprivate getNextLayerOffset(layer: ZIndexLayer): number {\n\t\tconst current = this.layerCounters.get(layer) ?? 0;\n\t\tthis.layerCounters.set(layer, current + 1);\n\t\treturn current;\n\t}\n\n\t/**\n\t * Get maximum z-index currently in use in a layer\n\t */\n\tprivate getMaxZIndexInLayer(layer: ZIndexLayer): number {\n\t\tlet max = layer;\n\n\t\tfor (const tool of this.tools.values()) {\n\t\t\tif (tool.layer === layer) {\n\t\t\t\tmax = Math.max(max, tool.baseZIndex);\n\t\t\t}\n\t\t}\n\n\t\treturn max;\n\t}\n\n\t/**\n\t * Reset all tools to their base z-indices\n\t */\n\tresetZIndices(): void {\n\t\tfor (const tool of this.tools.values()) {\n\t\t\tif (tool.element) {\n\t\t\t\ttool.element.style.zIndex = String(tool.baseZIndex);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Update tool element reference\n\t *\n\t * @param id Tool identifier\n\t * @param element New DOM element\n\t */\n\tupdateToolElement(id: string, element: HTMLElement): void {\n\t\tconst tool = this.tools.get(id);\n\t\tif (!tool) {\n\t\t\tlog(`Tool ${id} not found`);\n\t\t\treturn;\n\t\t}\n\n\t\t// Remove old event listener using stored handler reference\n\t\tif (tool.element && tool.mouseDownHandler) {\n\t\t\ttool.element.removeEventListener(\"mousedown\", tool.mouseDownHandler);\n\t\t}\n\n\t\t// Create new handler and add listener\n\t\tconst mouseDownHandler = () => this.bringToFront(element);\n\t\telement.addEventListener(\"mousedown\", mouseDownHandler);\n\n\t\t// Update element and handler reference\n\t\ttool.element = element;\n\t\ttool.mouseDownHandler = mouseDownHandler;\n\n\t\t// Apply z-index to new element\n\t\telement.style.zIndex = String(tool.baseZIndex);\n\t\tif (tool.isVisible) {\n\t\t\telement.style.display = \"\";\n\t\t} else {\n\t\t\telement.style.display = \"none\";\n\t\t}\n\t}\n\n\t/**\n\t * Hide all tools\n\t */\n\thideAllTools(): void {\n\t\tfor (const id of this.tools.keys()) {\n\t\t\tthis.hideTool(id);\n\t\t}\n\t\tthis.notifyListeners();\n\t}\n\n\t/**\n\t * Get tool state (interface method)\n\t *\n\t * @param id Tool identifier\n\t * @returns Tool state or undefined\n\t */\n\tgetToolState(id: string): ToolState | undefined {\n\t\tconst tool = this.tools.get(id);\n\t\tif (!tool) return undefined;\n\n\t\treturn {\n\t\t\tid: tool.id,\n\t\t\tname: tool.name,\n\t\t\tisVisible: tool.isVisible,\n\t\t\telement: tool.element ?? null,\n\t\t\tlayer: tool.layer,\n\t\t};\n\t}\n\n\t/**\n\t * Get all visible tools (interface method)\n\t *\n\t * @returns Array of visible tool states\n\t */\n\tgetVisibleTools(): ToolState[] {\n\t\treturn Array.from(this.tools.values())\n\t\t\t.filter((tool) => tool.isVisible)\n\t\t\t.map((tool) => ({\n\t\t\t\tid: tool.id,\n\t\t\t\tname: tool.name,\n\t\t\t\tisVisible: tool.isVisible,\n\t\t\t\telement: tool.element ?? null,\n\t\t\t\tlayer: tool.layer,\n\t\t\t}));\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"ToolCoordinator.js","sourceRoot":"","sources":["../../src/services/ToolCoordinator.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * ToolCoordinator\n *\n * Manages z-index layering and visibility for floating tools.\n * Prevents conflicts between calculator, ruler, protractor, etc.\n *\n * Features:\n * - Centralized z-index management with defined layers\n * - Tool visibility state tracking\n * - Bring-to-front on interaction\n * - Framework-agnostic (works with any DOM element)\n *\n * Part of PIE Assessment Toolkit.\n */\n\nimport { createLogger } from \"../utils/logger.js\";\nimport type { ToolCoordinatorApi, ToolState } from \"./interfaces.js\";\n\nconst log = createLogger(\"ToolCoordinator\");\n\n/**\n * Z-index layers for assessment components\n */\nexport enum ZIndexLayer {\n\tBASE = 0, // PIE content, player chrome (0-999)\n\tTOOL = 1000, // Non-modal tools (ruler, protractor) (1000-1999)\n\tMODAL = 2000, // Modal tools (calculator) (2000-2999)\n\tCONTROL = 3000, // Drag handles, resize controls (3000-3999)\n\tHIGHLIGHT = 4000, // TTS and annotation highlights (4000-4999)\n}\n\n/**\n * Tool registration info\n */\ninterface ToolRegistration {\n\tid: string;\n\tname: string;\n\telement: HTMLElement | null;\n\tlayer: ZIndexLayer;\n\tisVisible: boolean;\n\tbaseZIndex: number;\n\tmouseDownHandler?: (e: MouseEvent) => void;\n}\n\n/**\n * Configuration for ToolCoordinator\n * Currently empty but allows future extension without breaking changes\n */\nexport type ToolCoordinatorConfig = Record<string, never>;\n\nexport class ToolCoordinator implements ToolCoordinatorApi {\n\tprivate config: ToolCoordinatorConfig;\n\tprivate tools = new Map<string, ToolRegistration>();\n\tprivate layerCounters = new Map<ZIndexLayer, number>();\n\tprivate listeners = new Set<() => void>();\n\t/**\n\t * Activation (on/off) state keyed by tool id, kept independent of the\n\t * element registration lifecycle. A tool's DOM element can be unregistered\n\t * and re-registered as the item re-renders (e.g. a model change re-mounts\n\t * the toolbar overlay); when that happens the on/off state must survive so\n\t * that only an explicit toggle — the toolbar button — turns a tool off.\n\t * Cleared on genuine teardown via {@link releaseTool}.\n\t */\n\tprivate visibilityState = new Map<string, boolean>();\n\n\tconstructor(config: ToolCoordinatorConfig = {}) {\n\t\tthis.config = config;\n\t\t// Initialize layer counters\n\t\tthis.layerCounters.set(ZIndexLayer.BASE, 0);\n\t\tthis.layerCounters.set(ZIndexLayer.TOOL, 0);\n\t\tthis.layerCounters.set(ZIndexLayer.MODAL, 0);\n\t\tthis.layerCounters.set(ZIndexLayer.CONTROL, 0);\n\t\tthis.layerCounters.set(ZIndexLayer.HIGHLIGHT, 0);\n\t}\n\n\t/**\n\t * Subscribe to tool state changes\n\t * Returns unsubscribe function\n\t */\n\tsubscribe(listener: () => void): () => void {\n\t\tthis.listeners.add(listener);\n\t\treturn () => {\n\t\t\tthis.listeners.delete(listener);\n\t\t};\n\t}\n\n\t/**\n\t * Notify all listeners of state change\n\t */\n\tprivate notifyListeners(): void {\n\t\tthis.listeners.forEach((listener) => listener());\n\t}\n\n\t/**\n\t * Register a tool with the coordinator\n\t *\n\t * @param id Unique tool identifier\n\t * @param name Display name\n\t * @param element DOM element for the tool (optional)\n\t * @param layer Z-index layer (defaults to MODAL)\n\t */\n\tregisterTool(\n\t\tid: string,\n\t\tname: string,\n\t\telement?: HTMLElement,\n\t\tlayer: ZIndexLayer = ZIndexLayer.MODAL,\n\t): void {\n\t\tlog(\"registerTool called:\", { id, name, hasElement: !!element, layer });\n\n\t\tif (this.tools.has(id)) {\n\t\t\tlog(`Tool ${id} is already registered`);\n\t\t\treturn;\n\t\t}\n\n\t\t// Restore prior on/off state. A re-registration (e.g. after an item\n\t\t// re-render unmounts and re-mounts the tool element) must preserve the\n\t\t// tool's activation state so only an explicit toggle can turn it off.\n\t\tconst isVisible = this.visibilityState.get(id) ?? false;\n\n\t\t// If no element provided, create a placeholder registration\n\t\tif (!element) {\n\t\t\tthis.tools.set(id, {\n\t\t\t\tid,\n\t\t\t\tname,\n\t\t\t\telement: null,\n\t\t\t\tlayer,\n\t\t\t\tisVisible,\n\t\t\t\tbaseZIndex: layer,\n\t\t\t});\n\t\t\tlog(\"Tool registered without element:\", id);\n\t\t\treturn;\n\t\t}\n\n\t\t// Calculate base z-index for this layer\n\t\tconst baseZIndex = layer + this.getNextLayerOffset(layer);\n\n\t\t// Apply z-index\n\t\telement.style.zIndex = String(baseZIndex);\n\n\t\t// Reflect restored visibility onto the freshly registered element.\n\t\telement.style.display = isVisible ? \"\" : \"none\";\n\n\t\t// Create and store event handler to enable proper cleanup\n\t\tconst mouseDownHandler = () => this.bringToFront(element);\n\t\telement.addEventListener(\"mousedown\", mouseDownHandler);\n\n\t\t// Register tool with handler reference\n\t\tthis.tools.set(id, {\n\t\t\tid,\n\t\t\tname,\n\t\t\telement,\n\t\t\tlayer,\n\t\t\tisVisible,\n\t\t\tbaseZIndex,\n\t\t\tmouseDownHandler,\n\t\t});\n\n\t\tif (isVisible) {\n\t\t\tthis.bringToFront(element);\n\t\t}\n\n\t\tlog(\"Tool registered with element:\", id);\n\t}\n\n\t/**\n\t * Unregister a tool\n\t *\n\t * Detaches the element binding (listeners, registration) but intentionally\n\t * preserves the tool's activation state in {@link visibilityState}, so a\n\t * subsequent re-registration of the same id (e.g. after an item re-render)\n\t * restores whether the tool was on or off. Use {@link releaseTool} to also\n\t * discard the activation state on genuine teardown.\n\t *\n\t * @param id Tool identifier\n\t */\n\tunregisterTool(id: string): void {\n\t\tconst tool = this.tools.get(id);\n\t\tif (!tool) return;\n\n\t\t// Remove event listeners using stored handler reference\n\t\tif (tool.element && tool.mouseDownHandler) {\n\t\t\ttool.element.removeEventListener(\"mousedown\", tool.mouseDownHandler);\n\t\t}\n\n\t\tthis.tools.delete(id);\n\t}\n\n\t/**\n\t * Fully release a tool: unregister its element binding AND discard its\n\t * preserved activation state. Call this on genuine teardown (e.g. leaving\n\t * the item/section that owns the tool) rather than {@link unregisterTool},\n\t * which keeps the on/off state alive across element re-registration.\n\t *\n\t * @param id Tool identifier\n\t */\n\treleaseTool(id: string): void {\n\t\tthis.unregisterTool(id);\n\t\tthis.visibilityState.delete(id);\n\t}\n\n\t/**\n\t * Show a tool\n\t *\n\t * @param id Tool identifier\n\t */\n\tshowTool(id: string): void {\n\t\tconst tool = this.tools.get(id);\n\t\tif (!tool) {\n\t\t\tlog(`Tool ${id} not found`);\n\t\t\treturn;\n\t\t}\n\n\t\tif (tool.element) {\n\t\t\ttool.element.style.display = \"\";\n\t\t\tthis.bringToFront(tool.element);\n\t\t}\n\t\ttool.isVisible = true;\n\t\tthis.visibilityState.set(id, true);\n\t\tthis.notifyListeners();\n\t}\n\n\t/**\n\t * Hide a tool\n\t *\n\t * @param id Tool identifier\n\t */\n\thideTool(id: string): void {\n\t\tconst tool = this.tools.get(id);\n\t\tif (!tool) {\n\t\t\tlog(`Tool ${id} not found`);\n\t\t\treturn;\n\t\t}\n\n\t\tif (tool.element) {\n\t\t\ttool.element.style.display = \"none\";\n\t\t}\n\t\ttool.isVisible = false;\n\t\tthis.visibilityState.set(id, false);\n\t\tthis.notifyListeners();\n\t}\n\n\t/**\n\t * Toggle tool visibility\n\t *\n\t * @param id Tool identifier\n\t */\n\ttoggleTool(id: string): void {\n\t\tlog(\"toggleTool called for:\", id);\n\t\tconst tool = this.tools.get(id);\n\t\tif (!tool) {\n\t\t\tlog(\n\t\t\t\t`Tool ${id} not found. Registered tools:`,\n\t\t\t\tArray.from(this.tools.keys()),\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tlog(\"Tool found, current visibility:\", tool.isVisible);\n\t\tif (tool.isVisible) {\n\t\t\tthis.hideTool(id);\n\t\t} else {\n\t\t\tthis.showTool(id);\n\t\t}\n\t}\n\n\t/**\n\t * Check if tool is visible\n\t *\n\t * @param id Tool identifier\n\t * @returns true if tool is visible\n\t */\n\tisToolVisible(id: string): boolean {\n\t\tconst tool = this.tools.get(id);\n\t\tif (tool) return tool.isVisible;\n\t\t// No live element registration (e.g. mid re-render, between unmount and\n\t\t// re-mount): fall back to the preserved activation state so the tool\n\t\t// doesn't read as \"off\" during the gap.\n\t\treturn this.visibilityState.get(id) ?? false;\n\t}\n\n\t/**\n\t * Bring element to front of its layer\n\t *\n\t * @param element DOM element to bring forward\n\t */\n\tbringToFront(element: HTMLElement): void {\n\t\t// Find tool registration\n\t\tconst tool = Array.from(this.tools.values()).find(\n\t\t\t(t) => t.element === element,\n\t\t);\n\t\tif (!tool) return;\n\n\t\t// Calculate new z-index (highest in layer + 1)\n\t\tconst maxZIndexInLayer = this.getMaxZIndexInLayer(tool.layer);\n\t\tconst newZIndex = Math.max(maxZIndexInLayer + 1, tool.layer + 1);\n\n\t\t// Update z-index\n\t\telement.style.zIndex = String(newZIndex);\n\t\ttool.baseZIndex = newZIndex;\n\t}\n\n\t/**\n\t * Get all registered tool IDs\n\t */\n\tgetRegisteredTools(): string[] {\n\t\treturn Array.from(this.tools.keys());\n\t}\n\n\t/**\n\t * Get tool element by ID\n\t */\n\tgetToolElement(id: string): HTMLElement | null {\n\t\treturn this.tools.get(id)?.element ?? null;\n\t}\n\n\t/**\n\t * Get next offset within a layer\n\t */\n\tprivate getNextLayerOffset(layer: ZIndexLayer): number {\n\t\tconst current = this.layerCounters.get(layer) ?? 0;\n\t\tthis.layerCounters.set(layer, current + 1);\n\t\treturn current;\n\t}\n\n\t/**\n\t * Get maximum z-index currently in use in a layer\n\t */\n\tprivate getMaxZIndexInLayer(layer: ZIndexLayer): number {\n\t\tlet max = layer;\n\n\t\tfor (const tool of this.tools.values()) {\n\t\t\tif (tool.layer === layer) {\n\t\t\t\tmax = Math.max(max, tool.baseZIndex);\n\t\t\t}\n\t\t}\n\n\t\treturn max;\n\t}\n\n\t/**\n\t * Reset all tools to their base z-indices\n\t */\n\tresetZIndices(): void {\n\t\tfor (const tool of this.tools.values()) {\n\t\t\tif (tool.element) {\n\t\t\t\ttool.element.style.zIndex = String(tool.baseZIndex);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Update tool element reference\n\t *\n\t * @param id Tool identifier\n\t * @param element New DOM element\n\t */\n\tupdateToolElement(id: string, element: HTMLElement): void {\n\t\tconst tool = this.tools.get(id);\n\t\tif (!tool) {\n\t\t\tlog(`Tool ${id} not found`);\n\t\t\treturn;\n\t\t}\n\n\t\t// Remove old event listener using stored handler reference\n\t\tif (tool.element && tool.mouseDownHandler) {\n\t\t\ttool.element.removeEventListener(\"mousedown\", tool.mouseDownHandler);\n\t\t}\n\n\t\t// Create new handler and add listener\n\t\tconst mouseDownHandler = () => this.bringToFront(element);\n\t\telement.addEventListener(\"mousedown\", mouseDownHandler);\n\n\t\t// Update element and handler reference\n\t\ttool.element = element;\n\t\ttool.mouseDownHandler = mouseDownHandler;\n\n\t\t// Apply z-index to new element\n\t\telement.style.zIndex = String(tool.baseZIndex);\n\t\tif (tool.isVisible) {\n\t\t\telement.style.display = \"\";\n\t\t} else {\n\t\t\telement.style.display = \"none\";\n\t\t}\n\t}\n\n\t/**\n\t * Hide all tools\n\t */\n\thideAllTools(): void {\n\t\tfor (const id of this.tools.keys()) {\n\t\t\tthis.hideTool(id);\n\t\t}\n\t\tthis.notifyListeners();\n\t}\n\n\t/**\n\t * Get tool state (interface method)\n\t *\n\t * @param id Tool identifier\n\t * @returns Tool state or undefined\n\t */\n\tgetToolState(id: string): ToolState | undefined {\n\t\tconst tool = this.tools.get(id);\n\t\tif (!tool) return undefined;\n\n\t\treturn {\n\t\t\tid: tool.id,\n\t\t\tname: tool.name,\n\t\t\tisVisible: tool.isVisible,\n\t\t\telement: tool.element ?? null,\n\t\t\tlayer: tool.layer,\n\t\t};\n\t}\n\n\t/**\n\t * Get all visible tools (interface method)\n\t *\n\t * @returns Array of visible tool states\n\t */\n\tgetVisibleTools(): ToolState[] {\n\t\treturn Array.from(this.tools.values())\n\t\t\t.filter((tool) => tool.isVisible)\n\t\t\t.map((tool) => ({\n\t\t\t\tid: tool.id,\n\t\t\t\tname: tool.name,\n\t\t\t\tisVisible: tool.isVisible,\n\t\t\t\telement: tool.element ?? null,\n\t\t\t\tlayer: tool.layer,\n\t\t\t}));\n\t}\n}\n"]}
|
|
@@ -125,9 +125,15 @@ export interface ToolCoordinatorApi {
|
|
|
125
125
|
*/
|
|
126
126
|
registerTool(id: string, name: string, element?: HTMLElement, layer?: ZIndexLayer): void;
|
|
127
127
|
/**
|
|
128
|
-
* Unregister a tool
|
|
128
|
+
* Unregister a tool's element binding while preserving its activation
|
|
129
|
+
* (on/off) state, so a re-registration of the same id restores it.
|
|
129
130
|
*/
|
|
130
131
|
unregisterTool(id: string): void;
|
|
132
|
+
/**
|
|
133
|
+
* Fully release a tool: unregister and discard its preserved activation
|
|
134
|
+
* state. Use on genuine teardown rather than `unregisterTool`.
|
|
135
|
+
*/
|
|
136
|
+
releaseTool(id: string): void;
|
|
131
137
|
/**
|
|
132
138
|
* Show a tool
|
|
133
139
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"interfaces.js","sourceRoot":"","sources":["../../src/services/interfaces.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;;AAi2BH,0EAA0E","sourcesContent":["/**\n * Service Interfaces\n *\n * Public interfaces for all core assessment toolkit services.\n * These interfaces define the contracts that service implementations must fulfill,\n * enabling dependency injection, testing, and custom implementations.\n *\n * Part of PIE Assessment Toolkit.\n */\n\nimport type { I18nServiceApi } from \"@pie-players/pie-players-shared/i18n\";\nimport type {\n\tAccessibilityCatalogResolver,\n\tCatalogLookupContext,\n\tCatalogLookupOptions,\n\tCatalogOwnerContext,\n\tCatalogStatistics,\n\tCatalogType,\n\tResolvedCatalog,\n} from \"./AccessibilityCatalogResolver.js\";\nimport type { FrameworkErrorListener } from \"./framework-error-bus.js\";\nimport type { HighlightColor, HighlightType } from \"./HighlightCoordinator.js\";\nimport type {\n\tSectionControllerHandle,\n\tSectionItemEventSubscriptionArgs,\n\tSectionScopedEventSubscriptionArgs,\n\tSectionEventSubscriptionArgs,\n\tToolkitCoordinatorHooks,\n\tToolkitInitStatus,\n} from \"./ToolkitCoordinator.js\";\nimport type { FontSize, ThemeConfig } from \"./ThemeProvider.js\";\nimport type { ZIndexLayer } from \"./ToolCoordinator.js\";\nimport type { PlaybackState, TTSConfig } from \"./TTSService.js\";\nimport type { TTSHighlightTargetResolverProvider } from \"./tts/highlight-target-resolver.js\";\nimport type {\n\tToolPlacementConfig,\n\tToolPlacementLevel,\n\tToolProviderConfig,\n} from \"./tools-config-normalizer.js\";\nimport type { ToolProviderRegistry } from \"./tool-providers/ToolProviderRegistry.js\";\nimport type {\n\tPolicySource,\n\tPnpEnforcementMode,\n\tResolvedEngineInputs,\n\tToolPolicyChangeListener,\n\tToolPolicyDecision,\n\tToolPolicyDecisionRequest,\n} from \"../policy/engine.js\";\nimport type {\n\tAssessmentEntity,\n\tAssessmentItemRef,\n} from \"@pie-players/pie-players-shared/types\";\nimport type {\n\tITTSProvider,\n\tTTSProviderCapabilities,\n} from \"@pie-players/pie-tts\";\nimport type {\n\tResolvedToolContext,\n\tToolContextResolver,\n\tToolContextResolverContext,\n\tToolContextResolverMap,\n} from \"./ToolRegistry.js\";\n\n// Re-export I18nServiceApi from players-shared\nexport type { I18nServiceApi };\n\n/**\n * Theme provider interface\n *\n * Applies consistent accessibility theming across items and tools.\n */\nexport interface ThemeProviderApi {\n\t/**\n\t * Apply theme configuration\n\t */\n\tapplyTheme(config: ThemeConfig): void;\n\n\t/**\n\t * Get current theme configuration\n\t */\n\tgetCurrentTheme(): Required<ThemeConfig>;\n\n\t/**\n\t * Reset to default theme\n\t */\n\treset(): void;\n\n\t/**\n\t * Clean up and remove theme styles\n\t */\n\tdestroy(): void;\n}\n\n/**\n * Highlight coordinator interface\n *\n * Manages content highlighting for TTS, annotations, and selections.\n */\nexport interface HighlightCoordinatorApi {\n\t/**\n\t * Highlight a text range\n\t */\n\thighlightRange(\n\t\trange: Range,\n\t\ttype: HighlightType,\n\t\tcolor: HighlightColor,\n\t): void;\n\n\t/**\n\t * Highlight a word for TTS (temporary)\n\t */\n\thighlightTTSWord(\n\t\ttextNode: Text,\n\t\tstartOffset: number,\n\t\tendOffset: number,\n\t): void;\n\n\t/**\n\t * Highlight a single element as the active TTS word (temporary).\n\t *\n\t * Marks exactly the supplied element (via `data-pie-tts-word-element`)\n\t * without walking up to a containing ancestor. Used for atomic targets that\n\t * have no direct text node to range over — most notably MathJax CHTML tokens\n\t * (e.g. `<mjx-mi><mjx-c/></mjx-mi>`) and whole-expression fallbacks. Painting\n\t * the element itself is what lets a resolved math token highlight as a token\n\t * rather than escalating to the full `<math>` / `<mjx-container>`.\n\t *\n\t * Optional so lightweight coordinator mocks can omit it.\n\t */\n\thighlightTTSWordElement?(element: Element): void;\n\n\t/**\n\t * Highlight sentence(s) for TTS (background layer)\n\t */\n\thighlightTTSSentence(ranges: Range[]): void;\n\n\t/**\n\t * Highlight sentence/block element targets for TTS (background layer).\n\t *\n\t * Optional so lightweight coordinator mocks can omit it.\n\t */\n\thighlightTTSSentenceElements?(elements: Element[]): void;\n\n\t/**\n\t * Clear all TTS highlights (word and sentence)\n\t */\n\tclearTTS(): void;\n\n\t/**\n\t * Clear highlights of a specific type\n\t */\n\tclearHighlights(type: HighlightType): void;\n\n\t/**\n\t * Clear all highlights\n\t */\n\tclearAll(): void;\n\n\t/**\n\t * Check if highlighting is supported in current environment\n\t */\n\tisSupported(): boolean;\n\n\t/**\n\t * Update TTS highlight style dynamically\n\t */\n\tupdateTTSHighlightStyle(color: string, opacity: number): void;\n}\n\n/**\n * Tool state interface\n */\nexport interface ToolState {\n\tid: string;\n\tname: string;\n\tisVisible: boolean;\n\telement: HTMLElement | null;\n\tlayer: ZIndexLayer;\n}\n\n/**\n * Tool coordinator interface\n *\n * Manages z-index layering and visibility for floating tools.\n */\nexport interface ToolCoordinatorApi {\n\t/**\n\t * Register a tool with the coordinator\n\t */\n\tregisterTool(\n\t\tid: string,\n\t\tname: string,\n\t\telement?: HTMLElement,\n\t\tlayer?: ZIndexLayer,\n\t): void;\n\n\t/**\n\t * Unregister a tool\n\t */\n\tunregisterTool(id: string): void;\n\n\t/**\n\t * Show a tool\n\t */\n\tshowTool(id: string): void;\n\n\t/**\n\t * Hide a tool\n\t */\n\thideTool(id: string): void;\n\n\t/**\n\t * Toggle tool visibility\n\t */\n\ttoggleTool(id: string): void;\n\n\t/**\n\t * Check if tool is visible\n\t */\n\tisToolVisible(id: string): boolean;\n\n\t/**\n\t * Bring an element to the front of its layer\n\t */\n\tbringToFront(element: HTMLElement): void;\n\n\t/**\n\t * Update the element reference for a tool\n\t */\n\tupdateToolElement(id: string, element: HTMLElement): void;\n\n\t/**\n\t * Get state for a specific tool\n\t */\n\tgetToolState(id: string): ToolState | undefined;\n\n\t/**\n\t * Get all visible tools\n\t */\n\tgetVisibleTools(): ToolState[];\n\n\t/**\n\t * Subscribe to tool state changes\n\t */\n\tsubscribe(listener: () => void): () => void;\n}\n\n/**\n * TTS service interface\n *\n * Provides text-to-speech functionality with provider-based architecture.\n * Supports QTI 3.0 accessibility catalogs for pre-authored spoken content.\n */\nexport interface TtsServiceApi {\n\t/**\n\t * Initialize TTS with a provider\n\t */\n\tinitialize(\n\t\tprovider: ITTSProvider,\n\t\tconfig?: Partial<TTSConfig>,\n\t): Promise<void>;\n\n\t/**\n\t * Speak text with optional catalog support\n\t */\n\tspeak(\n\t\ttext: string,\n\t\toptions?: {\n\t\t\tcatalogId?: string;\n\t\t\tcatalogContext?: CatalogLookupContext;\n\t\t\tlanguage?: string;\n\t\t\tcontentElement?: Element;\n\t\t},\n\t): Promise<void>;\n\n\t/**\n\t * Speak a text range\n\t */\n\tspeakRange(\n\t\trange: Range,\n\t\toptions?: { contentRoot?: Element | null },\n\t): Promise<void>;\n\n\t/**\n\t * Pause playback\n\t */\n\tpause(): void;\n\n\t/**\n\t * Resume playback\n\t */\n\tresume(): void;\n\n\t/**\n\t * Stop playback\n\t */\n\tstop(): void;\n\n\t/**\n\t * Request active TTS controls to hand off/deactivate their UI state.\n\t *\n\t * This is an orchestration hint for TTS tool chrome and does not replace\n\t * playback controls such as stop/pause/resume.\n\t */\n\trequestControlHandoff(): void;\n\n\t/**\n\t * Seek forward by sentence units\n\t */\n\tseekForward(units?: number): Promise<void>;\n\n\t/**\n\t * Seek backward by sentence units\n\t */\n\tseekBackward(units?: number): Promise<void>;\n\n\t/**\n\t * Check if currently playing\n\t */\n\tisPlaying(): boolean;\n\n\t/**\n\t * Check if paused\n\t */\n\tisPaused(): boolean;\n\n\t/**\n\t * Get current playback state\n\t */\n\tgetState(): PlaybackState;\n\n\t/**\n\t * Get currently speaking text\n\t */\n\tgetCurrentText(): string | null;\n\n\t/**\n\t * Subscribe to state changes\n\t */\n\tonStateChange(id: string, callback: (state: PlaybackState) => void): void;\n\n\t/**\n\t * Unsubscribe from state changes\n\t */\n\toffStateChange(id: string, callback: (state: PlaybackState) => void): void;\n\n\t/**\n\t * Get capabilities of current provider\n\t */\n\tgetCapabilities(): TTSProviderCapabilities | null;\n\n\t/**\n\t * Update TTS settings dynamically (rate, pitch, voice)\n\t */\n\tupdateSettings(settings: Partial<TTSConfig>): Promise<void>;\n\n\t/**\n\t * Update playback speed and apply it to active planned playback.\n\t */\n\tsetPlaybackRate(rate: number): Promise<void>;\n\n\t/**\n\t * Set highlight coordinator for word highlighting\n\t */\n\tsetHighlightCoordinator(coordinator: HighlightCoordinatorApi): void;\n\n\t/**\n\t * Set a late-bound provider for optional host TTS highlight target remapping.\n\t */\n\tsetHighlightTargetResolverProvider?(\n\t\tprovider: TTSHighlightTargetResolverProvider | null,\n\t): () => void;\n\n\t/**\n\t * Set accessibility catalog resolver for spoken content\n\t */\n\tsetCatalogResolver(resolver: AccessibilityCatalogResolver): void;\n}\n\n/**\n * Accessibility catalog resolver interface\n *\n * Manages QTI 3.0 accessibility catalogs at assessment and item levels.\n * Provides lookup and resolution services for alternative content representations.\n */\nexport interface AccessibilityCatalogResolverApi {\n\t/**\n\t * Set the default language for fallback resolution\n\t */\n\tsetDefaultLanguage(language: string): void;\n\n\t/**\n\t * Get the default language\n\t */\n\tgetDefaultLanguage(): string;\n\n\t/**\n\t * Add item-level catalogs (called when rendering a new item)\n\t */\n\taddItemCatalogs(catalogs: any[]): void;\n\n\t/**\n\t * Register catalogs scoped to a mounted content owner.\n\t */\n\tregisterCatalogs?(context: CatalogOwnerContext, catalogs: any[]): () => void;\n\n\t/**\n\t * Clear item-level catalogs (called when leaving an item)\n\t */\n\tclearItemCatalogs(): void;\n\n\t/**\n\t * Check if a catalog exists\n\t */\n\thasCatalog(catalogId: string): boolean;\n\n\t/**\n\t * Get alternative content for a catalog identifier\n\t */\n\tgetAlternative(\n\t\tcatalogId: string,\n\t\toptions: CatalogLookupOptions,\n\t): ResolvedCatalog | null;\n\n\t/**\n\t * Get all available alternatives for a catalog identifier\n\t */\n\tgetAllAlternatives(catalogId: string): ResolvedCatalog[];\n\n\t/**\n\t * Get all catalog identifiers available\n\t */\n\tgetAllCatalogIds(): string[];\n\n\t/**\n\t * Get statistics about available catalogs\n\t */\n\tgetStatistics(): CatalogStatistics;\n\n\t/**\n\t * Check if a specific catalog type is available\n\t */\n\thasAlternativeType(catalogId: string, type: CatalogType): boolean;\n\n\t/**\n\t * Get all catalog IDs that have a specific type of alternative\n\t */\n\tgetCatalogsByType(type: CatalogType): string[];\n\n\t/**\n\t * Reset all catalogs\n\t */\n\treset(): void;\n\n\t/**\n\t * Destroy and cleanup\n\t */\n\tdestroy(): void;\n}\n\n/**\n * Element tool state store interface\n *\n * Manages element-level ephemeral tool state using composite keys for global uniqueness.\n * Tool state is client-only and separate from PIE session data (which is sent to server for scoring).\n */\nexport interface ElementToolStateStoreApi {\n\t/**\n\t * Generate a globally unique element ID from components\n\t */\n\tgetGlobalElementId(\n\t\tassessmentId: string,\n\t\tsectionId: string,\n\t\titemId: string,\n\t\telementId: string,\n\t): string;\n\n\t/**\n\t * Parse a global element ID into its components\n\t */\n\tparseGlobalElementId(globalElementId: string): {\n\t\tassessmentId: string;\n\t\tsectionId: string;\n\t\titemId: string;\n\t\telementId: string;\n\t} | null;\n\n\t/**\n\t * Set state for a specific tool on an element\n\t */\n\tsetState(globalElementId: string, toolId: string, state: any): void;\n\n\t/**\n\t * Get state for a specific tool on an element\n\t */\n\tgetState(globalElementId: string, toolId: string): any | undefined;\n\n\t/**\n\t * Get all tool states for a specific element\n\t */\n\tgetElementState(globalElementId: string): Record<string, any>;\n\n\t/**\n\t * Get all element states\n\t */\n\tgetAllState(): Record<string, Record<string, any>>;\n\n\t/**\n\t * Subscribe to state changes\n\t */\n\tsubscribe(\n\t\tcallback: (state: Map<string, Map<string, any>>) => void,\n\t): () => void;\n\n\t/**\n\t * Set callback for persistence integration\n\t */\n\tsetOnStateChange(\n\t\tcallback: (state: Record<string, Record<string, any>>) => void,\n\t): void;\n\n\t/**\n\t * Load state from persistence\n\t */\n\tloadState(state: Record<string, Record<string, any>>): void;\n\n\t/**\n\t * Clear state for a specific element\n\t */\n\tclearElement(globalElementId: string): void;\n\n\t/**\n\t * Clear state for a specific tool across all elements\n\t */\n\tclearTool(toolId: string): void;\n\n\t/**\n\t * Clear all elements in a specific section\n\t */\n\tclearSection(assessmentId: string, sectionId: string): void;\n\n\t/**\n\t * Clear all state\n\t */\n\tclearAll(): void;\n}\n\n/**\n * Toolkit coordinator interface\n *\n * Orchestrates all toolkit services (TTS, tools, accessibility, state management) from a single entry point.\n * Provides centralized configuration for tool availability and settings.\n */\nexport interface ToolkitCoordinatorApi {\n\t/**\n\t * Assessment identifier\n\t */\n\treadonly assessmentId: string;\n\n\t/**\n\t * Configuration\n\t */\n\treadonly config: {\n\t\ttools?: {\n\t\t\tproviders?: Record<string, ToolProviderConfig | undefined>;\n\t\t};\n\t};\n\n\t/**\n\t * TTS service\n\t */\n\treadonly ttsService: TtsServiceApi;\n\n\t/**\n\t * Tool coordinator\n\t */\n\treadonly toolCoordinator: ToolCoordinatorApi;\n\n\t/**\n\t * Highlight coordinator\n\t */\n\treadonly highlightCoordinator: HighlightCoordinatorApi;\n\n\t/**\n\t * Element tool state store\n\t */\n\treadonly elementToolStateStore: ElementToolStateStoreApi;\n\n\t/**\n\t * Catalog resolver\n\t */\n\treadonly catalogResolver: AccessibilityCatalogResolverApi;\n\n\t/**\n\t * Tool provider registry\n\t */\n\treadonly toolProviderRegistry: ToolProviderRegistry;\n\n\t/**\n\t * Get all services as a bundle\n\t */\n\tgetServiceBundle(): {\n\t\tttsService: TtsServiceApi;\n\t\ttoolCoordinator: ToolCoordinatorApi;\n\t\thighlightCoordinator: HighlightCoordinatorApi;\n\t\telementToolStateStore: ElementToolStateStoreApi;\n\t\tcatalogResolver: AccessibilityCatalogResolverApi;\n\t\ttoolProviderRegistry: ToolProviderRegistry;\n\t};\n\n\t/**\n\t * Ensure TTS service is initialized and ready.\n\t */\n\tensureTTSReady(config?: Record<string, unknown>): Promise<void>;\n\n\t/**\n\t * Ensure a provider is initialized and ready.\n\t */\n\tensureProviderReady(providerId: string): Promise<unknown>;\n\n\t/**\n\t * Wait until coordinator initialization is complete.\n\t */\n\twaitUntilReady(): Promise<void>;\n\n\t/**\n\t * Check if coordinator has completed initialization.\n\t */\n\tisReady(): boolean;\n\n\t/**\n\t * Read current initialization status.\n\t */\n\tgetInitStatus(): ToolkitInitStatus;\n\n\t/**\n\t * Check if a tool is enabled\n\t */\n\tisToolEnabled(toolId: string): boolean;\n\n\t/**\n\t * Get tool configuration\n\t */\n\tgetToolConfig(toolId: string): ToolProviderConfig | null;\n\n\t/**\n\t * Update tool configuration\n\t */\n\tupdateToolConfig(toolId: string, updates: Partial<ToolProviderConfig>): void;\n\n\t/**\n\t * Update the enabled tool list for one placement level.\n\t */\n\tupdateToolPlacement(level: ToolPlacementLevel, toolIds: string[]): void;\n\n\t/**\n\t * Patch one or more placement levels in the canonical tools config.\n\t */\n\tupdateToolsPlacement(partial: ToolPlacementConfig): void;\n\n\t/**\n\t * Register or update lifecycle hooks at runtime.\n\t */\n\tsetHooks(hooks: ToolkitCoordinatorHooks): void;\n\n\t/**\n\t * Return a section controller if already created.\n\t */\n\tgetSectionController(args: {\n\t\tsectionId: string;\n\t\tattemptId?: string;\n\t}): SectionControllerHandle | undefined;\n\n\t/**\n\t * Subscribe to section controller events.\n\t *\n\t * The listener is bound to the toolkit's *active section cohort* and\n\t * automatically migrates across cohort transitions\n\t * (`getOrCreateSectionController` for a different cohort). On every\n\t * migration the listener receives a snapshot replay of the new\n\t * cohort's already-loaded `content-loaded` events followed by the\n\t * aggregate `section-loading-complete`, in the canonical order a\n\t * fresh subscriber would have observed.\n\t *\n\t * Throws if no active section cohort exists; host code must call\n\t * `getOrCreateSectionController(...)` at least once before\n\t * subscribing. (`toolkit-ready` alone is not sufficient — it fires\n\t * once toolkit state has loaded but before any section controller\n\t * has been created.) The typical pattern is to subscribe once\n\t * immediately after the first `getOrCreateSectionController(...)`\n\t * resolves; the subscription then follows the active cohort across\n\t * all subsequent navigation without further wiring.\n\t *\n\t * Subscribing the same `listener` reference twice replaces the prior\n\t * subscription with the new one (filter args from the second call\n\t * win); calling the returned disposer twice is a no-op.\n\t *\n\t * A listener that throws is caught and `console.warn`-logged; the\n\t * throw does not interrupt fan-out to the remaining listeners.\n\t */\n\tsubscribeSectionEvents(args: SectionEventSubscriptionArgs): () => void;\n\n\t/**\n\t * Subscribe to item-scoped section controller events.\n\t *\n\t * Same active-cohort binding contract as {@link subscribeSectionEvents};\n\t * defaults `eventTypes` to the item-scoped subset.\n\t */\n\tsubscribeItemEvents(args: SectionItemEventSubscriptionArgs): () => void;\n\n\t/**\n\t * Subscribe to section-scoped lifecycle/loading/completion/error events.\n\t *\n\t * Same active-cohort binding contract as {@link subscribeSectionEvents};\n\t * defaults `eventTypes` to the section-scoped subset.\n\t */\n\tsubscribeSectionLifecycleEvents(\n\t\targs: SectionScopedEventSubscriptionArgs,\n\t): () => void;\n\n\t/**\n\t * Create or reuse a section controller with single-flight deduplication.\n\t */\n\tgetOrCreateSectionController(args: {\n\t\tsectionId: string;\n\t\tattemptId?: string;\n\t\tinput?: unknown;\n\t\tupdateExisting?: boolean;\n\t\tcreateDefaultController: () =>\n\t\t\t| SectionControllerHandle\n\t\t\t| Promise<SectionControllerHandle>;\n\t}): Promise<SectionControllerHandle>;\n\n\t/**\n\t * Dispose an existing section controller.\n\t */\n\tdisposeSectionController(args: {\n\t\tsectionId: string;\n\t\tattemptId?: string;\n\t\tpersistBeforeDispose?: boolean;\n\t\tclearPersistence?: boolean;\n\t}): Promise<void>;\n\n\t/**\n\t * Subscribe to framework-error events emitted by the coordinator.\n\t *\n\t * Mirrors the shape of {@link subscribeTelemetry} (see\n\t * `ToolkitCoordinator.subscribeTelemetry`): a synchronous,\n\t * multi-subscriber stream where each call to the internal bus's\n\t * `reportFrameworkError` fans out to every active listener exactly once.\n\t *\n\t * The returned function detaches the listener; calling it twice is a\n\t * no-op. A listener that throws is caught and logged; the throw does\n\t * not break fan-out to the remaining listeners.\n\t *\n\t * Use this to wire framework errors into Sentry / Datadog / a custom\n\t * banner without listening on a DOM event. The DOM event\n\t * (`framework-error`) and the canonical `onFrameworkError` prop on\n\t * `<pie-assessment-toolkit>` consume the same bus.\n\t */\n\tsubscribeFrameworkErrors(listener: FrameworkErrorListener): () => void;\n\n\t// ----------------------------------------------------------------\n\t// Tool Policy Engine — public surface (M8 PR 2 / PR 3).\n\t//\n\t// The coordinator owns a single `ToolPolicyEngine` instance and\n\t// exposes its decision and subscription surface through the API\n\t// so that toolbar custom elements (`pie-item-toolbar`,\n\t// `pie-section-toolbar`), the base section player, and bespoke\n\t// host instrumentation (PNP debugger, etc.) all flow through the\n\t// same engine. Hosts that want to drive PNP/profile inputs imperatively\n\t// (instead of binding props on `<pie-assessment-toolkit>`) call\n\t// `updateAssessment` / `updateCurrentItemRef` /\n\t// `setPnpEnforcement` directly.\n\t// ----------------------------------------------------------------\n\n\t/**\n\t * Resolve the visible tool set for a given placement level + scope.\n\t * Returns the engine's full decision (visible tools, diagnostics,\n\t * provenance). Hosts that only need the IDs may map\n\t * `decision.visibleTools` themselves.\n\t */\n\tdecideToolPolicy(request: ToolPolicyDecisionRequest): ToolPolicyDecision;\n\n\t/**\n\t * Subscribe to policy-engine change events. Fires whenever the\n\t * coordinator's bound policy inputs change (`updateToolConfig`,\n\t * `updateToolPlacement`, `updateAssessment`, `updateCurrentItemRef`,\n\t * `setPnpEnforcement`) or a custom `PolicySource` is registered /\n\t * removed. Listeners that need the new visible tool set should\n\t * call `decideToolPolicy(...)` with their level / scope.\n\t */\n\tonPolicyChange(listener: ToolPolicyChangeListener): () => void;\n\n\t/**\n\t * Bind (or clear) the active assessment for PNP/profile policy decisions.\n\t *\n\t * Under auto-mode (no host override via {@link setPnpEnforcement}),\n\t * the engine flips to `pnpEnforcement: \"on\"` iff the assessment\n\t * carries profile precedence material (`personalNeedsProfile`,\n\t * `settings.districtPolicy`, `settings.testAdministration`) or the\n\t * currently-bound item ref carries item-level profile inputs. A bare\n\t * assessment record (just `id` / `name`) keeps `\"off\"`.\n\t *\n\t * The host override set via {@link setPnpEnforcement} is sticky\n\t * across assessment swaps.\n\t */\n\tupdateAssessment(assessment: AssessmentEntity | null): void;\n\n\t/**\n\t * Bind (or clear) the current item reference for policy decisions.\n\t * Used by item-level profile gates (item `requiredTools` /\n\t * `restrictedTools` / `toolParameters`). Item-level profile material\n\t * also feeds the auto-mode helper — navigating to an item with\n\t * profile settings can flip auto-mode to `\"on\"` even when the parent\n\t * assessment carries no profile block of its own.\n\t */\n\tupdateCurrentItemRef(itemRef: AssessmentItemRef | null): void;\n\n\t/**\n\t * Override the auto-mode PNP/profile enforcement decision. Pass `\"on\"` /\n\t * `\"off\"` to pin the mode, or `null` to clear the override and\n\t * return to auto-mode (`\"on\"` iff the bound assessment / item ref\n\t * carries profile material, otherwise `\"off\"`).\n\t */\n\tsetPnpEnforcement(mode: PnpEnforcementMode | null): void;\n\n\t/**\n\t * Read the engine inputs currently driving decisions. Useful for\n\t * debugging / instrumentation; do not mutate.\n\t */\n\tgetPolicyInputs(): Readonly<ResolvedEngineInputs>;\n\n\t/**\n\t * Register a custom `PolicySource`. The source participates in\n\t * every subsequent `decideToolPolicy(...)` call until disposed\n\t * (the returned function detaches).\n\t */\n\tregisterPolicySource(source: PolicySource): () => void;\n\n\t/**\n\t * Register a host-owned resolver for scoped tool render context.\n\t */\n\tregisterToolContextResolver(\n\t\ttoolId: string,\n\t\tresolver: ToolContextResolver,\n\t): () => void;\n\n\t/**\n\t * Replace all host-owned render-context resolvers.\n\t */\n\tsetToolContextResolvers(\n\t\tresolvers: ToolContextResolverMap | null | undefined,\n\t): void;\n\n\t/**\n\t * Whether a host resolver is registered for this tool.\n\t */\n\thasToolContextResolver(toolId: string): boolean;\n\n\t/**\n\t * Resolve render visibility/params for a tool that already survived policy gates.\n\t */\n\tresolveToolContext(\n\t\tcontext: ToolContextResolverContext,\n\t): ResolvedToolContext | null;\n\n\t/**\n\t * Subscribe to resolver registration/removal changes.\n\t */\n\tonToolContextResolverChange(listener: () => void): () => void;\n}\n\n// I18nServiceApi is re-exported from @pie-players/pie-players-shared/i18n\n"]}
|
|
1
|
+
{"version":3,"file":"interfaces.js","sourceRoot":"","sources":["../../src/services/interfaces.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;;AAw2BH,0EAA0E","sourcesContent":["/**\n * Service Interfaces\n *\n * Public interfaces for all core assessment toolkit services.\n * These interfaces define the contracts that service implementations must fulfill,\n * enabling dependency injection, testing, and custom implementations.\n *\n * Part of PIE Assessment Toolkit.\n */\n\nimport type { I18nServiceApi } from \"@pie-players/pie-players-shared/i18n\";\nimport type {\n\tAccessibilityCatalogResolver,\n\tCatalogLookupContext,\n\tCatalogLookupOptions,\n\tCatalogOwnerContext,\n\tCatalogStatistics,\n\tCatalogType,\n\tResolvedCatalog,\n} from \"./AccessibilityCatalogResolver.js\";\nimport type { FrameworkErrorListener } from \"./framework-error-bus.js\";\nimport type { HighlightColor, HighlightType } from \"./HighlightCoordinator.js\";\nimport type {\n\tSectionControllerHandle,\n\tSectionItemEventSubscriptionArgs,\n\tSectionScopedEventSubscriptionArgs,\n\tSectionEventSubscriptionArgs,\n\tToolkitCoordinatorHooks,\n\tToolkitInitStatus,\n} from \"./ToolkitCoordinator.js\";\nimport type { FontSize, ThemeConfig } from \"./ThemeProvider.js\";\nimport type { ZIndexLayer } from \"./ToolCoordinator.js\";\nimport type { PlaybackState, TTSConfig } from \"./TTSService.js\";\nimport type { TTSHighlightTargetResolverProvider } from \"./tts/highlight-target-resolver.js\";\nimport type {\n\tToolPlacementConfig,\n\tToolPlacementLevel,\n\tToolProviderConfig,\n} from \"./tools-config-normalizer.js\";\nimport type { ToolProviderRegistry } from \"./tool-providers/ToolProviderRegistry.js\";\nimport type {\n\tPolicySource,\n\tPnpEnforcementMode,\n\tResolvedEngineInputs,\n\tToolPolicyChangeListener,\n\tToolPolicyDecision,\n\tToolPolicyDecisionRequest,\n} from \"../policy/engine.js\";\nimport type {\n\tAssessmentEntity,\n\tAssessmentItemRef,\n} from \"@pie-players/pie-players-shared/types\";\nimport type {\n\tITTSProvider,\n\tTTSProviderCapabilities,\n} from \"@pie-players/pie-tts\";\nimport type {\n\tResolvedToolContext,\n\tToolContextResolver,\n\tToolContextResolverContext,\n\tToolContextResolverMap,\n} from \"./ToolRegistry.js\";\n\n// Re-export I18nServiceApi from players-shared\nexport type { I18nServiceApi };\n\n/**\n * Theme provider interface\n *\n * Applies consistent accessibility theming across items and tools.\n */\nexport interface ThemeProviderApi {\n\t/**\n\t * Apply theme configuration\n\t */\n\tapplyTheme(config: ThemeConfig): void;\n\n\t/**\n\t * Get current theme configuration\n\t */\n\tgetCurrentTheme(): Required<ThemeConfig>;\n\n\t/**\n\t * Reset to default theme\n\t */\n\treset(): void;\n\n\t/**\n\t * Clean up and remove theme styles\n\t */\n\tdestroy(): void;\n}\n\n/**\n * Highlight coordinator interface\n *\n * Manages content highlighting for TTS, annotations, and selections.\n */\nexport interface HighlightCoordinatorApi {\n\t/**\n\t * Highlight a text range\n\t */\n\thighlightRange(\n\t\trange: Range,\n\t\ttype: HighlightType,\n\t\tcolor: HighlightColor,\n\t): void;\n\n\t/**\n\t * Highlight a word for TTS (temporary)\n\t */\n\thighlightTTSWord(\n\t\ttextNode: Text,\n\t\tstartOffset: number,\n\t\tendOffset: number,\n\t): void;\n\n\t/**\n\t * Highlight a single element as the active TTS word (temporary).\n\t *\n\t * Marks exactly the supplied element (via `data-pie-tts-word-element`)\n\t * without walking up to a containing ancestor. Used for atomic targets that\n\t * have no direct text node to range over — most notably MathJax CHTML tokens\n\t * (e.g. `<mjx-mi><mjx-c/></mjx-mi>`) and whole-expression fallbacks. Painting\n\t * the element itself is what lets a resolved math token highlight as a token\n\t * rather than escalating to the full `<math>` / `<mjx-container>`.\n\t *\n\t * Optional so lightweight coordinator mocks can omit it.\n\t */\n\thighlightTTSWordElement?(element: Element): void;\n\n\t/**\n\t * Highlight sentence(s) for TTS (background layer)\n\t */\n\thighlightTTSSentence(ranges: Range[]): void;\n\n\t/**\n\t * Highlight sentence/block element targets for TTS (background layer).\n\t *\n\t * Optional so lightweight coordinator mocks can omit it.\n\t */\n\thighlightTTSSentenceElements?(elements: Element[]): void;\n\n\t/**\n\t * Clear all TTS highlights (word and sentence)\n\t */\n\tclearTTS(): void;\n\n\t/**\n\t * Clear highlights of a specific type\n\t */\n\tclearHighlights(type: HighlightType): void;\n\n\t/**\n\t * Clear all highlights\n\t */\n\tclearAll(): void;\n\n\t/**\n\t * Check if highlighting is supported in current environment\n\t */\n\tisSupported(): boolean;\n\n\t/**\n\t * Update TTS highlight style dynamically\n\t */\n\tupdateTTSHighlightStyle(color: string, opacity: number): void;\n}\n\n/**\n * Tool state interface\n */\nexport interface ToolState {\n\tid: string;\n\tname: string;\n\tisVisible: boolean;\n\telement: HTMLElement | null;\n\tlayer: ZIndexLayer;\n}\n\n/**\n * Tool coordinator interface\n *\n * Manages z-index layering and visibility for floating tools.\n */\nexport interface ToolCoordinatorApi {\n\t/**\n\t * Register a tool with the coordinator\n\t */\n\tregisterTool(\n\t\tid: string,\n\t\tname: string,\n\t\telement?: HTMLElement,\n\t\tlayer?: ZIndexLayer,\n\t): void;\n\n\t/**\n\t * Unregister a tool's element binding while preserving its activation\n\t * (on/off) state, so a re-registration of the same id restores it.\n\t */\n\tunregisterTool(id: string): void;\n\n\t/**\n\t * Fully release a tool: unregister and discard its preserved activation\n\t * state. Use on genuine teardown rather than `unregisterTool`.\n\t */\n\treleaseTool(id: string): void;\n\n\t/**\n\t * Show a tool\n\t */\n\tshowTool(id: string): void;\n\n\t/**\n\t * Hide a tool\n\t */\n\thideTool(id: string): void;\n\n\t/**\n\t * Toggle tool visibility\n\t */\n\ttoggleTool(id: string): void;\n\n\t/**\n\t * Check if tool is visible\n\t */\n\tisToolVisible(id: string): boolean;\n\n\t/**\n\t * Bring an element to the front of its layer\n\t */\n\tbringToFront(element: HTMLElement): void;\n\n\t/**\n\t * Update the element reference for a tool\n\t */\n\tupdateToolElement(id: string, element: HTMLElement): void;\n\n\t/**\n\t * Get state for a specific tool\n\t */\n\tgetToolState(id: string): ToolState | undefined;\n\n\t/**\n\t * Get all visible tools\n\t */\n\tgetVisibleTools(): ToolState[];\n\n\t/**\n\t * Subscribe to tool state changes\n\t */\n\tsubscribe(listener: () => void): () => void;\n}\n\n/**\n * TTS service interface\n *\n * Provides text-to-speech functionality with provider-based architecture.\n * Supports QTI 3.0 accessibility catalogs for pre-authored spoken content.\n */\nexport interface TtsServiceApi {\n\t/**\n\t * Initialize TTS with a provider\n\t */\n\tinitialize(\n\t\tprovider: ITTSProvider,\n\t\tconfig?: Partial<TTSConfig>,\n\t): Promise<void>;\n\n\t/**\n\t * Speak text with optional catalog support\n\t */\n\tspeak(\n\t\ttext: string,\n\t\toptions?: {\n\t\t\tcatalogId?: string;\n\t\t\tcatalogContext?: CatalogLookupContext;\n\t\t\tlanguage?: string;\n\t\t\tcontentElement?: Element;\n\t\t},\n\t): Promise<void>;\n\n\t/**\n\t * Speak a text range\n\t */\n\tspeakRange(\n\t\trange: Range,\n\t\toptions?: { contentRoot?: Element | null },\n\t): Promise<void>;\n\n\t/**\n\t * Pause playback\n\t */\n\tpause(): void;\n\n\t/**\n\t * Resume playback\n\t */\n\tresume(): void;\n\n\t/**\n\t * Stop playback\n\t */\n\tstop(): void;\n\n\t/**\n\t * Request active TTS controls to hand off/deactivate their UI state.\n\t *\n\t * This is an orchestration hint for TTS tool chrome and does not replace\n\t * playback controls such as stop/pause/resume.\n\t */\n\trequestControlHandoff(): void;\n\n\t/**\n\t * Seek forward by sentence units\n\t */\n\tseekForward(units?: number): Promise<void>;\n\n\t/**\n\t * Seek backward by sentence units\n\t */\n\tseekBackward(units?: number): Promise<void>;\n\n\t/**\n\t * Check if currently playing\n\t */\n\tisPlaying(): boolean;\n\n\t/**\n\t * Check if paused\n\t */\n\tisPaused(): boolean;\n\n\t/**\n\t * Get current playback state\n\t */\n\tgetState(): PlaybackState;\n\n\t/**\n\t * Get currently speaking text\n\t */\n\tgetCurrentText(): string | null;\n\n\t/**\n\t * Subscribe to state changes\n\t */\n\tonStateChange(id: string, callback: (state: PlaybackState) => void): void;\n\n\t/**\n\t * Unsubscribe from state changes\n\t */\n\toffStateChange(id: string, callback: (state: PlaybackState) => void): void;\n\n\t/**\n\t * Get capabilities of current provider\n\t */\n\tgetCapabilities(): TTSProviderCapabilities | null;\n\n\t/**\n\t * Update TTS settings dynamically (rate, pitch, voice)\n\t */\n\tupdateSettings(settings: Partial<TTSConfig>): Promise<void>;\n\n\t/**\n\t * Update playback speed and apply it to active planned playback.\n\t */\n\tsetPlaybackRate(rate: number): Promise<void>;\n\n\t/**\n\t * Set highlight coordinator for word highlighting\n\t */\n\tsetHighlightCoordinator(coordinator: HighlightCoordinatorApi): void;\n\n\t/**\n\t * Set a late-bound provider for optional host TTS highlight target remapping.\n\t */\n\tsetHighlightTargetResolverProvider?(\n\t\tprovider: TTSHighlightTargetResolverProvider | null,\n\t): () => void;\n\n\t/**\n\t * Set accessibility catalog resolver for spoken content\n\t */\n\tsetCatalogResolver(resolver: AccessibilityCatalogResolver): void;\n}\n\n/**\n * Accessibility catalog resolver interface\n *\n * Manages QTI 3.0 accessibility catalogs at assessment and item levels.\n * Provides lookup and resolution services for alternative content representations.\n */\nexport interface AccessibilityCatalogResolverApi {\n\t/**\n\t * Set the default language for fallback resolution\n\t */\n\tsetDefaultLanguage(language: string): void;\n\n\t/**\n\t * Get the default language\n\t */\n\tgetDefaultLanguage(): string;\n\n\t/**\n\t * Add item-level catalogs (called when rendering a new item)\n\t */\n\taddItemCatalogs(catalogs: any[]): void;\n\n\t/**\n\t * Register catalogs scoped to a mounted content owner.\n\t */\n\tregisterCatalogs?(context: CatalogOwnerContext, catalogs: any[]): () => void;\n\n\t/**\n\t * Clear item-level catalogs (called when leaving an item)\n\t */\n\tclearItemCatalogs(): void;\n\n\t/**\n\t * Check if a catalog exists\n\t */\n\thasCatalog(catalogId: string): boolean;\n\n\t/**\n\t * Get alternative content for a catalog identifier\n\t */\n\tgetAlternative(\n\t\tcatalogId: string,\n\t\toptions: CatalogLookupOptions,\n\t): ResolvedCatalog | null;\n\n\t/**\n\t * Get all available alternatives for a catalog identifier\n\t */\n\tgetAllAlternatives(catalogId: string): ResolvedCatalog[];\n\n\t/**\n\t * Get all catalog identifiers available\n\t */\n\tgetAllCatalogIds(): string[];\n\n\t/**\n\t * Get statistics about available catalogs\n\t */\n\tgetStatistics(): CatalogStatistics;\n\n\t/**\n\t * Check if a specific catalog type is available\n\t */\n\thasAlternativeType(catalogId: string, type: CatalogType): boolean;\n\n\t/**\n\t * Get all catalog IDs that have a specific type of alternative\n\t */\n\tgetCatalogsByType(type: CatalogType): string[];\n\n\t/**\n\t * Reset all catalogs\n\t */\n\treset(): void;\n\n\t/**\n\t * Destroy and cleanup\n\t */\n\tdestroy(): void;\n}\n\n/**\n * Element tool state store interface\n *\n * Manages element-level ephemeral tool state using composite keys for global uniqueness.\n * Tool state is client-only and separate from PIE session data (which is sent to server for scoring).\n */\nexport interface ElementToolStateStoreApi {\n\t/**\n\t * Generate a globally unique element ID from components\n\t */\n\tgetGlobalElementId(\n\t\tassessmentId: string,\n\t\tsectionId: string,\n\t\titemId: string,\n\t\telementId: string,\n\t): string;\n\n\t/**\n\t * Parse a global element ID into its components\n\t */\n\tparseGlobalElementId(globalElementId: string): {\n\t\tassessmentId: string;\n\t\tsectionId: string;\n\t\titemId: string;\n\t\telementId: string;\n\t} | null;\n\n\t/**\n\t * Set state for a specific tool on an element\n\t */\n\tsetState(globalElementId: string, toolId: string, state: any): void;\n\n\t/**\n\t * Get state for a specific tool on an element\n\t */\n\tgetState(globalElementId: string, toolId: string): any | undefined;\n\n\t/**\n\t * Get all tool states for a specific element\n\t */\n\tgetElementState(globalElementId: string): Record<string, any>;\n\n\t/**\n\t * Get all element states\n\t */\n\tgetAllState(): Record<string, Record<string, any>>;\n\n\t/**\n\t * Subscribe to state changes\n\t */\n\tsubscribe(\n\t\tcallback: (state: Map<string, Map<string, any>>) => void,\n\t): () => void;\n\n\t/**\n\t * Set callback for persistence integration\n\t */\n\tsetOnStateChange(\n\t\tcallback: (state: Record<string, Record<string, any>>) => void,\n\t): void;\n\n\t/**\n\t * Load state from persistence\n\t */\n\tloadState(state: Record<string, Record<string, any>>): void;\n\n\t/**\n\t * Clear state for a specific element\n\t */\n\tclearElement(globalElementId: string): void;\n\n\t/**\n\t * Clear state for a specific tool across all elements\n\t */\n\tclearTool(toolId: string): void;\n\n\t/**\n\t * Clear all elements in a specific section\n\t */\n\tclearSection(assessmentId: string, sectionId: string): void;\n\n\t/**\n\t * Clear all state\n\t */\n\tclearAll(): void;\n}\n\n/**\n * Toolkit coordinator interface\n *\n * Orchestrates all toolkit services (TTS, tools, accessibility, state management) from a single entry point.\n * Provides centralized configuration for tool availability and settings.\n */\nexport interface ToolkitCoordinatorApi {\n\t/**\n\t * Assessment identifier\n\t */\n\treadonly assessmentId: string;\n\n\t/**\n\t * Configuration\n\t */\n\treadonly config: {\n\t\ttools?: {\n\t\t\tproviders?: Record<string, ToolProviderConfig | undefined>;\n\t\t};\n\t};\n\n\t/**\n\t * TTS service\n\t */\n\treadonly ttsService: TtsServiceApi;\n\n\t/**\n\t * Tool coordinator\n\t */\n\treadonly toolCoordinator: ToolCoordinatorApi;\n\n\t/**\n\t * Highlight coordinator\n\t */\n\treadonly highlightCoordinator: HighlightCoordinatorApi;\n\n\t/**\n\t * Element tool state store\n\t */\n\treadonly elementToolStateStore: ElementToolStateStoreApi;\n\n\t/**\n\t * Catalog resolver\n\t */\n\treadonly catalogResolver: AccessibilityCatalogResolverApi;\n\n\t/**\n\t * Tool provider registry\n\t */\n\treadonly toolProviderRegistry: ToolProviderRegistry;\n\n\t/**\n\t * Get all services as a bundle\n\t */\n\tgetServiceBundle(): {\n\t\tttsService: TtsServiceApi;\n\t\ttoolCoordinator: ToolCoordinatorApi;\n\t\thighlightCoordinator: HighlightCoordinatorApi;\n\t\telementToolStateStore: ElementToolStateStoreApi;\n\t\tcatalogResolver: AccessibilityCatalogResolverApi;\n\t\ttoolProviderRegistry: ToolProviderRegistry;\n\t};\n\n\t/**\n\t * Ensure TTS service is initialized and ready.\n\t */\n\tensureTTSReady(config?: Record<string, unknown>): Promise<void>;\n\n\t/**\n\t * Ensure a provider is initialized and ready.\n\t */\n\tensureProviderReady(providerId: string): Promise<unknown>;\n\n\t/**\n\t * Wait until coordinator initialization is complete.\n\t */\n\twaitUntilReady(): Promise<void>;\n\n\t/**\n\t * Check if coordinator has completed initialization.\n\t */\n\tisReady(): boolean;\n\n\t/**\n\t * Read current initialization status.\n\t */\n\tgetInitStatus(): ToolkitInitStatus;\n\n\t/**\n\t * Check if a tool is enabled\n\t */\n\tisToolEnabled(toolId: string): boolean;\n\n\t/**\n\t * Get tool configuration\n\t */\n\tgetToolConfig(toolId: string): ToolProviderConfig | null;\n\n\t/**\n\t * Update tool configuration\n\t */\n\tupdateToolConfig(toolId: string, updates: Partial<ToolProviderConfig>): void;\n\n\t/**\n\t * Update the enabled tool list for one placement level.\n\t */\n\tupdateToolPlacement(level: ToolPlacementLevel, toolIds: string[]): void;\n\n\t/**\n\t * Patch one or more placement levels in the canonical tools config.\n\t */\n\tupdateToolsPlacement(partial: ToolPlacementConfig): void;\n\n\t/**\n\t * Register or update lifecycle hooks at runtime.\n\t */\n\tsetHooks(hooks: ToolkitCoordinatorHooks): void;\n\n\t/**\n\t * Return a section controller if already created.\n\t */\n\tgetSectionController(args: {\n\t\tsectionId: string;\n\t\tattemptId?: string;\n\t}): SectionControllerHandle | undefined;\n\n\t/**\n\t * Subscribe to section controller events.\n\t *\n\t * The listener is bound to the toolkit's *active section cohort* and\n\t * automatically migrates across cohort transitions\n\t * (`getOrCreateSectionController` for a different cohort). On every\n\t * migration the listener receives a snapshot replay of the new\n\t * cohort's already-loaded `content-loaded` events followed by the\n\t * aggregate `section-loading-complete`, in the canonical order a\n\t * fresh subscriber would have observed.\n\t *\n\t * Throws if no active section cohort exists; host code must call\n\t * `getOrCreateSectionController(...)` at least once before\n\t * subscribing. (`toolkit-ready` alone is not sufficient — it fires\n\t * once toolkit state has loaded but before any section controller\n\t * has been created.) The typical pattern is to subscribe once\n\t * immediately after the first `getOrCreateSectionController(...)`\n\t * resolves; the subscription then follows the active cohort across\n\t * all subsequent navigation without further wiring.\n\t *\n\t * Subscribing the same `listener` reference twice replaces the prior\n\t * subscription with the new one (filter args from the second call\n\t * win); calling the returned disposer twice is a no-op.\n\t *\n\t * A listener that throws is caught and `console.warn`-logged; the\n\t * throw does not interrupt fan-out to the remaining listeners.\n\t */\n\tsubscribeSectionEvents(args: SectionEventSubscriptionArgs): () => void;\n\n\t/**\n\t * Subscribe to item-scoped section controller events.\n\t *\n\t * Same active-cohort binding contract as {@link subscribeSectionEvents};\n\t * defaults `eventTypes` to the item-scoped subset.\n\t */\n\tsubscribeItemEvents(args: SectionItemEventSubscriptionArgs): () => void;\n\n\t/**\n\t * Subscribe to section-scoped lifecycle/loading/completion/error events.\n\t *\n\t * Same active-cohort binding contract as {@link subscribeSectionEvents};\n\t * defaults `eventTypes` to the section-scoped subset.\n\t */\n\tsubscribeSectionLifecycleEvents(\n\t\targs: SectionScopedEventSubscriptionArgs,\n\t): () => void;\n\n\t/**\n\t * Create or reuse a section controller with single-flight deduplication.\n\t */\n\tgetOrCreateSectionController(args: {\n\t\tsectionId: string;\n\t\tattemptId?: string;\n\t\tinput?: unknown;\n\t\tupdateExisting?: boolean;\n\t\tcreateDefaultController: () =>\n\t\t\t| SectionControllerHandle\n\t\t\t| Promise<SectionControllerHandle>;\n\t}): Promise<SectionControllerHandle>;\n\n\t/**\n\t * Dispose an existing section controller.\n\t */\n\tdisposeSectionController(args: {\n\t\tsectionId: string;\n\t\tattemptId?: string;\n\t\tpersistBeforeDispose?: boolean;\n\t\tclearPersistence?: boolean;\n\t}): Promise<void>;\n\n\t/**\n\t * Subscribe to framework-error events emitted by the coordinator.\n\t *\n\t * Mirrors the shape of {@link subscribeTelemetry} (see\n\t * `ToolkitCoordinator.subscribeTelemetry`): a synchronous,\n\t * multi-subscriber stream where each call to the internal bus's\n\t * `reportFrameworkError` fans out to every active listener exactly once.\n\t *\n\t * The returned function detaches the listener; calling it twice is a\n\t * no-op. A listener that throws is caught and logged; the throw does\n\t * not break fan-out to the remaining listeners.\n\t *\n\t * Use this to wire framework errors into Sentry / Datadog / a custom\n\t * banner without listening on a DOM event. The DOM event\n\t * (`framework-error`) and the canonical `onFrameworkError` prop on\n\t * `<pie-assessment-toolkit>` consume the same bus.\n\t */\n\tsubscribeFrameworkErrors(listener: FrameworkErrorListener): () => void;\n\n\t// ----------------------------------------------------------------\n\t// Tool Policy Engine — public surface (M8 PR 2 / PR 3).\n\t//\n\t// The coordinator owns a single `ToolPolicyEngine` instance and\n\t// exposes its decision and subscription surface through the API\n\t// so that toolbar custom elements (`pie-item-toolbar`,\n\t// `pie-section-toolbar`), the base section player, and bespoke\n\t// host instrumentation (PNP debugger, etc.) all flow through the\n\t// same engine. Hosts that want to drive PNP/profile inputs imperatively\n\t// (instead of binding props on `<pie-assessment-toolkit>`) call\n\t// `updateAssessment` / `updateCurrentItemRef` /\n\t// `setPnpEnforcement` directly.\n\t// ----------------------------------------------------------------\n\n\t/**\n\t * Resolve the visible tool set for a given placement level + scope.\n\t * Returns the engine's full decision (visible tools, diagnostics,\n\t * provenance). Hosts that only need the IDs may map\n\t * `decision.visibleTools` themselves.\n\t */\n\tdecideToolPolicy(request: ToolPolicyDecisionRequest): ToolPolicyDecision;\n\n\t/**\n\t * Subscribe to policy-engine change events. Fires whenever the\n\t * coordinator's bound policy inputs change (`updateToolConfig`,\n\t * `updateToolPlacement`, `updateAssessment`, `updateCurrentItemRef`,\n\t * `setPnpEnforcement`) or a custom `PolicySource` is registered /\n\t * removed. Listeners that need the new visible tool set should\n\t * call `decideToolPolicy(...)` with their level / scope.\n\t */\n\tonPolicyChange(listener: ToolPolicyChangeListener): () => void;\n\n\t/**\n\t * Bind (or clear) the active assessment for PNP/profile policy decisions.\n\t *\n\t * Under auto-mode (no host override via {@link setPnpEnforcement}),\n\t * the engine flips to `pnpEnforcement: \"on\"` iff the assessment\n\t * carries profile precedence material (`personalNeedsProfile`,\n\t * `settings.districtPolicy`, `settings.testAdministration`) or the\n\t * currently-bound item ref carries item-level profile inputs. A bare\n\t * assessment record (just `id` / `name`) keeps `\"off\"`.\n\t *\n\t * The host override set via {@link setPnpEnforcement} is sticky\n\t * across assessment swaps.\n\t */\n\tupdateAssessment(assessment: AssessmentEntity | null): void;\n\n\t/**\n\t * Bind (or clear) the current item reference for policy decisions.\n\t * Used by item-level profile gates (item `requiredTools` /\n\t * `restrictedTools` / `toolParameters`). Item-level profile material\n\t * also feeds the auto-mode helper — navigating to an item with\n\t * profile settings can flip auto-mode to `\"on\"` even when the parent\n\t * assessment carries no profile block of its own.\n\t */\n\tupdateCurrentItemRef(itemRef: AssessmentItemRef | null): void;\n\n\t/**\n\t * Override the auto-mode PNP/profile enforcement decision. Pass `\"on\"` /\n\t * `\"off\"` to pin the mode, or `null` to clear the override and\n\t * return to auto-mode (`\"on\"` iff the bound assessment / item ref\n\t * carries profile material, otherwise `\"off\"`).\n\t */\n\tsetPnpEnforcement(mode: PnpEnforcementMode | null): void;\n\n\t/**\n\t * Read the engine inputs currently driving decisions. Useful for\n\t * debugging / instrumentation; do not mutate.\n\t */\n\tgetPolicyInputs(): Readonly<ResolvedEngineInputs>;\n\n\t/**\n\t * Register a custom `PolicySource`. The source participates in\n\t * every subsequent `decideToolPolicy(...)` call until disposed\n\t * (the returned function detaches).\n\t */\n\tregisterPolicySource(source: PolicySource): () => void;\n\n\t/**\n\t * Register a host-owned resolver for scoped tool render context.\n\t */\n\tregisterToolContextResolver(\n\t\ttoolId: string,\n\t\tresolver: ToolContextResolver,\n\t): () => void;\n\n\t/**\n\t * Replace all host-owned render-context resolvers.\n\t */\n\tsetToolContextResolvers(\n\t\tresolvers: ToolContextResolverMap | null | undefined,\n\t): void;\n\n\t/**\n\t * Whether a host resolver is registered for this tool.\n\t */\n\thasToolContextResolver(toolId: string): boolean;\n\n\t/**\n\t * Resolve render visibility/params for a tool that already survived policy gates.\n\t */\n\tresolveToolContext(\n\t\tcontext: ToolContextResolverContext,\n\t): ResolvedToolContext | null;\n\n\t/**\n\t * Subscribe to resolver registration/removal changes.\n\t */\n\tonToolContextResolverChange(listener: () => void): () => void;\n}\n\n// I18nServiceApi is re-exported from @pie-players/pie-players-shared/i18n\n"]}
|
|
@@ -228,9 +228,11 @@ export function hasChoiceInteraction(context) {
|
|
|
228
228
|
"pie-multiple-choice",
|
|
229
229
|
"pie-inline-choice",
|
|
230
230
|
"pie-select-text",
|
|
231
|
+
"pie-ebsr",
|
|
231
232
|
"multiple-choice",
|
|
232
233
|
"inline-choice",
|
|
233
234
|
"select-text",
|
|
235
|
+
"ebsr",
|
|
234
236
|
];
|
|
235
237
|
if (isElementContext(context)) {
|
|
236
238
|
const config = context.item.config;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-context.js","sourceRoot":"","sources":["../../src/services/tool-context.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Tool Context Types\n *\n * Defines the context types passed to tools for making visibility and behavior decisions.\n * Each context level provides increasingly specific information about where the tool is being used.\n */\n\nimport type {\n\tAssessmentEntity,\n\tAssessmentItemRef,\n\tAssessmentSection,\n\tItemEntity,\n\tPassageEntity,\n\tRubricBlock,\n} from \"@pie-players/pie-players-shared/types\";\n\n/**\n * Tool placement levels - where a tool can appear in the UI\n */\nexport type ToolLevel =\n\t| \"assessment\" // Assessment-wide tools (e.g., global calculator)\n\t| \"section\" // Section toolbar tools\n\t| \"item\" // Item-level tools (question toolbar)\n\t| \"passage\" // Passage header tools (stimulus rubric blocks)\n\t| \"rubric\" // Rubric block tools (instructions, rubrics)\n\t| \"element\"; // Element-specific tools (inline with interaction)\n\n/**\n * Base context available to all tools\n */\nexport interface BaseToolContext {\n\t/** The level at which this tool is being evaluated */\n\tlevel: ToolLevel;\n\n\t/** The full assessment entity */\n\tassessment: AssessmentEntity;\n\n\t/** Current section, if applicable */\n\tsection?: AssessmentSection;\n\n\t/** Reference to the current item (always available except at assessment level) */\n\titemRef?: AssessmentItemRef;\n\n\t/** DOM container where the tool will be rendered */\n\tcontainer?: HTMLElement;\n}\n\n/**\n * Context for assessment-level tools\n */\nexport interface AssessmentToolContext extends BaseToolContext {\n\tlevel: \"assessment\";\n}\n\n/**\n * Context for section-level tools\n */\nexport interface SectionToolContext extends BaseToolContext {\n\tlevel: \"section\";\n\tsection: AssessmentSection;\n}\n\n/**\n * Context for item-level tools (question toolbar)\n */\nexport interface ItemToolContext extends BaseToolContext {\n\tlevel: \"item\";\n\titemRef: AssessmentItemRef;\n\titem: ItemEntity;\n\n\t/** Passage entity if this item is associated with a passage */\n\tpassage?: PassageEntity;\n}\n\n/**\n * Context for passage-level tools (passage header)\n *\n * Passages can appear as:\n * - Standalone passage entities associated with items\n * - RubricBlock with class=\"stimulus\" containing embedded passage\n */\nexport interface PassageToolContext extends BaseToolContext {\n\tlevel: \"passage\";\n\titemRef?: AssessmentItemRef;\n\tpassage: PassageEntity;\n\n\t/** Items associated with this passage (if applicable) */\n\titems?: ItemEntity[];\n\n\t/** RubricBlock containing this passage (if from rubric) */\n\trubricBlock?: RubricBlock;\n}\n\n/**\n * Context for rubric block tools\n *\n * RubricBlocks can contain:\n * - Instructions (class=\"instructions\")\n * - Rubrics (class=\"rubric\")\n * - Stimulus/passages (class=\"stimulus\" with embedded passage)\n * - Simple HTML content\n */\nexport interface RubricToolContext extends BaseToolContext {\n\tlevel: \"rubric\";\n\trubricBlock: RubricBlock;\n\n\t/** Section containing this rubric block */\n\tsection: AssessmentSection;\n\n\t/** If rubric contains a passage (class=\"stimulus\") */\n\tpassage?: PassageEntity;\n}\n\n/**\n * Context for element-level tools (inline with interaction)\n *\n * Note: Element-level context uses the PIE interaction element ID\n * and provides access to the full item config for content inspection.\n */\nexport interface ElementToolContext extends BaseToolContext {\n\tlevel: \"element\";\n\titemRef: AssessmentItemRef;\n\titem: ItemEntity;\n\n\t/** The PIE element/interaction ID */\n\telementId: string;\n\n\t/** Passage entity if this item is associated with a passage */\n\tpassage?: PassageEntity;\n\n\t/** DOM element that triggered the tool (e.g., the specific input field) */\n\ttriggerElement?: HTMLElement;\n}\n\n/**\n * Union type of all tool contexts\n */\nexport type ToolContext =\n\t| AssessmentToolContext\n\t| SectionToolContext\n\t| ItemToolContext\n\t| PassageToolContext\n\t| RubricToolContext\n\t| ElementToolContext;\n\n/**\n * Type guard to check if context is at assessment level\n */\nexport function isAssessmentContext(\n\tcontext: ToolContext,\n): context is AssessmentToolContext {\n\treturn context.level === \"assessment\";\n}\n\n/**\n * Type guard to check if context is at section level\n */\nexport function isSectionContext(\n\tcontext: ToolContext,\n): context is SectionToolContext {\n\treturn context.level === \"section\";\n}\n\n/**\n * Type guard to check if context is at item level\n */\nexport function isItemContext(\n\tcontext: ToolContext,\n): context is ItemToolContext {\n\treturn context.level === \"item\";\n}\n\n/**\n * Type guard to check if context is at passage level\n */\nexport function isPassageContext(\n\tcontext: ToolContext,\n): context is PassageToolContext {\n\treturn context.level === \"passage\";\n}\n\n/**\n * Type guard to check if context is at rubric level\n */\nexport function isRubricContext(\n\tcontext: ToolContext,\n): context is RubricToolContext {\n\treturn context.level === \"rubric\";\n}\n\n/**\n * Type guard to check if context is at element level\n */\nexport function isElementContext(\n\tcontext: ToolContext,\n): context is ElementToolContext {\n\treturn context.level === \"element\";\n}\n\n/**\n * Helper to extract text content from an item or element for analysis\n */\nexport function extractTextContent(context: ToolContext): string {\n\tif (isElementContext(context)) {\n\t\tconst config = context.item.config;\n\t\tif (!config) return \"\";\n\t\tconst textChunks: string[] = [];\n\t\tconst stripHtml = (value: string) => value.replace(/<[^>]*>/g, \" \").trim();\n\n\t\t// Try to find element markup by element id.\n\t\tconst elementMarkup = config.elements?.[context.elementId];\n\t\tif (elementMarkup) {\n\t\t\tif (typeof elementMarkup === \"string\") {\n\t\t\t\ttextChunks.push(stripHtml(elementMarkup));\n\t\t\t}\n\t\t}\n\n\t\t// Also inspect model data keyed by this element id.\n\t\t// In many items, math appears in model.prompt/labels rather than elements[elementId].\n\t\tconst modelsRaw = config.models;\n\t\tconst models = Array.isArray(modelsRaw)\n\t\t\t? modelsRaw\n\t\t\t: modelsRaw && typeof modelsRaw === \"object\"\n\t\t\t\t? Object.values(modelsRaw as Record<string, unknown>)\n\t\t\t\t: [];\n\t\tconst model = models.find(\n\t\t\t(m: any) => m && typeof m === \"object\" && m.id === context.elementId,\n\t\t) as Record<string, unknown> | undefined;\n\t\tif (model) {\n\t\t\tfor (const value of Object.values(model)) {\n\t\t\t\tif (typeof value === \"string\") {\n\t\t\t\t\ttextChunks.push(stripHtml(value));\n\t\t\t\t}\n\t\t\t\tif (Array.isArray(value)) {\n\t\t\t\t\tfor (const entry of value) {\n\t\t\t\t\t\tif (entry && typeof entry === \"object\") {\n\t\t\t\t\t\t\tfor (const nested of Object.values(\n\t\t\t\t\t\t\t\tentry as Record<string, unknown>,\n\t\t\t\t\t\t\t)) {\n\t\t\t\t\t\t\t\tif (typeof nested === \"string\") {\n\t\t\t\t\t\t\t\t\ttextChunks.push(stripHtml(nested));\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn textChunks.filter(Boolean).join(\" \").trim();\n\t}\n\n\tif (isItemContext(context)) {\n\t\tconst item = context.item;\n\t\tif (!item?.config) return \"\";\n\n\t\tconst config = item.config as Record<string, unknown>;\n\t\tconst textChunks: string[] = [];\n\t\tconst stripHtml = (value: string) => value.replace(/<[^>]*>/g, \" \").trim();\n\n\t\t// Primary item markup\n\t\tif (typeof config.markup === \"string\") {\n\t\t\ttextChunks.push(stripHtml(config.markup));\n\t\t}\n\n\t\t// Element markup snippets\n\t\tconst elements = config.elements as Record<string, unknown> | undefined;\n\t\tif (elements && typeof elements === \"object\") {\n\t\t\tfor (const elementMarkup of Object.values(elements)) {\n\t\t\t\tif (typeof elementMarkup === \"string\") {\n\t\t\t\t\ttextChunks.push(stripHtml(elementMarkup));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Model-level text (prompts, labels, etc.)\n\t\tconst modelsRaw = config.models;\n\t\tconst models = Array.isArray(modelsRaw)\n\t\t\t? modelsRaw\n\t\t\t: modelsRaw && typeof modelsRaw === \"object\"\n\t\t\t\t? Object.values(modelsRaw as Record<string, unknown>)\n\t\t\t\t: [];\n\t\tfor (const model of models) {\n\t\t\tif (!model || typeof model !== \"object\") continue;\n\t\t\tfor (const value of Object.values(model as Record<string, unknown>)) {\n\t\t\t\tif (typeof value === \"string\") {\n\t\t\t\t\ttextChunks.push(stripHtml(value));\n\t\t\t\t}\n\t\t\t\tif (Array.isArray(value)) {\n\t\t\t\t\tfor (const entry of value) {\n\t\t\t\t\t\tif (entry && typeof entry === \"object\") {\n\t\t\t\t\t\t\tfor (const nested of Object.values(\n\t\t\t\t\t\t\t\tentry as Record<string, unknown>,\n\t\t\t\t\t\t\t)) {\n\t\t\t\t\t\t\t\tif (typeof nested === \"string\") {\n\t\t\t\t\t\t\t\t\ttextChunks.push(stripHtml(nested));\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn textChunks.filter(Boolean).join(\" \").trim();\n\t}\n\n\tif (isPassageContext(context)) {\n\t\tconst passage = context.passage;\n\t\tif (!passage?.config) return \"\";\n\n\t\tconst config = passage.config as Record<string, unknown>;\n\t\tconst textChunks: string[] = [];\n\t\tconst stripHtml = (value: string) => value.replace(/<[^>]*>/g, \" \").trim();\n\n\t\t// Primary passage markup/content\n\t\tif (typeof config.markup === \"string\") {\n\t\t\ttextChunks.push(stripHtml(config.markup));\n\t\t}\n\t\tif (typeof config.content === \"string\") {\n\t\t\ttextChunks.push(stripHtml(config.content));\n\t\t}\n\t\tif (typeof config.prompt === \"string\") {\n\t\t\ttextChunks.push(stripHtml(config.prompt));\n\t\t}\n\n\t\t// Element markup snippets\n\t\tconst elements = config.elements as Record<string, unknown> | undefined;\n\t\tif (elements && typeof elements === \"object\") {\n\t\t\tfor (const elementMarkup of Object.values(elements)) {\n\t\t\t\tif (typeof elementMarkup === \"string\") {\n\t\t\t\t\ttextChunks.push(stripHtml(elementMarkup));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Model-level text (prompts, labels, etc.)\n\t\tconst modelsRaw = config.models;\n\t\tconst models = Array.isArray(modelsRaw)\n\t\t\t? modelsRaw\n\t\t\t: modelsRaw && typeof modelsRaw === \"object\"\n\t\t\t\t? Object.values(modelsRaw as Record<string, unknown>)\n\t\t\t\t: [];\n\t\tfor (const model of models) {\n\t\t\tif (!model || typeof model !== \"object\") continue;\n\t\t\tfor (const value of Object.values(model as Record<string, unknown>)) {\n\t\t\t\tif (typeof value === \"string\") {\n\t\t\t\t\ttextChunks.push(stripHtml(value));\n\t\t\t\t}\n\t\t\t\tif (Array.isArray(value)) {\n\t\t\t\t\tfor (const entry of value) {\n\t\t\t\t\t\tif (entry && typeof entry === \"object\") {\n\t\t\t\t\t\t\tfor (const nested of Object.values(\n\t\t\t\t\t\t\t\tentry as Record<string, unknown>,\n\t\t\t\t\t\t\t)) {\n\t\t\t\t\t\t\t\tif (typeof nested === \"string\") {\n\t\t\t\t\t\t\t\t\ttextChunks.push(stripHtml(nested));\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn textChunks.filter(Boolean).join(\" \").trim();\n\t}\n\n\tif (isRubricContext(context)) {\n\t\tconst rubric = context.rubricBlock;\n\n\t\t// If rubric has embedded passage, extract from passage config\n\t\tif (rubric.passage?.config) {\n\t\t\tconst markup = rubric.passage.config.markup || \"\";\n\t\t\treturn markup.replace(/<[^>]*>/g, \" \").trim();\n\t\t}\n\n\t\t// Otherwise, use simple content string\n\t\tconst content = rubric.content || \"\";\n\t\treturn content.replace(/<[^>]*>/g, \" \").trim();\n\t}\n\n\treturn \"\";\n}\n\n/**\n * Helper to check if context contains mathematical content\n * (Basic heuristic - can be overridden by tools)\n */\nexport function hasMathContent(context: ToolContext): boolean {\n\tconst text = extractTextContent(context);\n\n\t// Look for common math indicators\n\tconst mathIndicators = [\n\t\t/<math[>\\s]/i, // MathML\n\t\t/\\\\\\[([^\\]]+)\\\\\\]/, // LaTeX display math\n\t\t/\\$\\$[^$]+\\$\\$/, // LaTeX display math ($$...$$)\n\t\t/\\\\\\(/, // LaTeX inline math\n\t\t/[+\\-*/=<>≤≥∑∫√π]/, // Math symbols\n\t\t/\\d+\\s*[+\\-*/=]\\s*\\d+/, // Simple arithmetic\n\t];\n\n\treturn mathIndicators.some((pattern) => pattern.test(text));\n}\n\n/**\n * Helper to check if context contains choice-based interactions\n */\nexport function hasChoiceInteraction(context: ToolContext): boolean {\n\tconst interactionTypes = [\n\t\t\"pie-multiple-choice\",\n\t\t\"pie-inline-choice\",\n\t\t\"pie-select-text\",\n\t\t\"multiple-choice\",\n\t\t\"inline-choice\",\n\t\t\"select-text\",\n\t];\n\n\tif (isElementContext(context)) {\n\t\tconst config = context.item.config;\n\t\tif (!config?.models) return false;\n\n\t\t// Find model for this element\n\t\tconst models = Array.isArray(config.models)\n\t\t\t? config.models\n\t\t\t: Object.values(config.models as Record<string, unknown>);\n\t\tconst model = models.find(\n\t\t\t(m: any) => m && typeof m === \"object\" && m.id === context.elementId,\n\t\t);\n\t\tif (!model) return false;\n\n\t\tconst type = (model as any).element || \"\";\n\t\treturn interactionTypes.includes(type);\n\t}\n\n\tif (isItemContext(context)) {\n\t\tconst modelsRaw = context.item.config?.models;\n\t\tconst models = Array.isArray(modelsRaw)\n\t\t\t? modelsRaw\n\t\t\t: modelsRaw && typeof modelsRaw === \"object\"\n\t\t\t\t? Object.values(modelsRaw as Record<string, unknown>)\n\t\t\t\t: [];\n\t\treturn models.some((m: any) => {\n\t\t\tif (!m || typeof m !== \"object\") return false;\n\t\t\tconst type = m.element || \"\";\n\t\t\tif (interactionTypes.includes(type)) return true;\n\t\t\t// Fallback for configs that don't provide canonical element names.\n\t\t\treturn Array.isArray(m.choices) && m.choices.length > 0;\n\t\t});\n\t}\n\n\treturn false;\n}\n\n/**\n * Helper to check if context contains text that can be read aloud\n */\nexport function hasReadableText(context: ToolContext): boolean {\n\tconst text = extractTextContent(context);\n\t// Must have at least 10 characters of text (arbitrary threshold)\n\treturn text.trim().length >= 10;\n}\n\n/**\n * Helper to check if context contains science content\n * (Basic heuristic - can be overridden by tools)\n */\nexport function hasScienceContent(context: ToolContext): boolean {\n\tconst text = extractTextContent(context);\n\n\t// Look for common science indicators\n\tconst scienceIndicators = [\n\t\t/chemistry|chemical|element|atom|molecule|compound/i,\n\t\t/periodic\\s+table/i,\n\t\t/H₂O|CO₂|NaCl|O₂|N₂/i, // Chemical formulas\n\t\t/\\b[A-Z][a-z]?\\d*\\b/, // Element symbols (H, He, Li, etc.)\n\t\t/biology|organism|cell|DNA|RNA|protein/i,\n\t\t/physics|force|energy|velocity|acceleration/i,\n\t];\n\n\treturn scienceIndicators.some((pattern) => pattern.test(text));\n}\n"]}
|
|
1
|
+
{"version":3,"file":"tool-context.js","sourceRoot":"","sources":["../../src/services/tool-context.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Tool Context Types\n *\n * Defines the context types passed to tools for making visibility and behavior decisions.\n * Each context level provides increasingly specific information about where the tool is being used.\n */\n\nimport type {\n\tAssessmentEntity,\n\tAssessmentItemRef,\n\tAssessmentSection,\n\tItemEntity,\n\tPassageEntity,\n\tRubricBlock,\n} from \"@pie-players/pie-players-shared/types\";\n\n/**\n * Tool placement levels - where a tool can appear in the UI\n */\nexport type ToolLevel =\n\t| \"assessment\" // Assessment-wide tools (e.g., global calculator)\n\t| \"section\" // Section toolbar tools\n\t| \"item\" // Item-level tools (question toolbar)\n\t| \"passage\" // Passage header tools (stimulus rubric blocks)\n\t| \"rubric\" // Rubric block tools (instructions, rubrics)\n\t| \"element\"; // Element-specific tools (inline with interaction)\n\n/**\n * Base context available to all tools\n */\nexport interface BaseToolContext {\n\t/** The level at which this tool is being evaluated */\n\tlevel: ToolLevel;\n\n\t/** The full assessment entity */\n\tassessment: AssessmentEntity;\n\n\t/** Current section, if applicable */\n\tsection?: AssessmentSection;\n\n\t/** Reference to the current item (always available except at assessment level) */\n\titemRef?: AssessmentItemRef;\n\n\t/** DOM container where the tool will be rendered */\n\tcontainer?: HTMLElement;\n}\n\n/**\n * Context for assessment-level tools\n */\nexport interface AssessmentToolContext extends BaseToolContext {\n\tlevel: \"assessment\";\n}\n\n/**\n * Context for section-level tools\n */\nexport interface SectionToolContext extends BaseToolContext {\n\tlevel: \"section\";\n\tsection: AssessmentSection;\n}\n\n/**\n * Context for item-level tools (question toolbar)\n */\nexport interface ItemToolContext extends BaseToolContext {\n\tlevel: \"item\";\n\titemRef: AssessmentItemRef;\n\titem: ItemEntity;\n\n\t/** Passage entity if this item is associated with a passage */\n\tpassage?: PassageEntity;\n}\n\n/**\n * Context for passage-level tools (passage header)\n *\n * Passages can appear as:\n * - Standalone passage entities associated with items\n * - RubricBlock with class=\"stimulus\" containing embedded passage\n */\nexport interface PassageToolContext extends BaseToolContext {\n\tlevel: \"passage\";\n\titemRef?: AssessmentItemRef;\n\tpassage: PassageEntity;\n\n\t/** Items associated with this passage (if applicable) */\n\titems?: ItemEntity[];\n\n\t/** RubricBlock containing this passage (if from rubric) */\n\trubricBlock?: RubricBlock;\n}\n\n/**\n * Context for rubric block tools\n *\n * RubricBlocks can contain:\n * - Instructions (class=\"instructions\")\n * - Rubrics (class=\"rubric\")\n * - Stimulus/passages (class=\"stimulus\" with embedded passage)\n * - Simple HTML content\n */\nexport interface RubricToolContext extends BaseToolContext {\n\tlevel: \"rubric\";\n\trubricBlock: RubricBlock;\n\n\t/** Section containing this rubric block */\n\tsection: AssessmentSection;\n\n\t/** If rubric contains a passage (class=\"stimulus\") */\n\tpassage?: PassageEntity;\n}\n\n/**\n * Context for element-level tools (inline with interaction)\n *\n * Note: Element-level context uses the PIE interaction element ID\n * and provides access to the full item config for content inspection.\n */\nexport interface ElementToolContext extends BaseToolContext {\n\tlevel: \"element\";\n\titemRef: AssessmentItemRef;\n\titem: ItemEntity;\n\n\t/** The PIE element/interaction ID */\n\telementId: string;\n\n\t/** Passage entity if this item is associated with a passage */\n\tpassage?: PassageEntity;\n\n\t/** DOM element that triggered the tool (e.g., the specific input field) */\n\ttriggerElement?: HTMLElement;\n}\n\n/**\n * Union type of all tool contexts\n */\nexport type ToolContext =\n\t| AssessmentToolContext\n\t| SectionToolContext\n\t| ItemToolContext\n\t| PassageToolContext\n\t| RubricToolContext\n\t| ElementToolContext;\n\n/**\n * Type guard to check if context is at assessment level\n */\nexport function isAssessmentContext(\n\tcontext: ToolContext,\n): context is AssessmentToolContext {\n\treturn context.level === \"assessment\";\n}\n\n/**\n * Type guard to check if context is at section level\n */\nexport function isSectionContext(\n\tcontext: ToolContext,\n): context is SectionToolContext {\n\treturn context.level === \"section\";\n}\n\n/**\n * Type guard to check if context is at item level\n */\nexport function isItemContext(\n\tcontext: ToolContext,\n): context is ItemToolContext {\n\treturn context.level === \"item\";\n}\n\n/**\n * Type guard to check if context is at passage level\n */\nexport function isPassageContext(\n\tcontext: ToolContext,\n): context is PassageToolContext {\n\treturn context.level === \"passage\";\n}\n\n/**\n * Type guard to check if context is at rubric level\n */\nexport function isRubricContext(\n\tcontext: ToolContext,\n): context is RubricToolContext {\n\treturn context.level === \"rubric\";\n}\n\n/**\n * Type guard to check if context is at element level\n */\nexport function isElementContext(\n\tcontext: ToolContext,\n): context is ElementToolContext {\n\treturn context.level === \"element\";\n}\n\n/**\n * Helper to extract text content from an item or element for analysis\n */\nexport function extractTextContent(context: ToolContext): string {\n\tif (isElementContext(context)) {\n\t\tconst config = context.item.config;\n\t\tif (!config) return \"\";\n\t\tconst textChunks: string[] = [];\n\t\tconst stripHtml = (value: string) => value.replace(/<[^>]*>/g, \" \").trim();\n\n\t\t// Try to find element markup by element id.\n\t\tconst elementMarkup = config.elements?.[context.elementId];\n\t\tif (elementMarkup) {\n\t\t\tif (typeof elementMarkup === \"string\") {\n\t\t\t\ttextChunks.push(stripHtml(elementMarkup));\n\t\t\t}\n\t\t}\n\n\t\t// Also inspect model data keyed by this element id.\n\t\t// In many items, math appears in model.prompt/labels rather than elements[elementId].\n\t\tconst modelsRaw = config.models;\n\t\tconst models = Array.isArray(modelsRaw)\n\t\t\t? modelsRaw\n\t\t\t: modelsRaw && typeof modelsRaw === \"object\"\n\t\t\t\t? Object.values(modelsRaw as Record<string, unknown>)\n\t\t\t\t: [];\n\t\tconst model = models.find(\n\t\t\t(m: any) => m && typeof m === \"object\" && m.id === context.elementId,\n\t\t) as Record<string, unknown> | undefined;\n\t\tif (model) {\n\t\t\tfor (const value of Object.values(model)) {\n\t\t\t\tif (typeof value === \"string\") {\n\t\t\t\t\ttextChunks.push(stripHtml(value));\n\t\t\t\t}\n\t\t\t\tif (Array.isArray(value)) {\n\t\t\t\t\tfor (const entry of value) {\n\t\t\t\t\t\tif (entry && typeof entry === \"object\") {\n\t\t\t\t\t\t\tfor (const nested of Object.values(\n\t\t\t\t\t\t\t\tentry as Record<string, unknown>,\n\t\t\t\t\t\t\t)) {\n\t\t\t\t\t\t\t\tif (typeof nested === \"string\") {\n\t\t\t\t\t\t\t\t\ttextChunks.push(stripHtml(nested));\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn textChunks.filter(Boolean).join(\" \").trim();\n\t}\n\n\tif (isItemContext(context)) {\n\t\tconst item = context.item;\n\t\tif (!item?.config) return \"\";\n\n\t\tconst config = item.config as Record<string, unknown>;\n\t\tconst textChunks: string[] = [];\n\t\tconst stripHtml = (value: string) => value.replace(/<[^>]*>/g, \" \").trim();\n\n\t\t// Primary item markup\n\t\tif (typeof config.markup === \"string\") {\n\t\t\ttextChunks.push(stripHtml(config.markup));\n\t\t}\n\n\t\t// Element markup snippets\n\t\tconst elements = config.elements as Record<string, unknown> | undefined;\n\t\tif (elements && typeof elements === \"object\") {\n\t\t\tfor (const elementMarkup of Object.values(elements)) {\n\t\t\t\tif (typeof elementMarkup === \"string\") {\n\t\t\t\t\ttextChunks.push(stripHtml(elementMarkup));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Model-level text (prompts, labels, etc.)\n\t\tconst modelsRaw = config.models;\n\t\tconst models = Array.isArray(modelsRaw)\n\t\t\t? modelsRaw\n\t\t\t: modelsRaw && typeof modelsRaw === \"object\"\n\t\t\t\t? Object.values(modelsRaw as Record<string, unknown>)\n\t\t\t\t: [];\n\t\tfor (const model of models) {\n\t\t\tif (!model || typeof model !== \"object\") continue;\n\t\t\tfor (const value of Object.values(model as Record<string, unknown>)) {\n\t\t\t\tif (typeof value === \"string\") {\n\t\t\t\t\ttextChunks.push(stripHtml(value));\n\t\t\t\t}\n\t\t\t\tif (Array.isArray(value)) {\n\t\t\t\t\tfor (const entry of value) {\n\t\t\t\t\t\tif (entry && typeof entry === \"object\") {\n\t\t\t\t\t\t\tfor (const nested of Object.values(\n\t\t\t\t\t\t\t\tentry as Record<string, unknown>,\n\t\t\t\t\t\t\t)) {\n\t\t\t\t\t\t\t\tif (typeof nested === \"string\") {\n\t\t\t\t\t\t\t\t\ttextChunks.push(stripHtml(nested));\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn textChunks.filter(Boolean).join(\" \").trim();\n\t}\n\n\tif (isPassageContext(context)) {\n\t\tconst passage = context.passage;\n\t\tif (!passage?.config) return \"\";\n\n\t\tconst config = passage.config as Record<string, unknown>;\n\t\tconst textChunks: string[] = [];\n\t\tconst stripHtml = (value: string) => value.replace(/<[^>]*>/g, \" \").trim();\n\n\t\t// Primary passage markup/content\n\t\tif (typeof config.markup === \"string\") {\n\t\t\ttextChunks.push(stripHtml(config.markup));\n\t\t}\n\t\tif (typeof config.content === \"string\") {\n\t\t\ttextChunks.push(stripHtml(config.content));\n\t\t}\n\t\tif (typeof config.prompt === \"string\") {\n\t\t\ttextChunks.push(stripHtml(config.prompt));\n\t\t}\n\n\t\t// Element markup snippets\n\t\tconst elements = config.elements as Record<string, unknown> | undefined;\n\t\tif (elements && typeof elements === \"object\") {\n\t\t\tfor (const elementMarkup of Object.values(elements)) {\n\t\t\t\tif (typeof elementMarkup === \"string\") {\n\t\t\t\t\ttextChunks.push(stripHtml(elementMarkup));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Model-level text (prompts, labels, etc.)\n\t\tconst modelsRaw = config.models;\n\t\tconst models = Array.isArray(modelsRaw)\n\t\t\t? modelsRaw\n\t\t\t: modelsRaw && typeof modelsRaw === \"object\"\n\t\t\t\t? Object.values(modelsRaw as Record<string, unknown>)\n\t\t\t\t: [];\n\t\tfor (const model of models) {\n\t\t\tif (!model || typeof model !== \"object\") continue;\n\t\t\tfor (const value of Object.values(model as Record<string, unknown>)) {\n\t\t\t\tif (typeof value === \"string\") {\n\t\t\t\t\ttextChunks.push(stripHtml(value));\n\t\t\t\t}\n\t\t\t\tif (Array.isArray(value)) {\n\t\t\t\t\tfor (const entry of value) {\n\t\t\t\t\t\tif (entry && typeof entry === \"object\") {\n\t\t\t\t\t\t\tfor (const nested of Object.values(\n\t\t\t\t\t\t\t\tentry as Record<string, unknown>,\n\t\t\t\t\t\t\t)) {\n\t\t\t\t\t\t\t\tif (typeof nested === \"string\") {\n\t\t\t\t\t\t\t\t\ttextChunks.push(stripHtml(nested));\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn textChunks.filter(Boolean).join(\" \").trim();\n\t}\n\n\tif (isRubricContext(context)) {\n\t\tconst rubric = context.rubricBlock;\n\n\t\t// If rubric has embedded passage, extract from passage config\n\t\tif (rubric.passage?.config) {\n\t\t\tconst markup = rubric.passage.config.markup || \"\";\n\t\t\treturn markup.replace(/<[^>]*>/g, \" \").trim();\n\t\t}\n\n\t\t// Otherwise, use simple content string\n\t\tconst content = rubric.content || \"\";\n\t\treturn content.replace(/<[^>]*>/g, \" \").trim();\n\t}\n\n\treturn \"\";\n}\n\n/**\n * Helper to check if context contains mathematical content\n * (Basic heuristic - can be overridden by tools)\n */\nexport function hasMathContent(context: ToolContext): boolean {\n\tconst text = extractTextContent(context);\n\n\t// Look for common math indicators\n\tconst mathIndicators = [\n\t\t/<math[>\\s]/i, // MathML\n\t\t/\\\\\\[([^\\]]+)\\\\\\]/, // LaTeX display math\n\t\t/\\$\\$[^$]+\\$\\$/, // LaTeX display math ($$...$$)\n\t\t/\\\\\\(/, // LaTeX inline math\n\t\t/[+\\-*/=<>≤≥∑∫√π]/, // Math symbols\n\t\t/\\d+\\s*[+\\-*/=]\\s*\\d+/, // Simple arithmetic\n\t];\n\n\treturn mathIndicators.some((pattern) => pattern.test(text));\n}\n\n/**\n * Helper to check if context contains choice-based interactions\n */\nexport function hasChoiceInteraction(context: ToolContext): boolean {\n\tconst interactionTypes = [\n\t\t\"pie-multiple-choice\",\n\t\t\"pie-inline-choice\",\n\t\t\"pie-select-text\",\n\t\t\"pie-ebsr\",\n\t\t\"multiple-choice\",\n\t\t\"inline-choice\",\n\t\t\"select-text\",\n\t\t\"ebsr\",\n\t];\n\n\tif (isElementContext(context)) {\n\t\tconst config = context.item.config;\n\t\tif (!config?.models) return false;\n\n\t\t// Find model for this element\n\t\tconst models = Array.isArray(config.models)\n\t\t\t? config.models\n\t\t\t: Object.values(config.models as Record<string, unknown>);\n\t\tconst model = models.find(\n\t\t\t(m: any) => m && typeof m === \"object\" && m.id === context.elementId,\n\t\t);\n\t\tif (!model) return false;\n\n\t\tconst type = (model as any).element || \"\";\n\t\treturn interactionTypes.includes(type);\n\t}\n\n\tif (isItemContext(context)) {\n\t\tconst modelsRaw = context.item.config?.models;\n\t\tconst models = Array.isArray(modelsRaw)\n\t\t\t? modelsRaw\n\t\t\t: modelsRaw && typeof modelsRaw === \"object\"\n\t\t\t\t? Object.values(modelsRaw as Record<string, unknown>)\n\t\t\t\t: [];\n\t\treturn models.some((m: any) => {\n\t\t\tif (!m || typeof m !== \"object\") return false;\n\t\t\tconst type = m.element || \"\";\n\t\t\tif (interactionTypes.includes(type)) return true;\n\t\t\t// Fallback for configs that don't provide canonical element names.\n\t\t\treturn Array.isArray(m.choices) && m.choices.length > 0;\n\t\t});\n\t}\n\n\treturn false;\n}\n\n/**\n * Helper to check if context contains text that can be read aloud\n */\nexport function hasReadableText(context: ToolContext): boolean {\n\tconst text = extractTextContent(context);\n\t// Must have at least 10 characters of text (arbitrary threshold)\n\treturn text.trim().length >= 10;\n}\n\n/**\n * Helper to check if context contains science content\n * (Basic heuristic - can be overridden by tools)\n */\nexport function hasScienceContent(context: ToolContext): boolean {\n\tconst text = extractTextContent(context);\n\n\t// Look for common science indicators\n\tconst scienceIndicators = [\n\t\t/chemistry|chemical|element|atom|molecule|compound/i,\n\t\t/periodic\\s+table/i,\n\t\t/H₂O|CO₂|NaCl|O₂|N₂/i, // Chemical formulas\n\t\t/\\b[A-Z][a-z]?\\d*\\b/, // Element symbols (H, He, Li, etc.)\n\t\t/biology|organism|cell|DNA|RNA|protein/i,\n\t\t/physics|force|energy|velocity|acceleration/i,\n\t];\n\n\treturn scienceIndicators.some((pattern) => pattern.test(text));\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pie-players/pie-assessment-toolkit",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.61",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "PIE assessment toolkit: composable services + reference implementation for assessment players and tool coordination",
|
|
6
6
|
"license": "MIT",
|
|
@@ -75,15 +75,15 @@
|
|
|
75
75
|
"test": "bun test"
|
|
76
76
|
},
|
|
77
77
|
"dependencies": {
|
|
78
|
-
"@pie-players/pie-calculator": "0.3.
|
|
79
|
-
"@pie-players/pie-context": "0.3.
|
|
80
|
-
"@pie-players/pie-players-shared": "0.3.
|
|
81
|
-
"@pie-players/pie-tts": "0.3.
|
|
78
|
+
"@pie-players/pie-calculator": "0.3.61",
|
|
79
|
+
"@pie-players/pie-context": "0.3.61",
|
|
80
|
+
"@pie-players/pie-players-shared": "0.3.61",
|
|
81
|
+
"@pie-players/pie-tts": "0.3.61",
|
|
82
82
|
"speech-rule-engine": "^5.0.0-rc.1"
|
|
83
83
|
},
|
|
84
84
|
"peerDependencies": {
|
|
85
|
-
"@pie-players/pie-calculator-desmos": "0.3.
|
|
86
|
-
"@pie-players/tts-client-server": "0.3.
|
|
85
|
+
"@pie-players/pie-calculator-desmos": "0.3.61",
|
|
86
|
+
"@pie-players/tts-client-server": "0.3.61"
|
|
87
87
|
},
|
|
88
88
|
"peerDependenciesMeta": {
|
|
89
89
|
"@pie-players/pie-calculator-desmos": {
|