@pie-players/pie-players-shared 0.3.73 → 0.3.75

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 (137) hide show
  1. package/README.md +30 -2
  2. package/dist/components/vendor/nds/README.md +7 -5
  3. package/dist/config/profile.d.ts +0 -2
  4. package/dist/config/profile.js +2 -8
  5. package/dist/i18n/index.d.ts +2 -3
  6. package/dist/i18n/index.js +2 -3
  7. package/dist/i18n/messages/en-US.d.ts +11 -24
  8. package/dist/i18n/messages/en-US.js +13 -26
  9. package/dist/i18n/messages/nl-NL.d.ts +7 -24
  10. package/dist/i18n/messages/nl-NL.js +9 -26
  11. package/dist/i18n/provider.d.ts +4 -5
  12. package/dist/i18n/provider.js +4 -5
  13. package/dist/i18n/types.d.ts +4 -5
  14. package/dist/index.d.ts +4 -2
  15. package/dist/index.js +3 -1
  16. package/dist/instrumentation/index.d.ts +5 -4
  17. package/dist/instrumentation/index.js +5 -4
  18. package/dist/instrumentation/new-relic-agent.d.ts +20 -0
  19. package/dist/instrumentation/new-relic-agent.js +24 -0
  20. package/dist/instrumentation/providers/BaseInstrumentationProvider.d.ts +5 -4
  21. package/dist/instrumentation/providers/NewRelicInstrumentationProvider.d.ts +7 -7
  22. package/dist/instrumentation/providers/NewRelicInstrumentationProvider.js +21 -32
  23. package/dist/instrumentation/types.d.ts +7 -3
  24. package/dist/loader-config.d.ts +16 -6
  25. package/dist/loaders/controller-loading.d.ts +14 -0
  26. package/dist/loaders/controller-loading.js +12 -0
  27. package/dist/loaders/defaults.d.ts +10 -0
  28. package/dist/loaders/defaults.js +10 -0
  29. package/dist/loaders/element-loader.d.ts +20 -8
  30. package/dist/loaders/element-loader.js +102 -36
  31. package/dist/loaders/element-package-policy.d.ts +2 -0
  32. package/dist/loaders/element-package-policy.js +2 -1
  33. package/dist/loaders/esm-adapter.d.ts +29 -0
  34. package/dist/loaders/esm-adapter.js +635 -102
  35. package/dist/loaders/iife-adapter.d.ts +0 -5
  36. package/dist/loaders/iife-adapter.js +3 -9
  37. package/dist/loaders/index.d.ts +12 -3
  38. package/dist/loaders/index.js +9 -3
  39. package/dist/loaders/mathjax-events.d.ts +22 -0
  40. package/dist/loaders/mathjax-events.js +65 -0
  41. package/dist/loaders/module-shim.d.ts +12 -0
  42. package/dist/loaders/module-shim.js +12 -0
  43. package/dist/loaders/preloaded-alignment.d.ts +24 -0
  44. package/dist/loaders/preloaded-alignment.js +57 -0
  45. package/dist/loaders/preloaded-authored-tags.d.ts +26 -0
  46. package/dist/loaders/preloaded-authored-tags.js +69 -0
  47. package/dist/loaders/preloaded-registration.d.ts +72 -0
  48. package/dist/loaders/preloaded-registration.js +141 -0
  49. package/dist/media/index.d.ts +69 -0
  50. package/dist/media/index.js +194 -0
  51. package/dist/pie/authoring.d.ts +1 -1
  52. package/dist/pie/authoring.js +25 -18
  53. package/dist/pie/element-announcements.d.ts +39 -0
  54. package/dist/pie/element-announcements.js +67 -0
  55. package/dist/pie/element-observer.d.ts +3 -1
  56. package/dist/pie/element-observer.js +1 -0
  57. package/dist/pie/elements-rendered.d.ts +25 -0
  58. package/dist/pie/elements-rendered.js +96 -0
  59. package/dist/pie/index.d.ts +8 -10
  60. package/dist/pie/index.js +9 -10
  61. package/dist/pie/initialization.js +13 -22
  62. package/dist/pie/initialize-element.d.ts +3 -0
  63. package/dist/pie/initialize-element.js +3 -4
  64. package/dist/pie/instrumentation-event-bridge.js +23 -1
  65. package/dist/pie/instrumentation-event-map.js +11 -25
  66. package/dist/pie/instrumentation-provider-resolution.d.ts +2 -1
  67. package/dist/pie/instrumentation-provider-resolution.js +0 -1
  68. package/dist/pie/item-controller.js +4 -0
  69. package/dist/pie/item-session-contract.d.ts +8 -0
  70. package/dist/pie/item-session-contract.js +30 -9
  71. package/dist/pie/logger.d.ts +6 -0
  72. package/dist/pie/logger.js +10 -0
  73. package/dist/pie/markup-math.d.ts +20 -0
  74. package/dist/pie/markup-math.js +118 -0
  75. package/dist/pie/math-assets.d.ts +48 -0
  76. package/dist/pie/math-assets.js +81 -0
  77. package/dist/pie/math-rendering.d.ts +10 -21
  78. package/dist/pie/math-rendering.js +50 -35
  79. package/dist/pie/registry.d.ts +13 -1
  80. package/dist/pie/registry.js +39 -1
  81. package/dist/pie/resource-monitor.d.ts +6 -33
  82. package/dist/pie/resource-monitor.js +14 -213
  83. package/dist/pie/scoring.d.ts +12 -8
  84. package/dist/pie/scoring.js +21 -24
  85. package/dist/pie/session-commit.d.ts +16 -0
  86. package/dist/pie/session-commit.js +34 -6
  87. package/dist/pie/session-snapshot.d.ts +80 -0
  88. package/dist/pie/session-snapshot.js +166 -0
  89. package/dist/pie/stages.d.ts +10 -47
  90. package/dist/pie/stages.js +10 -47
  91. package/dist/pie/tag-names.d.ts +1 -0
  92. package/dist/pie/tag-names.js +1 -0
  93. package/dist/pie/types.d.ts +4 -5
  94. package/dist/pie/types.js +1 -0
  95. package/dist/pie/updates.d.ts +2 -2
  96. package/dist/pie/updates.js +33 -15
  97. package/dist/pie/versioned-tag.d.ts +4 -0
  98. package/dist/pie/versioned-tag.js +4 -0
  99. package/dist/security/index.d.ts +2 -3
  100. package/dist/security/index.js +2 -3
  101. package/dist/security/sanitize-item-markup.d.ts +4 -2
  102. package/dist/security/sanitize-item-markup.js +48 -6
  103. package/dist/security/sanitize-svg-icon.d.ts +0 -2
  104. package/dist/security/sanitize-svg-icon.js +0 -5
  105. package/dist/security/wrap-overwide-images.js +19 -0
  106. package/dist/security/wrap-overwide.d.ts +13 -0
  107. package/dist/security/wrap-overwide.js +63 -3
  108. package/dist/timed-media/policy.js +5 -16
  109. package/dist/types/index.d.ts +67 -423
  110. package/dist/types/index.js +0 -68
  111. package/dist/ui/attribute-coercion.d.ts +12 -0
  112. package/dist/ui/attribute-coercion.js +33 -0
  113. package/dist/ui/content-styles.d.ts +18 -16
  114. package/dist/ui/content-styles.js +67 -36
  115. package/dist/ui/overlay-containment.d.ts +31 -0
  116. package/dist/ui/overlay-containment.js +63 -0
  117. package/dist/ui/overlay-placement.d.ts +110 -0
  118. package/dist/ui/overlay-placement.js +222 -0
  119. package/dist/ui/pointer-gesture.d.ts +32 -0
  120. package/dist/ui/pointer-gesture.js +56 -0
  121. package/dist/ui/pointer-rotate.d.ts +29 -0
  122. package/dist/ui/pointer-rotate.js +39 -0
  123. package/package.json +23 -18
  124. package/dist/pie/component-context.d.ts +0 -21
  125. package/dist/pie/component-context.js +0 -29
  126. package/dist/pie/player-initializer.d.ts +0 -54
  127. package/dist/pie/player-initializer.js +0 -122
  128. package/dist/pie/stage-tracker.d.ts +0 -50
  129. package/dist/pie/stage-tracker.js +0 -105
  130. package/dist/security/wrap-model-rich-content.d.ts +0 -1
  131. package/dist/security/wrap-model-rich-content.js +0 -40
  132. package/dist/server/npm-auth-env.d.ts +0 -13
  133. package/dist/server/npm-auth-env.js +0 -64
  134. package/dist/server/npm-registry.d.ts +0 -7
  135. package/dist/server/npm-registry.js +0 -59
  136. package/dist/ui/debug-panel-persistence.d.ts +0 -17
  137. package/dist/ui/debug-panel-persistence.js +0 -37
