@pie-players/pie-players-shared 0.3.74 → 1.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/README.md +88 -75
  2. package/dist/components/vendor/nds/README.md +4 -5
  3. package/dist/config/profile.d.ts +1 -1
  4. package/dist/config/profile.js +1 -1
  5. package/dist/i18n/index.d.ts +1 -1
  6. package/dist/i18n/index.js +1 -1
  7. package/dist/i18n/language-tags.d.ts +9 -1
  8. package/dist/i18n/language-tags.js +11 -1
  9. package/dist/i18n/messages/en-US.d.ts +33 -28
  10. package/dist/i18n/messages/en-US.js +34 -29
  11. package/dist/i18n/messages/nl-NL.d.ts +33 -28
  12. package/dist/i18n/messages/nl-NL.js +34 -29
  13. package/dist/i18n/provider.d.ts +14 -7
  14. package/dist/i18n/provider.js +26 -29
  15. package/dist/i18n/types.d.ts +4 -5
  16. package/dist/index.d.ts +1 -1
  17. package/dist/index.js +0 -1
  18. package/dist/instrumentation/providers/BaseInstrumentationProvider.js +0 -14
  19. package/dist/loader-config.d.ts +0 -2
  20. package/dist/loader-config.js +0 -2
  21. package/dist/loaders/element-loader.js +13 -6
  22. package/dist/loaders/esm-adapter.d.ts +11 -0
  23. package/dist/loaders/esm-adapter.js +74 -45
  24. package/dist/loaders/iife-adapter.d.ts +3 -3
  25. package/dist/loaders/iife-adapter.js +36 -35
  26. package/dist/loaders/index.d.ts +4 -2
  27. package/dist/loaders/index.js +2 -1
  28. package/dist/loaders/mathjax-events.d.ts +22 -0
  29. package/dist/loaders/mathjax-events.js +65 -0
  30. package/dist/loaders/preloaded-registration.d.ts +18 -2
  31. package/dist/loaders/preloaded-registration.js +14 -2
  32. package/dist/media/index.d.ts +4 -6
  33. package/dist/media/index.js +6 -8
  34. package/dist/object/index.d.ts +8 -3
  35. package/dist/object/index.js +8 -6
  36. package/dist/pie/asset-handler.d.ts +0 -17
  37. package/dist/pie/asset-handler.js +1 -1
  38. package/dist/pie/authoring.js +5 -7
  39. package/dist/pie/elements-rendered.d.ts +23 -0
  40. package/dist/pie/elements-rendered.js +96 -0
  41. package/dist/pie/index.d.ts +4 -6
  42. package/dist/pie/index.js +4 -7
  43. package/dist/pie/initialization.d.ts +13 -14
  44. package/dist/pie/initialization.js +61 -75
  45. package/dist/pie/instrumentation-event-bridge.js +23 -1
  46. package/dist/pie/instrumentation-event-map.js +11 -18
  47. package/dist/pie/item-controller.js +4 -0
  48. package/dist/pie/item-session-contract.js +7 -9
  49. package/dist/pie/logger.d.ts +6 -0
  50. package/dist/pie/logger.js +10 -0
  51. package/dist/pie/math-assets.d.ts +48 -0
  52. package/dist/pie/math-assets.js +81 -0
  53. package/dist/pie/resource-monitor.d.ts +34 -32
  54. package/dist/pie/resource-monitor.js +152 -231
  55. package/dist/pie/scoring.d.ts +1 -6
  56. package/dist/pie/scoring.js +3 -8
  57. package/dist/pie/session-commit.js +1 -1
  58. package/dist/pie/stages.d.ts +11 -47
  59. package/dist/pie/stages.js +11 -47
  60. package/dist/pie/updates.js +5 -4
  61. package/dist/pie/utils.d.ts +0 -2
  62. package/dist/pie/utils.js +0 -2
  63. package/dist/security/sanitize-item-markup.d.ts +8 -4
  64. package/dist/security/sanitize-item-markup.js +52 -6
  65. package/dist/security/sanitize-svg-icon.js +2 -2
  66. package/dist/security/wrap-overwide-images.d.ts +1 -1
  67. package/dist/security/wrap-overwide-images.js +20 -1
  68. package/dist/security/wrap-overwide.d.ts +8 -0
  69. package/dist/security/wrap-overwide.js +47 -3
  70. package/dist/timed-media/media-element-source.js +1 -2
  71. package/dist/types/index.d.ts +88 -153
  72. package/dist/types/index.js +2 -2
  73. package/dist/ui/attribute-coercion.d.ts +5 -0
  74. package/dist/ui/attribute-coercion.js +6 -4
  75. package/dist/ui/content-styles.d.ts +6 -10
  76. package/dist/ui/content-styles.js +8 -13
  77. package/dist/ui/first-focusable.d.ts +0 -1
  78. package/dist/ui/first-focusable.js +0 -1
  79. package/dist/ui/focus-trap.js +2 -3
  80. package/dist/ui/scope-css.d.ts +23 -26
  81. package/dist/ui/scope-css.js +23 -26
  82. package/package.json +6 -4
  83. package/dist/loaders/mathjax-version-conflict.d.ts +0 -17
  84. package/dist/loaders/mathjax-version-conflict.js +0 -51
  85. package/dist/pie/stage-tracker.d.ts +0 -50
  86. package/dist/pie/stage-tracker.js +0 -105
