@longsightgroup/qti3-player 0.4.0 → 0.5.4

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 (291) hide show
  1. package/README.md +146 -13
  2. package/dist/content/content-dom.d.ts +11 -0
  3. package/dist/content/content-dom.d.ts.map +1 -0
  4. package/dist/content/content-dom.js +262 -0
  5. package/dist/content/content-dom.js.map +1 -0
  6. package/dist/content/content-renderer.d.ts +17 -0
  7. package/dist/content/content-renderer.d.ts.map +1 -0
  8. package/dist/content/content-renderer.js +82 -0
  9. package/dist/content/content-renderer.js.map +1 -0
  10. package/dist/controls/remove-button.d.ts +3 -0
  11. package/dist/controls/remove-button.d.ts.map +1 -0
  12. package/dist/controls/remove-button.js +12 -0
  13. package/dist/controls/remove-button.js.map +1 -0
  14. package/dist/index.d.ts +13 -2
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +6 -0
  17. package/dist/index.js.map +1 -1
  18. package/dist/interactions/choice-interaction.d.ts +4 -0
  19. package/dist/interactions/choice-interaction.d.ts.map +1 -0
  20. package/dist/interactions/choice-interaction.js +81 -0
  21. package/dist/interactions/choice-interaction.js.map +1 -0
  22. package/dist/interactions/drawing-interaction.d.ts +6 -0
  23. package/dist/interactions/drawing-interaction.d.ts.map +1 -0
  24. package/dist/interactions/drawing-interaction.js +401 -0
  25. package/dist/interactions/drawing-interaction.js.map +1 -0
  26. package/dist/interactions/end-attempt-interaction.d.ts +4 -0
  27. package/dist/interactions/end-attempt-interaction.d.ts.map +1 -0
  28. package/dist/interactions/end-attempt-interaction.js +13 -0
  29. package/dist/interactions/end-attempt-interaction.js.map +1 -0
  30. package/dist/interactions/gap-match-interaction.d.ts +4 -0
  31. package/dist/interactions/gap-match-interaction.d.ts.map +1 -0
  32. package/dist/interactions/gap-match-interaction.js +277 -0
  33. package/dist/interactions/gap-match-interaction.js.map +1 -0
  34. package/dist/interactions/graphic-associate-interaction.d.ts +4 -0
  35. package/dist/interactions/graphic-associate-interaction.d.ts.map +1 -0
  36. package/dist/interactions/graphic-associate-interaction.js +297 -0
  37. package/dist/interactions/graphic-associate-interaction.js.map +1 -0
  38. package/dist/interactions/graphic-context.d.ts +3 -0
  39. package/dist/interactions/graphic-context.d.ts.map +1 -0
  40. package/dist/interactions/graphic-context.js +35 -0
  41. package/dist/interactions/graphic-context.js.map +1 -0
  42. package/dist/interactions/hotspot-interaction.d.ts +4 -0
  43. package/dist/interactions/hotspot-interaction.d.ts.map +1 -0
  44. package/dist/interactions/hotspot-interaction.js +68 -0
  45. package/dist/interactions/hotspot-interaction.js.map +1 -0
  46. package/dist/interactions/hottext-interaction.d.ts +3 -0
  47. package/dist/interactions/hottext-interaction.d.ts.map +1 -0
  48. package/dist/interactions/hottext-interaction.js +66 -0
  49. package/dist/interactions/hottext-interaction.js.map +1 -0
  50. package/dist/interactions/inline-choice-interaction.d.ts +4 -0
  51. package/dist/interactions/inline-choice-interaction.d.ts.map +1 -0
  52. package/dist/interactions/inline-choice-interaction.js +31 -0
  53. package/dist/interactions/inline-choice-interaction.js.map +1 -0
  54. package/dist/interactions/inline-controls.d.ts +6 -0
  55. package/dist/interactions/inline-controls.d.ts.map +1 -0
  56. package/dist/interactions/inline-controls.js +15 -0
  57. package/dist/interactions/inline-controls.js.map +1 -0
  58. package/dist/interactions/interaction-diagnostics.d.ts +7 -0
  59. package/dist/interactions/interaction-diagnostics.d.ts.map +1 -0
  60. package/dist/interactions/interaction-diagnostics.js +137 -0
  61. package/dist/interactions/interaction-diagnostics.js.map +1 -0
  62. package/dist/interactions/interaction-dispatch.d.ts +2 -0
  63. package/dist/interactions/interaction-dispatch.d.ts.map +1 -0
  64. package/dist/interactions/interaction-dispatch.js +2 -0
  65. package/dist/interactions/interaction-dispatch.js.map +1 -0
  66. package/dist/interactions/interaction-label.d.ts +4 -0
  67. package/dist/interactions/interaction-label.d.ts.map +1 -0
  68. package/dist/interactions/interaction-label.js +8 -0
  69. package/dist/interactions/interaction-label.js.map +1 -0
  70. package/dist/interactions/interaction-registry.d.ts +24 -0
  71. package/dist/interactions/interaction-registry.d.ts.map +1 -0
  72. package/dist/interactions/interaction-registry.js +138 -0
  73. package/dist/interactions/interaction-registry.js.map +1 -0
  74. package/dist/interactions/match-interaction.d.ts +4 -0
  75. package/dist/interactions/match-interaction.d.ts.map +1 -0
  76. package/dist/interactions/match-interaction.js +188 -0
  77. package/dist/interactions/match-interaction.js.map +1 -0
  78. package/dist/interactions/object-asset.d.ts +8 -0
  79. package/dist/interactions/object-asset.d.ts.map +1 -0
  80. package/dist/interactions/object-asset.js +182 -0
  81. package/dist/interactions/object-asset.js.map +1 -0
  82. package/dist/interactions/pair-interaction.d.ts +4 -0
  83. package/dist/interactions/pair-interaction.d.ts.map +1 -0
  84. package/dist/interactions/pair-interaction.js +125 -0
  85. package/dist/interactions/pair-interaction.js.map +1 -0
  86. package/dist/interactions/point-value.d.ts +16 -0
  87. package/dist/interactions/point-value.d.ts.map +1 -0
  88. package/dist/interactions/point-value.js +37 -0
  89. package/dist/interactions/point-value.js.map +1 -0
  90. package/dist/interactions/portable-custom-interaction.d.ts +16 -0
  91. package/dist/interactions/portable-custom-interaction.d.ts.map +1 -0
  92. package/dist/interactions/portable-custom-interaction.js +97 -0
  93. package/dist/interactions/portable-custom-interaction.js.map +1 -0
  94. package/dist/interactions/position-object-interaction.d.ts +4 -0
  95. package/dist/interactions/position-object-interaction.d.ts.map +1 -0
  96. package/dist/interactions/position-object-interaction.js +177 -0
  97. package/dist/interactions/position-object-interaction.js.map +1 -0
  98. package/dist/interactions/routing.d.ts +5 -0
  99. package/dist/interactions/routing.d.ts.map +1 -0
  100. package/dist/interactions/routing.js +24 -0
  101. package/dist/interactions/routing.js.map +1 -0
  102. package/dist/interactions/select-point-interaction.d.ts +4 -0
  103. package/dist/interactions/select-point-interaction.d.ts.map +1 -0
  104. package/dist/interactions/select-point-interaction.js +166 -0
  105. package/dist/interactions/select-point-interaction.js.map +1 -0
  106. package/dist/interactions/shared.d.ts +12 -0
  107. package/dist/interactions/shared.d.ts.map +1 -0
  108. package/dist/interactions/shared.js +65 -0
  109. package/dist/interactions/shared.js.map +1 -0
  110. package/dist/interactions/text-interaction.d.ts +6 -0
  111. package/dist/interactions/text-interaction.d.ts.map +1 -0
  112. package/dist/interactions/text-interaction.js +110 -0
  113. package/dist/interactions/text-interaction.js.map +1 -0
  114. package/dist/interactions/unsupported-interaction.d.ts +4 -0
  115. package/dist/interactions/unsupported-interaction.d.ts.map +1 -0
  116. package/dist/interactions/unsupported-interaction.js +23 -0
  117. package/dist/interactions/unsupported-interaction.js.map +1 -0
  118. package/dist/interactions/upload-interaction.d.ts +4 -0
  119. package/dist/interactions/upload-interaction.d.ts.map +1 -0
  120. package/dist/interactions/upload-interaction.js +10 -0
  121. package/dist/interactions/upload-interaction.js.map +1 -0
  122. package/dist/player/attempt-availability.d.ts +6 -0
  123. package/dist/player/attempt-availability.d.ts.map +1 -0
  124. package/dist/player/attempt-availability.js +27 -0
  125. package/dist/player/attempt-availability.js.map +1 -0
  126. package/dist/player/content-state.d.ts +19 -0
  127. package/dist/player/content-state.d.ts.map +1 -0
  128. package/dist/player/content-state.js +39 -0
  129. package/dist/player/content-state.js.map +1 -0
  130. package/dist/player/dynamic-body.d.ts +7 -0
  131. package/dist/player/dynamic-body.d.ts.map +1 -0
  132. package/dist/player/dynamic-body.js +28 -0
  133. package/dist/player/dynamic-body.js.map +1 -0
  134. package/dist/player/feedback-panel.d.ts +3 -0
  135. package/dist/player/feedback-panel.d.ts.map +1 -0
  136. package/dist/player/feedback-panel.js +14 -0
  137. package/dist/player/feedback-panel.js.map +1 -0
  138. package/dist/player/fetch-xml.d.ts +2 -0
  139. package/dist/player/fetch-xml.d.ts.map +1 -0
  140. package/dist/player/fetch-xml.js +10 -0
  141. package/dist/player/fetch-xml.js.map +1 -0
  142. package/dist/player/interaction-render.d.ts +14 -0
  143. package/dist/player/interaction-render.d.ts.map +1 -0
  144. package/dist/player/interaction-render.js +51 -0
  145. package/dist/player/interaction-render.js.map +1 -0
  146. package/dist/player/render-shell.d.ts +8 -0
  147. package/dist/player/render-shell.d.ts.map +1 -0
  148. package/dist/player/render-shell.js +37 -0
  149. package/dist/player/render-shell.js.map +1 -0
  150. package/dist/player/resolve-assets.d.ts +3 -0
  151. package/dist/player/resolve-assets.d.ts.map +1 -0
  152. package/dist/player/resolve-assets.js +12 -0
  153. package/dist/player/resolve-assets.js.map +1 -0
  154. package/dist/player/validation-messages.d.ts +17 -0
  155. package/dist/player/validation-messages.d.ts.map +1 -0
  156. package/dist/player/validation-messages.js +33 -0
  157. package/dist/player/validation-messages.js.map +1 -0
  158. package/dist/player-adapter.d.ts +62 -0
  159. package/dist/player-adapter.d.ts.map +1 -0
  160. package/dist/player-adapter.js +119 -0
  161. package/dist/player-adapter.js.map +1 -0
  162. package/dist/player-dev.d.ts +4 -0
  163. package/dist/player-dev.d.ts.map +1 -0
  164. package/dist/player-dev.js +14 -0
  165. package/dist/player-dev.js.map +1 -0
  166. package/dist/player-element.d.ts +14 -1
  167. package/dist/player-element.d.ts.map +1 -1
  168. package/dist/player-element.js +57 -5
  169. package/dist/player-element.js.map +1 -1
  170. package/dist/player-locale.d.ts +8 -3
  171. package/dist/player-locale.d.ts.map +1 -1
  172. package/dist/player-locale.js +16 -175
  173. package/dist/player-locale.js.map +1 -1
  174. package/dist/player-message-catalog-default.d.ts +4 -0
  175. package/dist/player-message-catalog-default.d.ts.map +1 -0
  176. package/dist/player-message-catalog-default.js +118 -0
  177. package/dist/player-message-catalog-default.js.map +1 -0
  178. package/dist/player-message-catalog-validate.d.ts +31 -0
  179. package/dist/player-message-catalog-validate.d.ts.map +1 -0
  180. package/dist/player-message-catalog-validate.js +327 -0
  181. package/dist/player-message-catalog-validate.js.map +1 -0
  182. package/dist/player-message-catalog.d.ts +18 -0
  183. package/dist/player-message-catalog.d.ts.map +1 -0
  184. package/dist/player-message-catalog.js +40 -0
  185. package/dist/player-message-catalog.js.map +1 -0
  186. package/dist/player-message-keys.d.ts +6 -0
  187. package/dist/player-message-keys.d.ts.map +1 -0
  188. package/dist/player-message-keys.js +7 -0
  189. package/dist/player-message-keys.js.map +1 -0
  190. package/dist/player-message-manifest.d.ts +272 -0
  191. package/dist/player-message-manifest.d.ts.map +1 -0
  192. package/dist/player-message-manifest.js +83 -0
  193. package/dist/player-message-manifest.js.map +1 -0
  194. package/dist/player-message-overrides.d.ts +3 -0
  195. package/dist/player-message-overrides.d.ts.map +1 -0
  196. package/dist/player-message-overrides.js +28 -0
  197. package/dist/player-message-overrides.js.map +1 -0
  198. package/dist/player-message-resolver.d.ts +31 -0
  199. package/dist/player-message-resolver.d.ts.map +1 -0
  200. package/dist/player-message-resolver.js +110 -0
  201. package/dist/player-message-resolver.js.map +1 -0
  202. package/dist/player-messages.d.ts +0 -38
  203. package/dist/player-messages.d.ts.map +1 -1
  204. package/dist/player-types.d.ts +12 -2
  205. package/dist/player-types.d.ts.map +1 -1
  206. package/dist/reorder/a11y.d.ts +7 -0
  207. package/dist/reorder/a11y.d.ts.map +1 -0
  208. package/dist/reorder/a11y.js +34 -0
  209. package/dist/reorder/a11y.js.map +1 -0
  210. package/dist/reorder/graphic-order-interaction.d.ts +4 -0
  211. package/dist/reorder/graphic-order-interaction.d.ts.map +1 -0
  212. package/dist/reorder/graphic-order-interaction.js +205 -0
  213. package/dist/reorder/graphic-order-interaction.js.map +1 -0
  214. package/dist/reorder/list-controls.d.ts +22 -0
  215. package/dist/reorder/list-controls.d.ts.map +1 -0
  216. package/dist/reorder/list-controls.js +75 -0
  217. package/dist/reorder/list-controls.js.map +1 -0
  218. package/dist/reorder/order-interaction.d.ts +4 -0
  219. package/dist/reorder/order-interaction.d.ts.map +1 -0
  220. package/dist/reorder/order-interaction.js +54 -0
  221. package/dist/reorder/order-interaction.js.map +1 -0
  222. package/dist/styles/base-styles.d.ts +2 -0
  223. package/dist/styles/base-styles.d.ts.map +1 -0
  224. package/dist/styles/base-styles.js +133 -0
  225. package/dist/styles/base-styles.js.map +1 -0
  226. package/dist/styles/choice-hottext-styles.d.ts +2 -0
  227. package/dist/styles/choice-hottext-styles.d.ts.map +1 -0
  228. package/dist/styles/choice-hottext-styles.js +76 -0
  229. package/dist/styles/choice-hottext-styles.js.map +1 -0
  230. package/dist/styles/control-styles.d.ts +2 -0
  231. package/dist/styles/control-styles.d.ts.map +1 -0
  232. package/dist/styles/control-styles.js +114 -0
  233. package/dist/styles/control-styles.js.map +1 -0
  234. package/dist/styles/drawing-styles.d.ts +2 -0
  235. package/dist/styles/drawing-styles.d.ts.map +1 -0
  236. package/dist/styles/drawing-styles.js +30 -0
  237. package/dist/styles/drawing-styles.js.map +1 -0
  238. package/dist/styles/gap-match-styles.d.ts +2 -0
  239. package/dist/styles/gap-match-styles.d.ts.map +1 -0
  240. package/dist/styles/gap-match-styles.js +33 -0
  241. package/dist/styles/gap-match-styles.js.map +1 -0
  242. package/dist/styles/graphic-styles.d.ts +2 -0
  243. package/dist/styles/graphic-styles.d.ts.map +1 -0
  244. package/dist/styles/graphic-styles.js +289 -0
  245. package/dist/styles/graphic-styles.js.map +1 -0
  246. package/dist/styles/match-pair-styles.d.ts +2 -0
  247. package/dist/styles/match-pair-styles.d.ts.map +1 -0
  248. package/dist/styles/match-pair-styles.js +62 -0
  249. package/dist/styles/match-pair-styles.js.map +1 -0
  250. package/dist/styles/text-slider-styles.d.ts +2 -0
  251. package/dist/styles/text-slider-styles.d.ts.map +1 -0
  252. package/dist/styles/text-slider-styles.js +35 -0
  253. package/dist/styles/text-slider-styles.js.map +1 -0
  254. package/package.json +8 -8
  255. package/src/controls/remove-button.ts +8 -5
  256. package/src/index.ts +61 -5
  257. package/src/interactions/choice-interaction.ts +6 -2
  258. package/src/interactions/drawing-interaction.ts +14 -9
  259. package/src/interactions/end-attempt-interaction.ts +3 -3
  260. package/src/interactions/gap-match-interaction.ts +32 -13
  261. package/src/interactions/graphic-associate-interaction.ts +15 -10
  262. package/src/interactions/hotspot-interaction.ts +10 -6
  263. package/src/interactions/inline-choice-interaction.ts +4 -4
  264. package/src/interactions/interaction-registry.ts +12 -12
  265. package/src/interactions/match-interaction.ts +9 -6
  266. package/src/interactions/pair-interaction.ts +22 -14
  267. package/src/interactions/position-object-interaction.ts +22 -13
  268. package/src/interactions/select-point-interaction.ts +25 -13
  269. package/src/interactions/shared.ts +21 -4
  270. package/src/interactions/text-interaction.ts +14 -4
  271. package/src/interactions/upload-interaction.ts +6 -3
  272. package/src/player/interaction-render.ts +4 -4
  273. package/src/player-adapter.ts +253 -0
  274. package/src/player-dev.ts +14 -0
  275. package/src/player-element.ts +78 -8
  276. package/src/player-locale.ts +28 -199
  277. package/src/player-message-catalog-default.ts +119 -0
  278. package/src/player-message-catalog-validate.ts +425 -0
  279. package/src/player-message-catalog.ts +72 -0
  280. package/src/player-message-keys.ts +12 -0
  281. package/src/player-message-manifest.ts +103 -0
  282. package/src/player-message-overrides.ts +38 -0
  283. package/src/player-message-resolver.ts +205 -0
  284. package/src/player-messages.ts +0 -30
  285. package/src/player-types.ts +15 -4
  286. package/src/reorder/a11y.ts +22 -7
  287. package/src/reorder/graphic-order-interaction.ts +23 -16
  288. package/src/reorder/list-controls.ts +8 -6
  289. package/src/reorder/order-interaction.ts +7 -5
  290. package/src/styles/base-styles.ts +20 -5
  291. package/src/styles/graphic-styles.ts +0 -6