@@ -7,6 +7,7 @@
7
7
  * or supply their own sanitizer function if they need a stricter / looser
8
8
  * allow-list.
9
9
  */
10
+ import { markAuthoredColors } from "@pie-element/shared-utils";
10
11
  import DOMPurify from "dompurify";
11
12
  import { SANITIZER_FORBIDDEN_ATTRS, SANITIZER_FORBIDDEN_TAGS, } from "./sanitize-forbidden-lists.js";
12
13
  import { installStyleAttributeHook } from "./sanitize-style-attribute.js";
@@ -29,6 +30,40 @@ const BASE_ALLOWED_ATTRS = [
29
30
  "lang",
30
31
  "dir",
31
32
  ];
33
+ // MathML authored content uses that DOMPurify drops: elementary math, which
34
+ // `@pie-element/shared-math-rendering-mathjax` rewrites as a table before
35
+ // MathJax reads it, and `semantics`, `annotation`, `mprescripts` and `none`.
36
+ // `annotation-xml` stays out because it is an HTML integration point.
37
+ const MATHML_TAGS = new Set([
38
+ "mstack",
39
+ "mlongdiv",
40
+ "msgroup",
41
+ "msrow",
42
+ "msline",
43
+ "mscarries",
44
+ "mscarry",
45
+ "semantics",
46
+ "annotation",
47
+ "mprescripts",
48
+ "none",
49
+ ]);
50
+ const MATHML_ATTRS = [
51
+ "stackalign",
52
+ "charalign",
53
+ "charspacing",
54
+ "longdivstyle",
55
+ "position",
56
+ "shift",
57
+ "location",
58
+ "crossout",
59
+ "leftoverhang",
60
+ "rightoverhang",
61
+ "mslinethickness",
62
+ "linebreak",
63
+ ];
64
+ // Prefixed MathML such as `<m:math>`, which the HTML parser reads as unknown
65
+ // HTML elements and the math adapter re-creates as MathML.
66
+ const PREFIXED_TAG = /^[a-z_][\w.-]*:[a-z][\w.-]*$/;
32
67
  const BASE_URI_SAFE_ATTRS = ["pie-id"];