@@ -1,11 +1,12 @@
1
1
  /**
2
- * This contains copies of the data types we use in PIEOneer. These types, unlike
3
- * the ones in the datastore package, should not have any dependencies, and might in some
4
- * cases be more shallow than their originals. The main reason for these is that they should
5
- * be able to be used in front-end code (as well as the PIEOneer backend code).
2
+ * The PIE content and assessment model the players share: item, passage and
3
+ * section configs, the QTI-aligned assessment structure, session snapshots,
4
+ * accessibility catalogs and media references, `Env`, the element controller
5
+ * contract and authoring asset events.
6
6
  *
7
- * INCLUDING REFERENCES TO THE DATASTORE PACKAGE INSTEAD WILL MAKE PIEONEER THROW 500
8
- * EVERYWHERE WITHOUT ANY FURTHER EXPLANATION
7
+ * The `*Entity` shapes mirror content-store records and may be shallower than
8
+ * them. The module depends on nothing beyond this package's own type modules,
9
+ * so front-end and server code can both import it.
9
10
  */
10
11
  import type { FormativeDeliveryPolicy, FormativeItemPolicy, FormativeSectionSlice } from "../formative/types.js";
11
12
  import type { TimedMediaSectionData, TimedMediaSectionSessionSlice } from "../timed-media/types.js";