@@ -26,7 +26,12 @@ import {
26
26
  collectEmbeddedInteractionDiagnostics,
27
27
  collectInteractionRenderDiagnostics,
28
28
  } from "./interactions/interaction-diagnostics.js";
29
+ import type { PlayerMessageCatalog } from "./player-message-catalog.js";
29
30
  import { defaultPlayerLocale, normalizedLocale, resolvePlayerMessages } from "./player-locale.js";
31
+ import type {
32
+ PlayerMessageResolver,
33
+ QtiPlayerMessageOverrides,
34
+ } from "./player-message-resolver.js";
30
35
  import { syncAttemptAvailability } from "./player/attempt-availability.js";
31
36
  import {
32
37
  currentTemplateValue,
@@ -48,7 +53,6 @@ import type {
48
53
  QtiAssessmentItemPlayerEventDetailMap,
49
54
  QtiAssessmentItemPlayerEventName,
50
55
  QtiPlayerLoadOptions,
51
- QtiPlayerMessageOverrides,
52
56
  QtiPlayerResolveAsset,
53
57
  QtiPlayerSessionControl,
54
58
  QtiScoreAttemptOptions,
@@ -80,11 +84,14 @@ export class QtiAssessmentItemPlayer extends HTMLElementBase {
80
84
  private validationMessages: QtiDiagnostic[] = [];
81
85
  private authoringDiagnostics: QtiDiagnostic[] = [];
82
86
  private languageOfInterfaceOverride: string | undefined;
87
+ private messageCatalogOverride: PlayerMessageCatalog | undefined;
83
88
  private messageOverrides: QtiPlayerMessageOverrides = {};
89
+ private resolvedMessagesCache: PlayerMessageResolver | undefined;
84
90
  private sessionControl: Required<QtiPlayerSessionControl> = {
85
91
  validateResponses: true,
86
92
  showFeedback: true,
87
93
  };
94
+ private loadGeneration = 0;
88
95
 
89
96
  get languageOfInterface(): string {
90
97
  return (
@@ -97,6 +104,7 @@ export class QtiAssessmentItemPlayer extends HTMLElementBase {
97
104
 
98
105
  set languageOfInterface(value: string | undefined) {
99
106
  this.languageOfInterfaceOverride = normalizedLocale(value);
107
+ this.invalidatePlayerMessages();
100
108
  this.rerenderIfLoaded();
101
109
  }
102
110
 
@@ -114,6 +122,17 @@ export class QtiAssessmentItemPlayer extends HTMLElementBase {
114
122
 
115
123
  set messages(value: QtiPlayerMessageOverrides | undefined) {
116
124
  this.messageOverrides = value ?? {};
125
+ this.invalidatePlayerMessages();
126
+ this.rerenderIfLoaded();
127
+ }
128
+
129
+ get messageCatalog(): PlayerMessageCatalog | undefined {
130
+ return this.messageCatalogOverride;
131
+ }
132
+
133
+ set messageCatalog(value: PlayerMessageCatalog | undefined) {
134
+ this.messageCatalogOverride = value;
135
+ this.invalidatePlayerMessages();
117
136
  this.rerenderIfLoaded();
118
137
  }
119
138
 
@@ -121,15 +140,59 @@ export class QtiAssessmentItemPlayer extends HTMLElementBase {
121
140
  if ((name !== "language-of-interface" && name !== "locale") || oldValue === newValue) {
122
141
  return;
123
142
  }
143
+ this.invalidatePlayerMessages();
124
144
  this.rerenderIfLoaded();
125
145
  }
126
146
 
147
+ private invalidatePlayerMessages(): void {
148
+ this.resolvedMessagesCache = undefined;
149
+ }
150
+
151
+ /** Clears the loaded item. Does not emit player events; declarative hosts control this via `xml`. */
152
+ clearItem(): void {
153
+ this.loadGeneration += 1;
154
+ delete this.documentModel;
155
+ delete this.session;
156
+ this.validationMessages = [];
157
+ this.authoringDiagnostics = [];
158
+ this.replaceChildren();
159
+ }
160
+
127
161
  async loadXml(xml: string, options: QtiPlayerLoadOptions = {}): Promise<void> {
162
+ const generation = this.beginLoad();
163
+ await this.applyLoadedXml(generation, xml, options);
164
+ }
165
+
166
+ async loadUrl(url: string, options: QtiPlayerLoadOptions = {}): Promise<void> {
167
+ const generation = this.beginLoad();
168
+ const fetchXml = options.fetchXml ?? defaultFetchXml;
169
+ const xml = await fetchXml(url);
170
+ if (!this.isCurrentLoad(generation)) return;
171
+ await this.applyLoadedXml(generation, xml, options);
172
+ }
173
+
174
+ private beginLoad(): number {
175
+ this.loadGeneration += 1;
176
+ return this.loadGeneration;
177
+ }
178
+
179
+ private isCurrentLoad(generation: number): boolean {
180
+ return generation === this.loadGeneration;
181
+ }
182
+
183
+ private async applyLoadedXml(
184
+ generation: number,
185
+ xml: string,
186
+ options: QtiPlayerLoadOptions,
187
+ ): Promise<void> {
188
+ if (!this.isCurrentLoad(generation)) return;
189
+
128
190
  this.sessionControl = {
129
191
  validateResponses: options.sessionControl?.validateResponses ?? true,
130
192
  showFeedback: options.sessionControl?.showFeedback ?? true,
131
193
  };
132
194
  this.resolveAsset = options.resolveAsset;
195
+
133
196
  const result = parseQtiXml(xml);
134
197
  const playerDiagnostics = result.document
135
198
  ? [
@@ -137,6 +200,8 @@ export class QtiAssessmentItemPlayer extends HTMLElementBase {
137
200
  ...collectEmbeddedInteractionDiagnostics(result.document.item),
138
201
  ]
139
202
  : [];
203
+ if (!this.isCurrentLoad(generation)) return;
204
+
140
205
  this.dispatchEvent(
141
206
  new CustomEvent("qti-diagnostics", {
142
207
  detail: { diagnostics: [...result.diagnostics, ...playerDiagnostics] },
@@ -146,10 +211,13 @@ export class QtiAssessmentItemPlayer extends HTMLElementBase {
146
211
  playerDiagnostics.filter((diagnostic) => diagnostic.severity === "error"),
147
212
  );
148
213
  if (!result.document) {
214
+ if (!this.isCurrentLoad(generation)) return;
149
215
  this.replaceChildren(errorView("Unable to parse QTI item."));
150
216
  return;
151
217
  }
152
218
 
219
+ if (!this.isCurrentLoad(generation)) return;
220
+
153
221
  this.documentModel = result.document;
154
222
  this.session = createItemSession(result.document, options.state);
155
223
  this.validationMessages = cloneDiagnostics(
@@ -163,11 +231,6 @@ export class QtiAssessmentItemPlayer extends HTMLElementBase {
163
231
  this.emitStateChange();
164
232
  }
165
233
 
166
- async loadUrl(url: string, options: QtiPlayerLoadOptions = {}): Promise<void> {
167
- const fetchXml = options.fetchXml ?? defaultFetchXml;
168
- await this.loadXml(await fetchXml(url), options);
169
- }
170
-
171
234
  scoreAttempt(options: QtiScoreAttemptOptions = {}): QtiScoreResult | undefined {
172
235
  const session = this.session;
173
236
  if (!session) return undefined;
@@ -288,8 +351,15 @@ export class QtiAssessmentItemPlayer extends HTMLElementBase {
288
351
  this.dispatchEvent(new CustomEvent<QtiAssessmentItemPlayerEventDetailMap[T]>(type, { detail }));
289
352
  }
290
353
 
291
- private playerMessages() {
292
- return resolvePlayerMessages(this.languageOfInterface, this.messageOverrides);
354
+ private playerMessages(): PlayerMessageResolver {
355
+ if (!this.resolvedMessagesCache) {
356
+ this.resolvedMessagesCache = resolvePlayerMessages(
357
+ this.languageOfInterface,
358
+ this.messageOverrides,
359
+ this.messageCatalogOverride,
360
+ );
361
+ }
362
+ return this.resolvedMessagesCache;
293
363
  }
294
364
 
295
365
  private rerenderIfLoaded(): void {
@@ -1,206 +1,35 @@
1
- import type { QtiPlayerMessages } from "./player-messages.js";
2
- import type { QtiPlayerMessageOverrides } from "./player-types.js";
3
-
4
- type QtiPlayerMessageCatalog = Readonly<Partial<QtiPlayerMessages>>;
5
-
6
- const defaultEnglishPlayerMessages: QtiPlayerMessages = {
7
- remove: () => "Remove",
8
- removePair: ({ label }) => `Remove ${label}`,
9
- clearDrawing: () => "Clear drawing",
10
- clearPoints: () => "Clear points",
11
- endAttempt: () => "End attempt",
12
- uploadResponse: () => "Upload response",
13
- movableObject: () => "Movable object",
14
- placeObject: () => "Place",
15
- inlineChoicePrompt: () => "Choose...",
16
- noPointSelected: () => "No point selected",
17
- noRegionSelected: () => "No region selected",
18
- noAssociationsMade: () => "No associations made",
19
- associationsMade: ({ count }) => `${count} ${count === 1 ? "association" : "associations"} made.`,
20
- associationPairLabel: ({ source, target }) => `${source} to ${target}`,
21
- hotspotSelectedChooseAnother: ({ label }) => `${label} selected. Choose another hotspot.`,
22
- moveChoice: ({ label, direction }) => `Move ${label} ${direction}`,
23
- movePoint: ({ direction }) => `Move point ${direction}`,
24
- moveObject: ({ direction }) => `Move object ${direction}`,
25
- };
26
-
27
- const playerMessages = {
28
- defaultEnglish: defaultEnglishPlayerMessages,
29
- spanish: {
30
- remove: () => "Quitar",
31
- removePair: ({ label }) => `Quitar ${label}`,
32
- clearDrawing: () => "Borrar dibujo",
33
- clearPoints: () => "Borrar puntos",
34
- endAttempt: () => "Finalizar intento",
35
- uploadResponse: () => "Subir respuesta",
36
- movableObject: () => "Objeto movible",
37
- placeObject: () => "Colocar",
38
- inlineChoicePrompt: () => "Elija...",
39
- noPointSelected: () => "Ningun punto seleccionado",
40
- noRegionSelected: () => "Ninguna region seleccionada",
41
- noAssociationsMade: () => "Ninguna asociacion realizada",
42
- associationsMade: ({ count }) =>
43
- `${count} ${count === 1 ? "asociacion realizada" : "asociaciones realizadas"}.`,
44
- associationPairLabel: ({ source, target }) => `${source} con ${target}`,
45
- hotspotSelectedChooseAnother: ({ label }) => `${label} seleccionado. Elija otro punto activo.`,
46
- moveChoice: ({ label, direction }) => `Mover ${label} ${spanishDirection(direction)}`,
47
- movePoint: ({ direction }) => `Mover punto ${spanishDirection(direction)}`,
48
- moveObject: ({ direction }) => `Mover objeto ${spanishDirection(direction)}`,
49
- },
50
- swedish: {
51
- remove: () => "Ta bort",
52
- removePair: ({ label }) => `Ta bort ${label}`,
53
- clearDrawing: () => "Rensa ritning",
54
- clearPoints: () => "Rensa punkter",
55
- endAttempt: () => "Avsluta forsok",
56
- uploadResponse: () => "Ladda upp svar",
57
- movableObject: () => "Flyttbart objekt",
58
- placeObject: () => "Placera",
59
- inlineChoicePrompt: () => "Valj...",
60
- noPointSelected: () => "Ingen punkt vald",
61
- noRegionSelected: () => "Ingen region vald",
62
- noAssociationsMade: () => "Inga associationer gjorda",
63
- associationsMade: ({ count }) =>
64
- `${count} ${count === 1 ? "association gjord" : "associationer gjorda"}.`,
65
- associationPairLabel: ({ source, target }) => `${source} med ${target}`,
66
- hotspotSelectedChooseAnother: ({ label }) => `${label} valt. Valj en annan hotspot.`,
67
- moveChoice: ({ label, direction }) => `Flytta ${label} ${swedishDirection(direction)}`,
68
- movePoint: ({ direction }) => `Flytta punkt ${swedishDirection(direction)}`,
69
- moveObject: ({ direction }) => `Flytta objekt ${swedishDirection(direction)}`,
70
- },
71
- german: {
72
- remove: () => "Entfernen",
73
- removePair: ({ label }) => `${label} entfernen`,
74
- clearDrawing: () => "Zeichnung loeschen",
75
- clearPoints: () => "Punkte loeschen",
76
- endAttempt: () => "Versuch beenden",
77
- uploadResponse: () => "Antwort hochladen",
78
- movableObject: () => "Bewegliches Objekt",
79
- placeObject: () => "Platzieren",
80
- inlineChoicePrompt: () => "Waehlen...",
81
- noPointSelected: () => "Kein Punkt ausgewaehlt",
82
- noRegionSelected: () => "Keine Region ausgewaehlt",
83
- noAssociationsMade: () => "Keine Zuordnungen erstellt",
84
- associationsMade: ({ count }) =>
85
- `${count} ${count === 1 ? "Zuordnung erstellt" : "Zuordnungen erstellt"}.`,
86
- associationPairLabel: ({ source, target }) => `${source} mit ${target}`,
87
- hotspotSelectedChooseAnother: ({ label }) =>
88
- `${label} ausgewaehlt. Waehlen Sie einen weiteren Hotspot.`,
89
- moveChoice: ({ label, direction }) => `${label} ${germanDirection(direction)} bewegen`,
90
- movePoint: ({ direction }) => `Punkt ${germanDirection(direction)} bewegen`,
91
- moveObject: ({ direction }) => `Objekt ${germanDirection(direction)} bewegen`,
92
- },
93
- portuguese: {
94
- remove: () => "Remover",
95
- removePair: ({ label }) => `Remover ${label}`,
96
- clearDrawing: () => "Limpar desenho",
97
- clearPoints: () => "Limpar pontos",
98
- endAttempt: () => "Finalizar tentativa",
99
- uploadResponse: () => "Enviar resposta",
100
- movableObject: () => "Objeto movel",
101
- placeObject: () => "Posicionar",
102
- inlineChoicePrompt: () => "Escolha...",
103
- noPointSelected: () => "Nenhum ponto selecionado",
104
- noRegionSelected: () => "Nenhuma regiao selecionada",
105
- noAssociationsMade: () => "Nenhuma associacao feita",
106
- associationsMade: ({ count }) =>
107
- `${count} ${count === 1 ? "associacao feita" : "associacoes feitas"}.`,
108
- associationPairLabel: ({ source, target }) => `${source} com ${target}`,
109
- hotspotSelectedChooseAnother: ({ label }) => `${label} selecionado. Escolha outro ponto ativo.`,
110
- moveChoice: ({ label, direction }) => `Mover ${label} ${portugueseDirection(direction)}`,
111
- movePoint: ({ direction }) => `Mover ponto ${portugueseDirection(direction)}`,
112
- moveObject: ({ direction }) => `Mover objeto ${portugueseDirection(direction)}`,
113
- },
114
- french: {
115
- remove: () => "Supprimer",
116
- removePair: ({ label }) => `Supprimer ${label}`,
117
- clearDrawing: () => "Effacer le dessin",
118
- clearPoints: () => "Effacer les points",
119
- endAttempt: () => "Terminer la tentative",
120
- uploadResponse: () => "Televerser la reponse",
121
- movableObject: () => "Objet mobile",
122
- placeObject: () => "Placer",
123
- inlineChoicePrompt: () => "Choisir...",
124
- noPointSelected: () => "Aucun point selectionne",
125
- noRegionSelected: () => "Aucune region selectionnee",
126
- noAssociationsMade: () => "Aucune association effectuee",
127
- associationsMade: ({ count }) =>
128
- `${count} ${count === 1 ? "association effectuee" : "associations effectuees"}.`,
129
- associationPairLabel: ({ source, target }) => `${source} avec ${target}`,
130
- hotspotSelectedChooseAnother: ({ label }) =>
131
- `${label} selectionne. Choisissez un autre point actif.`,
132
- moveChoice: ({ label, direction }) => `Deplacer ${label} vers ${frenchDirection(direction)}`,
133
- movePoint: ({ direction }) => `Deplacer le point vers ${frenchDirection(direction)}`,
134
- moveObject: ({ direction }) => `Deplacer l'objet vers ${frenchDirection(direction)}`,
135
- },
136
- } satisfies Record<string, QtiPlayerMessageCatalog>;
137
-
138
- const builtInPlayerMessageCatalogs: ReadonlyMap<string, QtiPlayerMessageCatalog> = new Map([
139
- ["en", playerMessages.defaultEnglish],
140
- ["es", playerMessages.spanish],
141
- ["es-es", playerMessages.spanish],
142
- ["es-mx", playerMessages.spanish],
143
- ["sv", playerMessages.swedish],
144
- ["sv-se", playerMessages.swedish],
145
- ["de", playerMessages.german],
146
- ["de-de", playerMessages.german],
147
- ["pt", playerMessages.portuguese],
148
- ["pt-br", playerMessages.portuguese],
149
- ["pt-pt", playerMessages.portuguese],
150
- ["fr", playerMessages.french],
151
- ["fr-ca", playerMessages.french],
152
- ["fr-fr", playerMessages.french],
153
- ]);
154
-
1
+ import type { PlayerMessageCatalog } from "./player-message-catalog.js";
2
+ import { applyPlayerMessageOverrides } from "./player-message-overrides.js";
3
+ import {
4
+ createPlayerMessageResolver,
5
+ defaultPlayerMessageResolver,
6
+ type PlayerMessageResolver,
7
+ } from "./player-message-resolver.js";
8
+ import { warnPlayerMessageOnce } from "./player-dev.js";
9
+ import type { QtiPlayerMessageOverrides } from "./player-message-resolver.js";
10
+
11
+ function isEnglishLocale(locale: string): boolean {
12
+ const normalized = normalizedLocale(locale) ?? locale;
13
+ return normalized.toLowerCase().startsWith("en");
14
+ }
15
+
16
+ /**
17
+ * Resolves player chrome messages: catalog (or English defaults) merged with overrides.
18
+ * `locale` does not select built-in catalogs; use `catalog` via `player.messageCatalog`.
19
+ */
155
20
  export function resolvePlayerMessages(
156
21
  locale: string,
157
22
  overrides: QtiPlayerMessageOverrides,
158
- ): QtiPlayerMessages {
159
- const catalog = builtInPlayerMessageCatalog(locale);
160
- return { ...defaultEnglishPlayerMessages, ...catalog, ...overrides };
161
- }
162
-
163
- function spanishDirection(direction: "up" | "down" | "left" | "right"): string {
164
- return { up: "arriba", down: "abajo", left: "a la izquierda", right: "a la derecha" }[direction];
165
- }
166
-
167
- function swedishDirection(direction: "up" | "down" | "left" | "right"): string {
168
- return { up: "upp", down: "ned", left: "vanster", right: "hoger" }[direction];
169
- }
170
-
171
- function germanDirection(direction: "up" | "down" | "left" | "right"): string {
172
- return { up: "nach oben", down: "nach unten", left: "nach links", right: "nach rechts" }[
173
- direction
174
- ];
175
- }
176
-
177
- function portugueseDirection(direction: "up" | "down" | "left" | "right"): string {
178
- return { up: "para cima", down: "para baixo", left: "para a esquerda", right: "para a direita" }[
179
- direction
180
- ];
181
- }
182
-
183
- function frenchDirection(direction: "up" | "down" | "left" | "right"): string {
184
- return { up: "le haut", down: "le bas", left: "la gauche", right: "la droite" }[direction];
185
- }
186
-
187
- function builtInPlayerMessageCatalog(locale: string): QtiPlayerMessageCatalog | undefined {
188
- for (const candidate of localeFallbacks(locale)) {
189
- const catalog = builtInPlayerMessageCatalogs.get(candidate);
190
- if (catalog) return catalog;
191
- }
192
- return undefined;
193
- }
194
-
195
- function localeFallbacks(locale: string): string[] {
196
- const normalized = normalizedLocale(locale)?.toLowerCase();
197
- if (!normalized) return ["en"];
198
- const parts = normalized.split("-");
199
- const fallbacks: string[] = [];
200
- for (let length = parts.length; length > 0; length -= 1) {
201
- fallbacks.push(parts.slice(0, length).join("-"));
23
+ catalog?: PlayerMessageCatalog,
24
+ ): PlayerMessageResolver {
25
+ if (!catalog && !isEnglishLocale(locale) && Object.keys(overrides).length === 0) {
26
+ warnPlayerMessageOnce(
27
+ `locale-without-catalog:${locale}`,
28
+ `language-of-interface is "${locale}" but no player.messageCatalog was set; chrome stays English. Load a locale file and assign player.messageCatalog.`,
29
+ );
202
30
  }
203
- return fallbacks.includes("en") ? fallbacks : [...fallbacks, "en"];
31
+ const base = catalog ? createPlayerMessageResolver(catalog) : defaultPlayerMessageResolver;
32
+ return applyPlayerMessageOverrides(base, overrides);
204
33
  }
205
34
 
206
35
  export function normalizedLocale(value: string | undefined | null): string | undefined {
@@ -0,0 +1,119 @@
1
+ import type { PlayerMessageCatalog } from "./player-message-catalog.js";
2
+
3
+ /** English player chrome catalog (JSON-serializable). Hosts copy this shape for locale files. */
4
+ export const defaultPlayerMessageCatalog: PlayerMessageCatalog = {
5
+ locale: "en",
6
+ directions: {
7
+ up: "up",
8
+ down: "down",
9
+ left: "left",
10
+ right: "right",
11
+ },
12
+ interactionTypes: {
13
+ associate: "Associate",
14
+ choice: "Choice",
15
+ drawing: "Drawing",
16
+ endAttempt: "End attempt",
17
+ extendedText: "Extended text",
18
+ gapMatch: "Gap match",
19
+ graphicAssociate: "Graphic associate",
20
+ graphicGapMatch: "Graphic gap match",
21
+ graphicOrder: "Graphic order",
22
+ hottext: "Hot text",
23
+ hotspot: "Hotspot",
24
+ inlineChoice: "Inline choice",
25
+ match: "Match",
26
+ media: "Media",
27
+ order: "Order",
28
+ pair: "Pair",
29
+ portableCustom: "Portable custom",
30
+ positionObject: "Position object",
31
+ selectPoint: "Select point",
32
+ slider: "Slider",
33
+ textEntry: "Text entry",
34
+ upload: "Upload",
35
+ },
36
+ strings: {
37
+ remove: "Remove",
38
+ removePair: "Remove {label}",
39
+ clearDrawing: "Clear drawing",
40
+ clearPoints: "Clear points",
41
+ endAttempt: "End attempt",
42
+ uploadResponse: "Upload response",
43
+ movableObject: "Movable object",
44
+ placeObject: "Place",
45
+ inlineChoicePrompt: "Choose...",
46
+ extendedTextCounter: "{characters} {characterUnit}, {words} {wordUnit}",
47
+ "characterUnit.one": "character",
48
+ "characterUnit.other": "characters",
49
+ "wordUnit.one": "word",
50
+ "wordUnit.other": "words",
51
+ hotspotSelectionSummary: "Selected {selection}",
52
+ noPointSelected: "No point selected",
53
+ noRegionSelected: "No region selected",
54
+ noAssociationsMade: "No associations made",
55
+ "associationsMade.one": "{count} association made.",
56
+ "associationsMade.other": "{count} associations made.",
57
+ associationPairLabel: "{source} to {target}",
58
+ hotspotSelectedChooseAnother: "{label} selected. Choose another hotspot.",
59
+ moveChoice: "Move {label} {direction}",
60
+ movePoint: "Move point {direction}",
61
+ moveObject: "Move object {direction}",
62
+ matchSourcesBank: "Match sources",
63
+ matchTargetsBank: "Match targets",
64
+ matchSelectedPairsList: "Match selected pairs",
65
+ interactionSourcesBank: "{typeName} sources",
66
+ interactionTargetsBank: "{typeName} targets",
67
+ interactionSelectedPairsList: "{typeName} selected pairs",
68
+ associateFirstConceptRegion: "First concept",
69
+ associatePairWithRegion: "Pair with",
70
+ matchPromptRegion: "Prompt",
71
+ matchMatchRegion: "Match",
72
+ genericSourceRegion: "Source",
73
+ genericTargetRegion: "Target",
74
+ interactionHotspots: "{typeName} hotspots",
75
+ interactionImageAlt: "{typeName} image",
76
+ interactionCurrentOrderList: "{typeName} current order",
77
+ interactionSelectedOrderList: "{typeName} selected order",
78
+ interactionChoicesBank: "{typeName} choices",
79
+ interactionGapTargets: "{typeName} targets",
80
+ interactionTargetImage: "{typeName} target image",
81
+ interactionOptionsList: "{typeName} options",
82
+ interactionCoordinateResponse: "{typeName} coordinate response",
83
+ interactionCoordinateArea: "{typeName} coordinate area",
84
+ interactionCoordinateAreaSelected: "{typeName} coordinate area, selected {coordinates}",
85
+ interactionPlacementResponse: "{typeName} object placement response",
86
+ interactionPlacementStage: "{typeName} placement stage",
87
+ interactionPlacementStageEmpty: "{typeName} placement stage, object not placed",
88
+ interactionPlacementStageAt: "{typeName} placement stage, object at {coordinates}",
89
+ interactionDrawingResponse: "{typeName} response",
90
+ drawingSurface: "Drawing response surface",
91
+ drawingSurfaceEmpty: "Drawing response surface, no strokes",
92
+ "drawingSurfaceStrokeCount.one": "Drawing response surface, {count} stroke",
93
+ "drawingSurfaceStrokeCount.other": "Drawing response surface, {count} strokes",
94
+ drawingStatusEmpty: "No drawing strokes.",
95
+ "drawingStatusStrokeCount.one": "{count} drawing stroke.",
96
+ "drawingStatusStrokeCount.other": "{count} drawing strokes.",
97
+ gapLabel: "Gap {index}",
98
+ graphicGapTargetLabel: "Target {index}",
99
+ gapEmptyState: "{label}, empty",
100
+ gapAssignedState: "{label}, assigned {assigned}",
101
+ "gapLabelsPlacedCount.one": "{count} label placed.",
102
+ "gapLabelsPlacedCount.other": "{count} labels placed.",
103
+ gapNoLabelsPlaced: "No labels placed.",
104
+ orderedItemAtPosition: "{label}, position {position} of {total}",
105
+ orderedItemMovedOneStep: "{label} moved {direction}.",
106
+ orderedItemMovedToPosition: "{label} moved to position {position} of {total}.",
107
+ "graphicOrderRegionsSelected.one": "{count} region ordered.",
108
+ "graphicOrderRegionsSelected.other": "{count} regions ordered.",
109
+ graphicOrderNoRegionsSelected: "No regions ordered.",
110
+ objectNotPlaced: "Object not placed",
111
+ objectPositionedAt: "Object positioned at {coordinates}",
112
+ selectedPointAt: "Selected point {coordinates}",
113
+ "selectedPointsSummary.one": "{count} selected point: {coordinates}",
114
+ "selectedPointsSummary.other": "{count} selected points: {coordinates}",
115
+ extendedTextResponseLabel: "Extended text response",
116
+ textResponseLabel: "Text response",
117
+ sliderResponseLabel: "Slider response",
118
+ },
119
+ };