33
68
  const FORBIDDEN_TAGS = SANITIZER_FORBIDDEN_TAGS;
34
69
  const FORBIDDEN_ATTRS = SANITIZER_FORBIDDEN_ATTRS;
@@ -57,6 +92,10 @@ function resolvePurifier() {
57
92
  // `style` is URI-safe to DOMPurify, so nothing inside it is inspected
58
93
  // without this. See sanitize-style-attribute.ts.
59
94
  installStyleAttributeHook(purifierInstance);
95
+ // After the style filter, so only declarations that survive it are marked.
96
+ // The markers are the contract a color scheme's CSS overrides authored
97
+ // colors through, shared with element model HTML. (PIE-1119)
98
+ purifierInstance.addHook?.("afterSanitizeAttributes", (node) => markAuthoredColors(node));
60
99
  return purifierInstance;
61
100
  }
62
101
  /**
@@ -67,6 +106,10 @@ function resolvePurifier() {
67
106
  * `form`, `meta`, `link`).
68
107
  * - Preserves PIE custom elements (`pie-*`) and any extra tags listed in
69
108
  * `allowedCustomElements`.
109
+ * - Marks elements that carry an authored color, as `markAuthoredColors`
110
+ * in `@pie-element/shared-utils` describes, so a color scheme can override
111
+ * it. A host that opts out with `trust-markup` or supplies its own
112
+ * sanitizer gets no markers.
70
113
  * - During SSR (no `window`) returns an empty string so untrusted markup
71
114
  * never reaches the prerender output; the live renderer will re-run the
72
115
  * sanitizer on hydrate.
@@ -80,8 +123,11 @@ export function sanitizeItemMarkup(markup, options = {}) {
80
123
  const allowedCustomElements = (options.allowedCustomElements ?? []).map((name) => name.toLowerCase());
81
124
  const explicitCustomElementSet = new Set(allowedCustomElements);
82
125
  const result = purifier.sanitize(markup, {
83
- ADD_TAGS: allowedCustomElements,
84
- ADD_ATTR: BASE_ALLOWED_ATTRS,
126
+ // DOMPurify still removes a tag in FORBID_TAGS that this accepts.
127
+ ADD_TAGS: (tagName) => explicitCustomElementSet.has(tagName) ||
128
+ MATHML_TAGS.has(tagName) ||
129
+ PREFIXED_TAG.test(tagName),
130
+ ADD_ATTR: [...BASE_ALLOWED_ATTRS, ...MATHML_ATTRS],
85
131
  ADD_URI_SAFE_ATTR: BASE_URI_SAFE_ATTRS,
86
132
  FORBID_TAGS: FORBIDDEN_TAGS,
87
133
  FORBID_ATTR: FORBIDDEN_ATTRS,
@@ -144,7 +190,3 @@ export function buildAuthoringAllowList(elementTagNames) {
144
190
  }
145
191
  return [...out];
146
192
  }
147
- /** Reset the memoised DOMPurify instance. Only intended for tests. */
148
- export function resetPurifierForTesting() {
149
- purifierInstance = null;
150
- }
@@ -10,5 +10,3 @@
10
10
  * or the input is not a string.