@@ -55,10 +56,12 @@ export interface ConfigEntity {
55
56
  markup: string;
56
57
  elements: ConfigElements;
57
58
  models: PieModel[];
59
+ /** Configure settings for authoring mode. */
58
60
  configuration?: Record<string, any>;
59
61
  /**
60
62
  * QTI 3.0: Extracted accessibility catalogs from embedded SSML.
61
- * Automatically generated by SSMLExtractor when <speak> tags are found in content.
63
+ * Written by SSMLExtractor when a host's preprocessing runs it over content
64
+ * holding <speak> tags; the players register what this carries.
62
65
  */
63
66
  extractedCatalogs?: AccessibilityCatalog[];
64
67
  [key: string]: any;
@@ -77,12 +80,9 @@ export interface PassageEntity extends VersionEntity, ConfigContainerEntity, Sea
77
80
  export interface ItemEntity extends VersionEntity, ConfigContainerEntity, SearchMetaDataEntity {
78
81
  name?: string;
79
82
  /**
80
- * `null` as well as absent, because importers write it. JSON has no
81
- * `undefined`, so an item transformed from another format carries an explicit
82
- * `passage: null` for "no passage" — the runtime has always tested for
83
- * null, so it expected it while the type denied it. Excluding null
84
- * only meant a host feeding real importer output through a typed path had to
85
- * cast it away.
83
+ * `null` as well as absent, because importers write it: JSON has no
84
+ * `undefined`, so an item transformed from another format carries an
85
+ * explicit `passage: null` for "no passage", and the runtime tests for it.
86
86
  */
87
87
  passage?: string | PassageEntity | null;
88
88
  /** QTI/APIP-style accessibility catalogs owned by the item root. */
@@ -92,6 +92,23 @@ export interface ItemEntity extends VersionEntity, ConfigContainerEntity, Search
92
92
  configuration?: any;
93
93
  highlights?: Record<string, string[]>;
94
94
  }
95
+ /**
96
+ * `baseId` and `version` are optional on an entity a section references. They
97
+ * identify a stored version, which a host building a section from an item
98
+ * config does not have, and no player reads either.
99
+ */
100
+ type WithOptionalVersion<T extends VersionEntity> = Omit<T, "baseId" | "version"> & Partial<Pick<VersionEntity, "baseId" | "version">>;
101
+ /**
102
+ * A passage as a section's item references it. `name` is optional on the same
103
+ * ground: the section player reads it only as a fallback for a missing `id`.
104
+ */
105
+ export type ReferencedPassageEntity = Omit<WithOptionalVersion<PassageEntity>, "name"> & {
106
+ name?: string;
107
+ };
108
+ /** An item as `AssessmentItemRef.item` references it. */
109
+ export type ReferencedItemEntity = Omit<WithOptionalVersion<ItemEntity>, "passage"> & {
110
+ passage?: string | ReferencedPassageEntity | null;
111
+ };
95
112
  /**
96
113
  * Metadata specifically for interpretation by clients, typically containing
97
114
  * options that are relevant for the user interface. This is not indexed for search.
@@ -146,7 +163,7 @@ export interface AssessmentItemRef extends SearchMetaDataEntity {
146
163
  * Resolved item entity with PIE config.
147
164
  * This is populated by the client before passing to the player.
148
165
  */
149
- item?: ItemEntity;
166
+ item?: ReferencedItemEntity;
150
167
  /** Item-level settings for tool requirements and customization */
151
168
  settings?: ItemSettings;
152
169
  /**
@@ -182,14 +199,6 @@ export interface AssessmentSection extends SearchMetaDataEntity, SettingsMetaDat
182
199
  */
183
200
  assessmentItemRefs?: AssessmentItemRef[];
184
201
  rubricBlocks?: RubricBlock[];
185
- /**
186
- * QTI 3.0: Personal Needs Profile (PNP 3.0) for section-scoped delivery.
187
- *
188
- * Section players read this (falling back to `settings.personalNeedsProfile`,
189
- * then to the computed default profile) to drive PNP policy when a section is
190
- * delivered without an enclosing assessment.
191
- */
192
- personalNeedsProfile?: PersonalNeedsProfile;
193
202
  /**
194
203
  * Formative delivery policy for this section: how many Tries a learner gets
195
204
  * on each item, when feedback is revealed, and whether the behavior is on at
@@ -232,11 +241,9 @@ export interface TestPart {
232
241
  * The host-facing persistence snapshot for one section, returned by
233
242
  * `SectionControllerHandle.getSession()` and accepted by `applySession()`.
234
243
  *
235
- * Canonical here rather than in a player or the toolkit: the toolkit is
236
- * complementary to a player rather than an alternative to one and sits beneath
237
- * both entry paths, so a shape both need belongs alongside `AssessmentSection`.
238
- * The delivery slices it carries are defined in this package too, which is what
239
- * previously forced them to reference this shape in prose instead of types.
244
+ * Canonical here, beside `AssessmentSection`: both entry paths need it, and the
245
+ * toolkit complements a player and sits beneath both of them. The delivery
246
+ * slices it carries live in this package too, so they reference it as a type.
240
247
  */
241
248
  export interface SectionControllerSessionState {
242
249
  currentItemIndex?: number;
@@ -362,7 +369,7 @@ export interface MediaFragmentRange {
362
369
  * inside `qti-card-entry` — multiple sources, MIME types, poster, and an
363
370
  * optional time range.
364
371
  *
365
- * Note there is no `kind` discriminant: the card's `catalog` field is QTI's
372
+ * It carries no `kind` discriminant: the card's `catalog` field is QTI's
366
373
  * `qti-card@support` and is the only discriminator. Restating it here would
367
374
  * create a second source of truth that can disagree with the first.
368
375
  */
@@ -373,15 +380,12 @@ export interface SignLanguageCardPayload {
373
380
  * signed alternate is LSM, not ASL, so this must never be inferred from the
374
381
  * item or assessment content language.
375
382
  *
376
- * Optional, and redundant when it equals the card's `language`. The card's
377
- * `language` is QTI's `xml:lang` on the card entry and is the only field
378
- * catalog resolution selects on — it decides *which* card is returned, before
379
- * anything knows the card is a signing card. This one is read after
380
- * resolution, to name the language in the region's accessible label and to
381
- * refuse a card in a sign language the learner did not ask for. Author it only
382
- * where the two genuinely differ, which is a card tagged with the item's
383
- * content language (`language: "en-US"`, `signLang: "ase"`) so that resolution
384
- * reaches it by the default-language rung.
383
+ * Optional: when absent, the card's `language` stands in, so author it only
384
+ * where the two differ. The sign-language tool reads every signing card in
385
+ * the owner's scope and selects on this value: a card in the requested sign
386
+ * language wins, a card naming no language is the fallback, and a card in
387
+ * another sign language is never shown. It also names the language in the
388
+ * region's accessible label.
385
389
  */
386
390
  signLang?: string;
387
391
  media: MediaAssetRef;
@@ -392,8 +396,7 @@ export interface SignLanguageCardPayload {
392
396
  *
393
397
  * QTI 3 treats recorded audio and synthesized speech as the *same* support:
394
398
  * both are `spoken`, and a card holds recorded audio through `qti-file-href`
395
- * plus a MIME type. So this is not a new accommodation, it is the other form the
396
- * existing one can take.
399
+ * plus a MIME type. A recording is the other form that accommodation takes.
397
400
  *
398
401
  * A node commonly carries both this and a `content` card in the same language —
399
402
  * APIP's pattern, which QTI's migration guidance keeps, because the script is
@@ -426,15 +429,13 @@ export type CatalogCardPayload = SignLanguageCardPayload | SpokenAudioCardPayloa
426
429
  * one of `content` or `payload`.
427
430
  *
428
431
  * One generic `payload` slot, not a field per accommodation. This is the shape
429
- * `pie-elements-ng` (PIE-879) and the `pie-api-aws` Learnosity importer
430
- * (PIE-881) restate structurally; all three read the same authored JSON and none
431
- * of them takes a package dependency on the others, so keeping the declarations
432
- * identical is the only thing holding interop together. It briefly was not:
433
- * those two producers wrote the signing payload under `signLanguage`, this
434
- * repo tolerated that as an input alias, and the alias was folded in on the
435
- * resolution path but not the enumeration path — so an imported card rendered
436
- * its signing video and simultaneously reported that the item had no signed
437
- * alternate. Both producers now emit `payload` and the alias is gone.
432
+ * `pie-elements-ng` and the PIE API service's Learnosity importer restate
433
+ * structurally; all three read the same authored JSON and none of them takes a
434
+ * package dependency on the others, so keeping the declarations identical is
435
+ * the only thing holding interop together; a `signLanguage` input alias, folded
436
+ * in on the resolution path but not the enumeration path, once had an imported
437
+ * card render its signing video while reporting no signed alternate. Both
438
+ * producers now emit `payload`.
438
439
  */
439
440
  export interface CatalogCard {
440
441
  catalog: string;
@@ -466,15 +467,7 @@ export interface AccessibilityCatalog {
466
467
  export interface PersonalNeedsProfile {
467
468
  supports: string[];
468
469
  prohibitedSupports?: string[];
469
- activateAtInit?: string[];
470
470
  }
471
- /**
472
- * QTI 3.0: Reference to a shared stimulus (passage).
473
- * Maps to qti-assessment-stimulus-ref element.
474
- *
475
- * NOTE: Not currently used. We embed PassageEntity directly in RubricBlock instead.
476
- * Kept for potential future use if we need to support external stimulus references.
477
- */
478
471
  export interface AssessmentEntity extends BaseEntity, SearchMetaDataEntity {
479
472
  name?: string;
480
473
  title?: string;
@@ -494,117 +487,59 @@ export interface AssessmentEntity extends BaseEntity, SearchMetaDataEntity {
494
487
  * QTI 3.0: testParts structure (authoritative for QTI format).
495
488
  */
496
489
  testParts?: TestPart[];
497
- /** QTI 3.0: Stimulus material references (Phase 3 - placeholder) */
498
- stimulusRefs?: any[];
499
490
  }
500
491
  /**
501
- * Enhanced settings structure for assessment configuration.
502
- * Provides structured fields for district policies, test administration,
503
- * tool configurations, and theme settings while remaining extensible.
492
+ * Assessment settings the tool policy reads: district policy, test
493
+ * administration and per-tool parameters.
504
494
  */
505
495
  export interface AssessmentSettings {
506
496
  /** District/organization policies */
507
497
  districtPolicy?: {
508
498
  blockedTools?: string[];
509
499
  requiredTools?: string[];
510
- policies?: Record<string, any>;
511
500
  };
512
501
  /** Test administration configuration */
513
502
  testAdministration?: {
514
- mode?: "practice" | "test" | "benchmark";
515
- toolOverrides?: Record<string, boolean>;
516
- startDate?: string;
517
- endDate?: string;
518
- };
519
- /** Tool-specific provider configurations */
520
- toolConfigs?: {
521
- calculator?: AssessmentCalculatorConfig;
522
503
  /**
523
- * Text-to-speech configuration.
524
- *
525
- * Standard parameters (voice, rate, pitch) are portable across providers
526
- * and follow W3C Web Speech API specifications.
527
- *
528
- * Provider-specific extensions should be placed in providerOptions.
529
- *
530
- * @see https://w3c.github.io/speech-api/
504
+ * Per-session override by tool id. `false` withdraws the tool and
505
+ * outranks every policy level except a district block. `true` grants it
506
+ * below a district block, an item's `restrictedTools` and the profile's
507
+ * `prohibitedSupports`, and above requirements and profile supports.
531
508
  */
532
- textToSpeech?: {
533
- /**
534
- * TTS provider
535
- *
536
- * @standard "browser" uses W3C Web Speech API
537
- * @extension "polly" and "custom" are provider-specific
538
- */
539
- provider?: "browser" | "polly" | "custom";
540
- /**
541
- * Voice identifier (provider-specific names)
542
- *
543
- * @standard W3C Web Speech API (concept)
544
- * @example "Joanna" (Polly), "en-US-Standard-A" (Google), browser voices
545
- */
546
- voice?: string;
547
- /**
548
- * Speech rate (speed multiplier)
549
- *
550
- * @standard W3C Web Speech API
551
- * @range 0.25 to 4.0
552
- * @default 1.0
553
- */
554
- rate?: number;
555
- /**
556
- * Pitch adjustment
557
- *
558
- * @standard W3C Web Speech API
559
- * @range 0 to 2 (as multiplier)
560
- * @default 1.0
561
- */
562
- pitch?: number;
563
- /**
564
- * Speech Rule Engine options for generated MathML speech.
565
- *
566
- * `style` is passed to SRE directly; ClearSpeak combines multiple
567
- * preferences with ":" (for example,
568
- * "ImpliedTimes_MoreImpliedTimes:Paren_Silent").
569
- *
570
- * Mirrors assessment-toolkit's SREMathSpeechOptions without importing
571
- * toolkit service types into the shared content contract package.
572
- */
573
- mathSpeech?: {
574
- domain?: string;
575
- style?: string;
576
- engineOptions?: Record<string, unknown>;
577
- };
578
- /**
579
- * Provider-specific options (extensions)
580
- *
581
- * @extension Not portable across providers
582
- * @example For AWS Polly: { engine: "neural", region: "us-east-1" }
583
- * @example For Google Cloud: { audioEncoding: "MP3", effectsProfileId: ["headphone-class-device"] }
584
- */
585
- providerOptions?: Record<string, any>;
586
- };
587
- [toolId: string]: any;
588
- };
589
- /** Theme configuration (not in PNP) */
590
- themeConfig?: {
591
- scheme?: "default" | "high-contrast" | "dark";
592
- fontSize?: number;
593
- fontFamily?: string;
594
- lineHeight?: number;
595
- reducedMotion?: boolean;
509
+ toolOverrides?: Record<string, boolean>;
596
510
  };
597
- /** Product-specific extensions */
598
- [key: string]: any;
511
+ /**
512
+ * Parameters by tool id, handed to the tool wherever policy shows it; an
513
+ * item's `toolParameters` entry wins on that item's own toolbar. Provider
514
+ * configuration belongs in the toolkit's `tools.providers`.
515
+ */
516
+ toolParameters?: ToolParameters;
599
517
  }
600
518
  /**
601
- * Calculator options exposed through assessment settings.
602
- * The engine is implicit (Desmos).
519
+ * Parameter shapes by tool id. A tool outside the stock set may add its own by
520
+ * declaration merging, with a type literal as the value.
521
+ *
522
+ * The values come from authored content, so a tool still checks what it reads.
603
523
  */
604
- export interface AssessmentCalculatorConfig {
605
- type?: "basic" | "scientific" | "graphing";
606
- [key: string]: any;
524
+ export interface ToolParameterMap {
525
+ calculator: {
526
+ type?: "basic" | "scientific" | "graphing";
527
+ };
528
+ answerEliminator: {
529
+ strategy?: "strikethrough" | "mask";
530
+ };
531
+ signLanguage: {
532
+ signLang?: string;
533
+ };
607
534
  }
535
+ /** The parameter shape of tool `K`: its {@link ToolParameterMap} entry, else open. */
536
+ export type ToolParametersFor<K extends string> = K extends keyof ToolParameterMap ? ToolParameterMap[K] : Record<string, unknown>;
537
+ /** Parameters by tool id: typed for the ids {@link ToolParameterMap} names. */
538
+ export type ToolParameters = {
539
+ [K in keyof ToolParameterMap]?: ToolParameterMap[K];
540
+ } & {
541
+ [toolId: string]: Record<string, unknown> | undefined;
542
+ };
608
543
  /**
609
544
  * Item-level settings for tool requirements.
610
545
  * Used in AssessmentItemRef.settings to specify item-specific tool needs.
@@ -612,8 +547,8 @@ export interface AssessmentCalculatorConfig {
612
547
  export interface ItemSettings {
613
548
  requiredTools?: string[];
614
549
  restrictedTools?: string[];
615
- toolParameters?: Record<string, any>;
616
- [key: string]: any;
550
+ /** Parameters by tool id; outranks the assessment's `toolParameters` entry. */
551
+ toolParameters?: ToolParameters;
617
552
  }
618
553
  export type PlayerMode = "gather" | "view" | "evaluate" | "author";
619
554
  export type PlayerRole = "student" | "instructor";
@@ -711,8 +646,8 @@ export interface AdvancedItemConfig {
711
646
  }
712
647
  export type ItemConfig = PieContent | AdvancedItemConfig;
713
648
  /**
714
- * During the loading of elements from PIE bundles, we do a trick where we make
715
- * editor components available as web components by appending this to the element name.
649
+ * Appended to an element's tag name to register the bundle's editor component
650
+ * as a custom element of its own when PIE bundles load.
716
651
  */
717
652
  export declare const editorPostFix = "-pie-editor--";
718
653
  export type ModelUpdatedDetail = {
@@ -1,6 +1,6 @@
1
1
  /**
2
- * During the loading of elements from PIE bundles, we do a trick where we make
3
- * editor components available as web components by appending this to the element name.
2
+ * Appended to an element's tag name to register the bundle's editor component
3
+ * as a custom element of its own when PIE bundles load.
4
4
  */
5
5
  export const editorPostFix = "-pie-editor--";
6
6
  export class ModelUpdatedEvent extends CustomEvent {
@@ -1,3 +1,8 @@
1
+ /**
2
+ * Reads a boolean-like prop or attribute value. Nullish or blank reads as
3
+ * `defaultValue`; `false`, `0`, `off` and `no` (any case) read as false; any
4
+ * other value reads as true.
5
+ */
1
6
  export declare function coerceBooleanLike(value: boolean | string | null | undefined, defaultValue?: boolean): boolean;
2
7
  /**
3
8
  * Reads a boolean attribute value: present is true and absent is false, as in
@@ -1,3 +1,8 @@
1
+ /**
2
+ * Reads a boolean-like prop or attribute value. Nullish or blank reads as
3
+ * `defaultValue`; `false`, `0`, `off` and `no` (any case) read as false; any
4
+ * other value reads as true.
5
+ */
1
6
  export function coerceBooleanLike(value, defaultValue = false) {
2
7
  if (typeof value === "boolean") {
3
8
  return value;
@@ -12,10 +17,7 @@ export function coerceBooleanLike(value, defaultValue = false) {
12
17
  if (["false", "0", "off", "no"].includes(normalizedValue)) {
13
18
  return false;
14
19
  }
15
- if (["true", "1", "on", "yes"].includes(normalizedValue)) {
16
- return true;
17
- }
18
- return Boolean(normalizedValue);
20
+ return true;
19
21
  }
20
22
  /**
21
23
  * Reads a boolean attribute value: present is true and absent is false, as in
@@ -9,11 +9,9 @@
9
9
  * light DOM, so those rules have to exist as a document-level stylesheet —
10
10
  * there is no shadow root to scope them to.
11
11
  *
12
- * Hosts used to be required to import that stylesheet themselves. Nothing
13
- * enforced it and nothing failed loudly when they didn't: the item rendered,
14
- * the passage was simply unstyled, and it surfaced as a visual bug reported by
15
- * hand days later. Players install the stylesheet themselves instead, which
16
- * keeps the host contract at "import the player".
12
+ * Players install that stylesheet themselves, which keeps the host contract at
13
+ * "import the player"; a host-side import was unenforced and failed silently,
14
+ * as an unstyled passage.
17
15
  *
18
16
  * The CSS text is passed in rather than imported here: this package builds with
19
17
  * plain `tsc`, so it cannot inline a stylesheet. Bundler-built player packages
@@ -58,11 +56,9 @@ export declare function contentStylesPresent(): boolean;
58
56
  * hand — first in the entry, before app CSS — so an app rule and a content rule
59
57
  * of equal specificity resolve in the host's favour, exactly as before.
60
58
  *
61
- * A cascade layer looks tempting here and is the wrong tool: unlayered author
62
- * declarations beat *all* layered ones regardless of specificity, so a host
63
- * reset as broad as `p { margin: 0 }` would silently outrank
64
- * `.numbered-paragraph { margin-left: 36px }`. That trades a visible missing
65
- * stylesheet for a subtler override bug, so ordinary specificity wins instead.
59
+ * A cascade layer would invert that: unlayered author declarations beat *all*
60
+ * layered ones regardless of specificity, so a host reset as broad as
61
+ * `p { margin: 0 }` would outrank `.numbered-paragraph { margin-left: 36px }`.
66
62
  *
67
63
  * @param cssText Contents of `@pie-players/pie-theme/components.css`.
68
64
  * @param source Package installing the styles, for diagnostics.
@@ -9,11 +9,9 @@
9
9
  * light DOM, so those rules have to exist as a document-level stylesheet —
10
10
  * there is no shadow root to scope them to.
11
11
  *
12
- * Hosts used to be required to import that stylesheet themselves. Nothing
13
- * enforced it and nothing failed loudly when they didn't: the item rendered,
14
- * the passage was simply unstyled, and it surfaced as a visual bug reported by
15
- * hand days later. Players install the stylesheet themselves instead, which
16
- * keeps the host contract at "import the player".
12
+ * Players install that stylesheet themselves, which keeps the host contract at
13
+ * "import the player"; a host-side import was unenforced and failed silently,
14
+ * as an unstyled passage.
17
15
  *
18
16
  * The CSS text is passed in rather than imported here: this package builds with
19
17
  * plain `tsc`, so it cannot inline a stylesheet. Bundler-built player packages
@@ -48,9 +46,8 @@ const declaresContentStylesSentinel = (rule) => {
48
46
  // Grouping rules hold no declarations of their own, so the sentinel sits one
49
47
  // or more levels down. A host that confines its copy — `@scope
50
48
  // (.item-content) { … }`, `@layer pie-content { … }` — presents exactly one
51
- // top-level rule with an empty `.style`, and a top-level-only scan reads that
52
- // as "no copy here". That made both detection paths blind to the one host
53
- // configuration this module most needs to recognise.
49
+ // top-level rule with an empty `.style`, which a top-level-only scan reads
50
+ // as "no copy here".
54
51
  const nested = rule.cssRules;
55
52
  if (!nested)
56
53
  return false;
@@ -115,11 +112,9 @@ export function contentStylesPresent() {
115
112
  * hand — first in the entry, before app CSS — so an app rule and a content rule
116
113
  * of equal specificity resolve in the host's favour, exactly as before.
117
114
  *
118
- * A cascade layer looks tempting here and is the wrong tool: unlayered author
119
- * declarations beat *all* layered ones regardless of specificity, so a host
120
- * reset as broad as `p { margin: 0 }` would silently outrank
121
- * `.numbered-paragraph { margin-left: 36px }`. That trades a visible missing
122
- * stylesheet for a subtler override bug, so ordinary specificity wins instead.
115
+ * A cascade layer would invert that: unlayered author declarations beat *all*
116
+ * layered ones regardless of specificity, so a host reset as broad as
117
+ * `p { margin: 0 }` would outrank `.numbered-paragraph { margin-left: 36px }`.
123
118
  *
124
119
  * @param cssText Contents of `@pie-players/pie-theme/components.css`.
125
120
  * @param source Package installing the styles, for diagnostics.
@@ -1,6 +1,5 @@
1
1
  /**
2
2
  * Selector for elements that are commonly keyboard-focusable in assessment UI.
3
- * Kept in sync with `focus-trap.ts` for consistent tab-order surfaces.
4
3
  */
5
4
  export declare const FOCUSABLE_SELECTOR: string;
6
5
  /**
@@ -1,6 +1,5 @@
1
1
  /**
2
2
  * Selector for elements that are commonly keyboard-focusable in assessment UI.
3
- * Kept in sync with `focus-trap.ts` for consistent tab-order surfaces.
4
3
  */
5
4
  export const FOCUSABLE_SELECTOR = [
6
5
  "a[href]",
@@ -57,9 +57,8 @@ export function createFocusTrap(container, options = {}) {
57
57
  container.focus?.();
58
58
  return;
59
59
  }
60
- const current = getDeepActiveElement(container.getRootNode() instanceof ShadowRoot
61
- ? container.getRootNode()
62
- : document);
60
+ const rootNode = container.getRootNode();
61
+ const current = getDeepActiveElement(rootNode instanceof ShadowRoot ? rootNode : document);
63
62
  const currentIndex = focusable.indexOf(current || focusable[0]);
64
63
  if (event.shiftKey) {
65
64
  if (currentIndex <= 0) {
@@ -4,37 +4,34 @@
4
4
  * external-style-urls="...">` or `itemConfig.resources.stylesheets[*].url`
5
5
  * styles that player's subtree instead of the whole host document.
6
6
  *
7
- * This replaces a single-regex implementation that prefixed every
8
- * selector-like fragment. That is correct for flat selector rules and wrong for
9
- * everything else: `@media screen { ... }` became
10
- * `.scope @media screen { ... }`, an invalid selector, so the browser dropped
11
- * the whole block and every rule inside it; `@font-face` and `@keyframes` were
12
- * corrupted the same way; and `:root` became `.scope :root`, which can never
13
- * match, because `:root` is `<html>` and is not a descendant of the player.
14
- * At-rules and `:root` custom properties therefore never applied at all.
7
+ * Walks the stylesheet brace-by-brace. Block at-rules that hold rules
8
+ * (`@media`, `@supports`, `@container`, `@layer`, `@scope`) keep their prelude
9
+ * and have their inner rules scoped, every other at-rule passes through, and a
10
+ * leading `:root`, `html` or `body` is replaced by the scope, since
11
+ * `.scope :root` can never match: `:root` is `<html>`, which is no descendant
12
+ * of the player. A regex prefixer fails on exactly these cases: it turned
13
+ * `@media screen { … }` into the invalid `.scope @media screen { … }`, which the
14
+ * browser drops with every rule inside, and corrupted `@font-face`,
15
+ * `@keyframes` and `:root` the same way.
15
16
  *
16
- * The QTI player's `scopeCssRules` (`pie-qti` -
17
- * `packages/item-player/src/components/utils/stylesheetRender.ts`) is not a
18
- * drop-in either. Its `:root` handling is the behaviour we want and is adopted
19
- * here, but it excludes `@` from its selector pattern rather than understanding
20
- * at-rules, so `@media` and `@supports` lose their condition and their inner
21
- * rules are hoisted and applied unconditionally — a mobile-only rule then
22
- * applies at every viewport width, which is a subtler failure than dropping it.
17
+ * The `:root` handling follows the QTI player's `scopeCssRules` (`pie-qti` -
18
+ * `packages/item-player/src/components/utils/stylesheetRender.ts`), which is no
19
+ * drop-in: it excludes `@` from its selector pattern without understanding
20
+ * at-rules, so `@media` and `@supports` lose their condition and their rules
21
+ * apply at every viewport width.
23
22
  *
24
- * So this walks the stylesheet brace-by-brace instead of pattern-matching it.
25
- * A real CSS parser would be the textbook answer and is deliberately not used:
26
- * `@pie-players/pie-item-player` ships with two workspace dependencies, this
27
- * runs in the delivery path, and scoping needs to know about rule boundaries
28
- * only — not about property grammar.
23
+ * No CSS parser: `@pie-players/pie-item-player` ships with two workspace
24
+ * dependencies, this runs in the delivery path, and scoping needs rule
25
+ * boundaries only.
29
26
  *
30
- * Known limits, all preserving existing behaviour rather than adding policy:
27
+ * Known limits:
31
28
  *
32
- * - `@import` is passed through untouched, exactly as the old regex left it. It
33
- * pulls in an unscoped stylesheet, so it defeats scoping, but blocking it is
34
- * a security policy decision and not this function's job. Callers gate
35
- * stylesheet URLs with `validateExternalStyleUrl` before fetching.
29
+ * - `@import` is passed through untouched. It pulls in an unscoped stylesheet,
30
+ * so it defeats scoping, but blocking it is a security policy decision and
31
+ * not this function's job. Callers gate stylesheet URLs with
32
+ * `validateExternalStyleUrl` before fetching.
36
33
  * - Declaration blocks are emitted verbatim, so `url(...)` references are left
37
- * as authored and resolve against the stylesheet's own URL as before.
34
+ * as authored and resolve against the stylesheet's own URL.
38
35
  * - A style rule's block is never rewritten, which is also what native CSS
39
36
  * nesting needs: nested selectors are relative to a parent that has already
40
37
  * been scoped.