11
11
  */
12
12
  export declare function sanitizeSvgIcon(icon: unknown): string;
13
- /** Reset sanitizer state. Intended for tests. */
14
- export declare function resetSvgSanitizerForTesting(): void;
@@ -61,8 +61,3 @@ export function sanitizeSvgIcon(icon) {
61
61
  stringCache.set(icon, str);
62
62
  return str;
63
63
  }
64
- /** Reset sanitizer state. Intended for tests. */
65
- export function resetSvgSanitizerForTesting() {
66
- svgPurifierInstance = null;
67
- stringCache.clear();
68
- }
@@ -32,12 +32,31 @@ function buildAriaLabel(image) {
32
32
  const trimmed = alt ? alt.trim() : "";
33
33
  return trimmed ? `Scrollable image: ${trimmed}` : "Scrollable image";
34
34
  }
35
+ /**
36
+ * A block-level image — an element's own `display: block`, a flex item — gets a
37
+ * block wrapper, so wrapping it adds no line box below it — a host reset of
38
+ * `img { display: block }` included. The live pass reads the computed display;
39
+ * markup parsed off-document has only the inline style, so the live pass reads
40
+ * a wrapper built there again.
41
+ */
42
+ function imageLayoutClass(image) {
43
+ const view = image.ownerDocument?.defaultView;
44
+ const display = view
45
+ ? view.getComputedStyle(image).display
46
+ : image.style?.display;
47
+ const blockLevel = !!display &&
48
+ !display.startsWith("inline") &&
49
+ display !== "none" &&
50
+ display !== "contents";
51
+ return blockLevel ? "pie-image-scroll-block" : null;
52
+ }
35
53
  const IMAGE_SPEC = {
36
54
  selector: "img",
37
55
  wrapperTag: "span",
38
56
  wrapperClass: "pie-image-scroll",
39
57
  markupProbe: /<img\b/i,
40
58
  buildAriaLabel,
59
+ layoutClass: imageLayoutClass,
41
60
  };
42
61
  /**
43
62
  * Wrap every unwrapped `<img>` descendant of `root` with the shared
@@ -14,6 +14,11 @@
14
14
  * the real divergence — an image has `alt`, a table has `<caption>`,
15
15
  * `aria-label` and `aria-labelledby` — which is why it is a function rather than
16
16
  * a template.
17
+ *
18
+ * Content inside an editing host is never wrapped, on either pass. The editor
19
+ * owns that DOM: ProseMirror reads the wrapper as a foreign mutation and redraws
20
+ * the node without it, so an observer-driven wrap and the editor undo each other
21
+ * for as long as the editor holds an image.
17
22
  */
18
23
  /** What one content kind needs in order to be wrapped. */
19
24
  export interface OverwideWrapSpec {
@@ -27,6 +32,11 @@ export interface OverwideWrapSpec {
27
32
  markupProbe: RegExp;
28
33
  /** The wrapper's accessible name for one node. */
29
34
  buildAriaLabel: (node: Element) => string;
35
+ /**
36
+ * An extra wrapper class carrying the node's own layout, or `null` for none.
37
+ * Absent when every wrapper lays out alike.
38
+ */
39
+ layoutClass?: (node: Element) => string | null;
30
40
  }
31
41
  export interface WrapOverwideOptions {
32
42
  /**
@@ -40,6 +50,9 @@ export interface WrapOverwideOptions {
40
50
  /**
41
51
  * Wrap every unwrapped match under `root`. Returns the number newly wrapped so
42
52
  * callers can short-circuit when nothing changed. Idempotent.
53
+ *
54
+ * Under a connected `root`, a wrapper whose layout was never read live gains
55
+ * the layout class this pass would have given it.
43
56
  */
44
57
  export declare function wrapOverwideInElement(root: Element, spec: OverwideWrapSpec, options?: WrapOverwideOptions): number;
45
58
  /**
@@ -14,8 +14,22 @@
14
14
  * the real divergence — an image has `alt`, a table has `<caption>`,
15
15
  * `aria-label` and `aria-labelledby` — which is why it is a function rather than
16
16
  * a template.
17
+ *
18
+ * Content inside an editing host is never wrapped, on either pass. The editor
19
+ * owns that DOM: ProseMirror reads the wrapper as a foreign mutation and redraws
20
+ * the node without it, so an observer-driven wrap and the editor undo each other
21
+ * for as long as the editor holds an image.
17
22
  */
18
23
  const PIE_CUSTOM_ELEMENT_TAG_REGEX = /^pie-/i;
24
+ /**
25
+ * An editing host: `contenteditable` in any state but `false`. A
26
+ * `contenteditable="false"` island inside a host still belongs to the host's
27
+ * editor — ProseMirror marks every leaf node that way, images included — so the
28
+ * test looks past such islands to the nearest host. A read-only editor root
29
+ * (`contenteditable="false"`) is no host; ProseMirror registers no DOM mutations
30
+ * while its view is not editable.
31
+ */
32
+ const EDITING_HOST_SELECTOR = '[contenteditable]:not([contenteditable="false" i])';
19
33
  function isInsidePieCustomElement(node, root) {
20
34
  let ancestor = node.parentElement;
21
35
  while (ancestor && ancestor !== root) {
@@ -26,9 +40,18 @@ function isInsidePieCustomElement(node, root) {
26
40
  }
27
41
  return false;
28
42
  }
43
+ /**
44
+ * Wrappers whose layout class was read from the live cascade. A wrapper built
45
+ * off-document saw only its node's inline style, so the first live pass to meet
46
+ * it reads the layout again; this set keeps that to once per wrapper.
47
+ */
48
+ const liveLayoutRead = new WeakSet();
29
49
  /**
30
50
  * Wrap every unwrapped match under `root`. Returns the number newly wrapped so
31
51
  * callers can short-circuit when nothing changed. Idempotent.
52
+ *
53
+ * Under a connected `root`, a wrapper whose layout was never read live gains
54
+ * the layout class this pass would have given it.
32
55
  */
33
56
  export function wrapOverwideInElement(root, spec, options = {}) {
34
57
  const { skipPieDescendants = false } = options;
@@ -38,22 +61,59 @@ export function wrapOverwideInElement(root, spec, options = {}) {
38
61
  const ownerDocument = root.ownerDocument;
39
62
  if (!ownerDocument)
40
63
  return 0;
41
- let wrapped = 0;
64
+ const readsLiveLayout = !!spec.layoutClass && !!ownerDocument.defaultView && root.isConnected;
65
+ const targets = [];
66
+ const unread = [];
42
67
  for (const node of nodes) {
43
68
  const parent = node.parentElement;
44
69
  if (!parent)
45
70
  continue;
46
71
  // Idempotency — already wrapped.
47
- if (parent.classList?.contains(spec.wrapperClass))
72
+ const wrapped = parent.classList?.contains(spec.wrapperClass);
73
+ if (wrapped && (!readsLiveLayout || liveLayoutRead.has(parent)))
48
74
  continue;
49
75
  // Authored-markup pass: leave PIE custom-element internals alone.
50
76
  if (skipPieDescendants && isInsidePieCustomElement(node, root))
51
77
  continue;
78
+ if (node.closest(EDITING_HOST_SELECTOR))
79
+ continue;
80
+ if (!wrapped)
81
+ targets.push(node);
82
+ else if (!unread.some((other) => other.parentElement === parent))
83
+ unread.push(node);
84
+ }
85
+ // Layout is read for every node before the first insertion: a read after a
86
+ // wrap forces a fresh style resolution, one per node. A wrapper's own styles
87
+ // override its node's layout — the theme makes every wrapped image a block —
88
+ // so an existing wrapper is read with its classes lifted.
89
+ const unreadWrappers = unread.map((node) => node.parentElement);
90
+ const liftedClasses = unreadWrappers.map((wrapper) => wrapper.className);
91
+ for (const wrapper of unreadWrappers)
92
+ wrapper.className = "";
93
+ const layoutClasses = targets.map((node) => spec.layoutClass?.(node) ?? null);
94
+ const unreadLayoutClasses = unread.map((node) => spec.layoutClass?.(node) ?? null);
95
+ for (const [index, wrapper] of unreadWrappers.entries()) {
96
+ wrapper.className = liftedClasses[index];
97
+ const layoutClass = unreadLayoutClasses[index];
98
+ if (layoutClass)
99
+ wrapper.classList.add(layoutClass);
100
+ liveLayoutRead.add(wrapper);
101
+ }
102
+ let wrapped = 0;
103
+ for (const [index, node] of targets.entries()) {
104
+ const parent = node.parentElement;
105
+ if (!parent)
106
+ continue;
52
107
  const wrapper = ownerDocument.createElement(spec.wrapperTag);
53
- wrapper.className = spec.wrapperClass;
108
+ const layoutClass = layoutClasses[index];
109
+ wrapper.className = layoutClass
110
+ ? `${spec.wrapperClass} ${layoutClass}`
111
+ : spec.wrapperClass;
54
112
  wrapper.setAttribute("tabindex", "0");
55
113
  wrapper.setAttribute("role", "region");
56
114
  wrapper.setAttribute("aria-label", spec.buildAriaLabel(node));
115
+ if (readsLiveLayout)
116
+ liveLayoutRead.add(wrapper);
57
117
  parent.insertBefore(wrapper, node);
58
118
  wrapper.appendChild(node);
59
119
  wrapped += 1;
@@ -6,6 +6,7 @@
6
6
  * reports them as a framework error and delivers as an ordinary section, which is
7
7
  * loud without costing the learner the content.
8
8
  */
9
+ import { normalizeMediaFragment } from "../media/index.js";
9
10
  /**
10
11
  * Applied when `playbackPolicy` is absent entirely. The restrictive values are
11
12
  * the defaults because a timed-media section is authored to sequence: a section
@@ -43,21 +44,6 @@ function isCorrectnessCondition(condition) {
43
44
  function trimmed(value) {
44
45
  return typeof value === "string" ? value.trim() : "";
45
46
  }
46
- function normalizeRange(value) {
47
- if (!value || typeof value !== "object")
48
- return null;
49
- const raw = value;
50
- const start = Number(raw.startSeconds);
51
- if (!Number.isFinite(start) || start < 0)
52
- return null;
53
- const end = Number(raw.endSeconds);
54
- // An end at or before the start is a window that can never contain a
55
- // position, so it is treated as no end — the same rule
56
- // `normalizeMediaFragment` applies in the toolkit, kept identical on purpose.
57
- if (!Number.isFinite(end) || end <= start)
58
- return { startSeconds: start };
59
- return { startSeconds: start, endSeconds: end };
60
- }
61
47
  function normalizePlaybackPolicy(value) {
62
48
  if (value === undefined || value === null) {
63
49
  return { policy: { ...TIMED_MEDIA_PLAYBACK_POLICY_DEFAULTS }, error: null };
@@ -179,7 +165,10 @@ export function normalizeTimedMediaSectionData(args) {
179
165
  continue;
180
166
  }
181
167
  seenIdentifiers.add(identifier);
182
- const range = normalizeRange(cue.range);
168
+ // The same rule as every other authored media range: an end at or before
169
+ // the start is a window that can never contain a position, so it is
170
+ // treated as no end.
171
+ const range = normalizeMediaFragment(cue.range);
183
172
  if (!range) {
184
173
  errors.push({
185
174
  code: "invalid-cue-range",