@intellectif/lk-core 0.9.0 → 0.10.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 (45) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/{activity-CPtJUBek.d.cts → activity-B0zbvu18.d.cts} +177 -2
  3. package/dist/{activity-CPtJUBek.d.ts → activity-B0zbvu18.d.ts} +177 -2
  4. package/dist/{chunk-YVZCFUGP.cjs → chunk-3FEAEO3D.cjs} +18 -13
  5. package/dist/chunk-3FEAEO3D.cjs.map +1 -0
  6. package/dist/{chunk-CVRALNUK.cjs → chunk-7ODQRNR4.cjs} +618 -45
  7. package/dist/chunk-7ODQRNR4.cjs.map +1 -0
  8. package/dist/{chunk-AHEUYOT3.js → chunk-FIS5KBCE.js} +670 -97
  9. package/dist/chunk-FIS5KBCE.js.map +1 -0
  10. package/dist/{chunk-ELTP5P6V.js → chunk-IXXMYJI7.js} +2 -2
  11. package/dist/{chunk-MOLL5KXO.cjs → chunk-VBM5H6P5.cjs} +8 -8
  12. package/dist/{chunk-MOLL5KXO.cjs.map → chunk-VBM5H6P5.cjs.map} +1 -1
  13. package/dist/{chunk-RL2PQLCY.cjs → chunk-VTTRXGDD.cjs} +4 -4
  14. package/dist/{chunk-RL2PQLCY.cjs.map → chunk-VTTRXGDD.cjs.map} +1 -1
  15. package/dist/{chunk-6DM2H6BD.js → chunk-XGCTAQSS.js} +7 -2
  16. package/dist/chunk-XGCTAQSS.js.map +1 -0
  17. package/dist/{chunk-QX7P3CHP.js → chunk-ZTY4UIUD.js} +2 -2
  18. package/dist/{index-gIN564mV.d.cts → index-DSfETm4b.d.ts} +321 -3
  19. package/dist/{index-BvrA8nIV.d.ts → index-u_rBLkuc.d.cts} +321 -3
  20. package/dist/index.cjs +53 -31
  21. package/dist/index.cjs.map +1 -1
  22. package/dist/index.d.cts +4 -4
  23. package/dist/index.d.ts +4 -4
  24. package/dist/index.js +26 -4
  25. package/dist/index.js.map +1 -1
  26. package/dist/schemas.cjs +25 -3
  27. package/dist/schemas.cjs.map +1 -1
  28. package/dist/schemas.d.cts +2 -2
  29. package/dist/schemas.d.ts +2 -2
  30. package/dist/schemas.js +24 -2
  31. package/dist/scoring.cjs +3 -3
  32. package/dist/scoring.d.cts +2 -2
  33. package/dist/scoring.d.ts +2 -2
  34. package/dist/scoring.js +2 -2
  35. package/dist/xapi.cjs +3 -3
  36. package/dist/xapi.d.cts +1 -1
  37. package/dist/xapi.d.ts +1 -1
  38. package/dist/xapi.js +2 -2
  39. package/package.json +1 -1
  40. package/dist/chunk-6DM2H6BD.js.map +0 -1
  41. package/dist/chunk-AHEUYOT3.js.map +0 -1
  42. package/dist/chunk-CVRALNUK.cjs.map +0 -1
  43. package/dist/chunk-YVZCFUGP.cjs.map +0 -1
  44. /package/dist/{chunk-ELTP5P6V.js.map → chunk-IXXMYJI7.js.map} +0 -0
  45. /package/dist/{chunk-QX7P3CHP.js.map → chunk-ZTY4UIUD.js.map} +0 -0
@@ -179,19 +179,19 @@ var FillInTheBlanksDataSchema = _v4.z.looseObject({
179
179
  difficultyLevel: _v4.z.literal([1, 2, 3, 4, 5]).optional()
180
180
  }).refine(
181
181
  (data) => {
182
- const placeholderCounts = /* @__PURE__ */ new Map();
182
+ const placeholderCounts2 = /* @__PURE__ */ new Map();
183
183
  for (const match of data.passage.matchAll(PLACEHOLDER_RE)) {
184
184
  const id = match[1];
185
- placeholderCounts.set(id, (_nullishCoalesce(placeholderCounts.get(id), () => ( 0))) + 1);
185
+ placeholderCounts2.set(id, (_nullishCoalesce(placeholderCounts2.get(id), () => ( 0))) + 1);
186
186
  }
187
187
  const blankIds = data.blanks.map((blank) => blank.id);
188
188
  if (new Set(blankIds).size !== blankIds.length) {
189
189
  return false;
190
190
  }
191
- if (placeholderCounts.size !== blankIds.length) {
191
+ if (placeholderCounts2.size !== blankIds.length) {
192
192
  return false;
193
193
  }
194
- return blankIds.every((id) => placeholderCounts.get(id) === 1);
194
+ return blankIds.every((id) => placeholderCounts2.get(id) === 1);
195
195
  },
196
196
  {
197
197
  error: "Each blank id must appear exactly once in blanks[] and have exactly one matching {{id}} placeholder in the passage, and vice versa.",
@@ -199,25 +199,29 @@ var FillInTheBlanksDataSchema = _v4.z.looseObject({
199
199
  }
200
200
  );
201
201
 
202
- // src/count-words.ts
203
- function countWords(text) {
204
- if (typeof text !== "string") {
205
- return 0;
206
- }
207
- const trimmed = text.trim();
208
- if (trimmed === "") {
209
- return 0;
210
- }
211
- return trimmed.split(/\s+/).length;
212
- }
213
-
214
202
  // src/schemas/multiple-choice.ts
215
203
 
204
+ var MultipleChoiceOptionMediaSchema = _v4.z.looseObject({
205
+ // No `embed`: an iframe swallows the click that selects the option, so the
206
+ // learner could not choose it. No `video`: a native control bar inside the
207
+ // option's label eats the same click.
208
+ type: _v4.z.enum(["image", "audio"]),
209
+ url: MediaUrlSchema,
210
+ alt: _v4.z.string().min(1).optional(),
211
+ captionsUrl: MediaUrlSchema.optional()
212
+ }).refine((media) => media.type !== "image" || media.alt !== void 0 && media.alt !== "", {
213
+ error: "An image option requires a non-empty `alt`.",
214
+ path: ["alt"]
215
+ }).refine((media) => !("playback" in media), {
216
+ error: "An option carries no playback policy. maxPlays, seek and rate are enforced only on the media above the question.",
217
+ path: ["playback"]
218
+ });
216
219
  var MultipleChoiceOptionSchema = _v4.z.looseObject({
217
220
  id: _v4.z.string().min(1),
218
221
  text: _v4.z.string().min(1),
219
222
  isCorrect: _v4.z.boolean(),
220
- feedback: _v4.z.string().optional()
223
+ feedback: _v4.z.string().optional(),
224
+ media: MultipleChoiceOptionMediaSchema.optional()
221
225
  });
222
226
  var MultipleChoiceDataSchema = _v4.z.looseObject({
223
227
  schemaVersion: _v4.z.literal("1.0"),
@@ -250,6 +254,122 @@ var MultipleChoiceDataSchema = _v4.z.looseObject({
250
254
  path: ["options"]
251
255
  });
252
256
 
257
+ // src/count-words.ts
258
+ function countWords(text) {
259
+ if (typeof text !== "string") {
260
+ return 0;
261
+ }
262
+ const trimmed = text.trim();
263
+ if (trimmed === "") {
264
+ return 0;
265
+ }
266
+ return trimmed.split(/\s+/).length;
267
+ }
268
+
269
+ // src/schemas/gap-select.ts
270
+
271
+ var GapSelectChoiceSchema = _v4.z.looseObject({
272
+ id: _v4.z.string().min(1),
273
+ text: _v4.z.string().min(1)
274
+ });
275
+ var GapSelectBankSchema = _v4.z.looseObject({
276
+ id: _v4.z.string().min(1),
277
+ choices: _v4.z.array(GapSelectChoiceSchema).min(2)
278
+ });
279
+ var GapSelectGapSchema = _v4.z.looseObject({
280
+ id: _v4.z.string().min(1),
281
+ choices: _v4.z.array(GapSelectChoiceSchema).min(2).optional(),
282
+ bankId: _v4.z.string().min(1).optional(),
283
+ correctChoiceId: _v4.z.string().min(1),
284
+ feedback: _v4.z.string().optional()
285
+ });
286
+ function resolveChoices(gap, banks) {
287
+ if (gap.choices !== void 0) {
288
+ return gap.bankId === void 0 ? gap.choices : void 0;
289
+ }
290
+ if (gap.bankId === void 0) {
291
+ return void 0;
292
+ }
293
+ return _optionalChain([banks, 'access', _10 => _10.find, 'call', _11 => _11((bank) => bank.id === gap.bankId), 'optionalAccess', _12 => _12.choices]);
294
+ }
295
+ function placeholderCounts(passage) {
296
+ const counts = /* @__PURE__ */ new Map();
297
+ for (const match of passage.matchAll(PLACEHOLDER_RE)) {
298
+ const id = match[1];
299
+ counts.set(id, (_nullishCoalesce(counts.get(id), () => ( 0))) + 1);
300
+ }
301
+ return counts;
302
+ }
303
+ var GapSelectDataSchema = _v4.z.looseObject({
304
+ schemaVersion: _v4.z.literal("1.0"),
305
+ type: _v4.z.literal("gap-select"),
306
+ id: _v4.z.string().min(1),
307
+ title: _v4.z.string().min(1),
308
+ passage: _v4.z.string().min(1),
309
+ passageHtml: _v4.z.string().optional(),
310
+ gaps: _v4.z.array(GapSelectGapSchema).min(1),
311
+ banks: _v4.z.array(GapSelectBankSchema).optional(),
312
+ scoringStrategy: _v4.z.enum(["all-or-nothing", "partial"]),
313
+ presentation: _v4.z.literal("dropdown").optional(),
314
+ shuffleChoices: _v4.z.boolean().optional(),
315
+ media: MediaSchema.optional(),
316
+ feedback: FeedbackSchema.optional(),
317
+ passThreshold: _v4.z.number().min(0).max(1).optional(),
318
+ locale: _v4.z.string().optional(),
319
+ learningObjectives: _v4.z.array(_v4.z.string()).optional(),
320
+ difficultyLevel: _v4.z.literal([1, 2, 3, 4, 5]).optional()
321
+ }).refine(
322
+ (data) => {
323
+ const ids = (_nullishCoalesce(data.banks, () => ( []))).map((bank) => bank.id);
324
+ return new Set(ids).size === ids.length;
325
+ },
326
+ { error: "Word bank ids must be unique.", path: ["banks"] }
327
+ ).refine(
328
+ (data) => {
329
+ const ids = data.gaps.map((gap) => gap.id);
330
+ return new Set(ids).size === ids.length;
331
+ },
332
+ { error: "Gap ids must be unique.", path: ["gaps"] }
333
+ ).refine(
334
+ (data) => {
335
+ const counts = placeholderCounts(data.passage);
336
+ const ids = data.gaps.map((gap) => gap.id);
337
+ return counts.size === new Set(ids).size && ids.every((id) => counts.get(id) === 1) && [...counts.values()].every((count) => count === 1);
338
+ },
339
+ {
340
+ error: "Every gap must appear exactly once in the passage as {{gap_id}}, and every {{gap_id}} must have a gap.",
341
+ path: ["passage"]
342
+ }
343
+ ).refine(
344
+ (data) => data.gaps.every((gap) => gap.choices === void 0 !== (gap.bankId === void 0)),
345
+ {
346
+ error: "Each gap needs exactly one choice source: its own `choices`, or a `bankId`.",
347
+ path: ["gaps"]
348
+ }
349
+ ).refine(
350
+ (data) => {
351
+ const bankIds = new Set((_nullishCoalesce(data.banks, () => ( []))).map((bank) => bank.id));
352
+ return data.gaps.every((gap) => gap.bankId === void 0 || bankIds.has(gap.bankId));
353
+ },
354
+ { error: "A gap references a `bankId` that no word bank defines.", path: ["gaps"] }
355
+ ).refine(
356
+ (data) => {
357
+ const banks = _nullishCoalesce(data.banks, () => ( []));
358
+ return data.gaps.every((gap) => {
359
+ const choices = resolveChoices(gap, banks);
360
+ if (choices === void 0) {
361
+ return true;
362
+ }
363
+ const ids = choices.map((choice) => choice.id);
364
+ return new Set(ids).size === ids.length && ids.includes(gap.correctChoiceId);
365
+ });
366
+ },
367
+ {
368
+ error: "Each gap needs unique choice ids and a `correctChoiceId` that is one of the choices it offers.",
369
+ path: ["gaps"]
370
+ }
371
+ );
372
+
253
373
  // src/schemas/written-response.ts
254
374
 
255
375
  var WrittenResponseRubricCriterionSchema = _v4.z.looseObject({
@@ -308,9 +428,16 @@ var redactedBase = {
308
428
  learningObjectives: _v4.z.array(_v4.z.string()).optional(),
309
429
  difficultyLevel: _v4.z.literal([1, 2, 3, 4, 5]).optional()
310
430
  };
431
+ var RedactedMultipleChoiceOptionMediaSchema = _v4.z.strictObject({
432
+ type: _v4.z.enum(["image", "audio"]),
433
+ url: _v4.z.string().min(1),
434
+ alt: _v4.z.string().min(1).optional(),
435
+ captionsUrl: _v4.z.string().min(1).optional()
436
+ });
311
437
  var RedactedMultipleChoiceOptionSchema = _v4.z.strictObject({
312
438
  id: _v4.z.string().min(1),
313
- text: _v4.z.string().min(1)
439
+ text: _v4.z.string().min(1),
440
+ media: RedactedMultipleChoiceOptionMediaSchema.optional()
314
441
  });
315
442
  var RedactedMultipleChoiceDataSchema = _v4.z.strictObject({
316
443
  ...redactedBase,
@@ -332,6 +459,29 @@ var RedactedFillInTheBlanksDataSchema = _v4.z.strictObject({
332
459
  passageHtml: _v4.z.string().optional(),
333
460
  blanks: _v4.z.array(RedactedBlankConfigSchema).min(1)
334
461
  });
462
+ var RedactedGapSelectChoiceSchema = _v4.z.strictObject({
463
+ id: _v4.z.string().min(1),
464
+ text: _v4.z.string().min(1)
465
+ });
466
+ var RedactedGapSelectBankSchema = _v4.z.strictObject({
467
+ id: _v4.z.string().min(1),
468
+ choices: _v4.z.array(RedactedGapSelectChoiceSchema).min(2)
469
+ });
470
+ var RedactedGapSelectGapSchema = _v4.z.strictObject({
471
+ id: _v4.z.string().min(1),
472
+ choices: _v4.z.array(RedactedGapSelectChoiceSchema).min(2).optional(),
473
+ bankId: _v4.z.string().min(1).optional()
474
+ });
475
+ var RedactedGapSelectDataSchema = _v4.z.strictObject({
476
+ ...redactedBase,
477
+ type: _v4.z.literal("gap-select"),
478
+ passage: _v4.z.string().min(1),
479
+ passageHtml: _v4.z.string().optional(),
480
+ gaps: _v4.z.array(RedactedGapSelectGapSchema).min(1),
481
+ banks: _v4.z.array(RedactedGapSelectBankSchema).optional(),
482
+ presentation: _v4.z.literal("dropdown").optional(),
483
+ shuffleChoices: _v4.z.boolean().optional()
484
+ });
335
485
  var RedactedWrittenResponseDataSchema = _v4.z.strictObject({
336
486
  ...redactedBase,
337
487
  type: _v4.z.literal("written-response"),
@@ -484,6 +634,7 @@ var DRAFT_ISSUE_SEVERITY = {
484
634
  mc_option_id_duplicate: "invalid",
485
635
  mc_option_text_required: "incomplete",
486
636
  mc_option_correctness_required: "incomplete",
637
+ mc_option_media_kind: "invalid",
487
638
  mc_correct_option_required: "incomplete",
488
639
  mc_single_mode_one_correct: "invalid",
489
640
  // fill-in-the-blanks
@@ -500,6 +651,27 @@ var DRAFT_ISSUE_SEVERITY = {
500
651
  fib_placeholder_missing: "incomplete",
501
652
  fib_placeholder_duplicate: "invalid",
502
653
  fib_blanks_mismatch: "invalid",
654
+ // gap-select
655
+ gs_passage_required: "incomplete",
656
+ gs_gaps_required: "incomplete",
657
+ gs_gap_id_required: "invalid",
658
+ gs_gap_id_duplicate: "invalid",
659
+ gs_gap_missing: "incomplete",
660
+ gs_placeholder_missing: "incomplete",
661
+ gs_placeholder_duplicate: "invalid",
662
+ gs_gaps_mismatch: "invalid",
663
+ gs_presentation_invalid: "invalid",
664
+ gs_bank_id_required: "invalid",
665
+ gs_bank_id_duplicate: "invalid",
666
+ gs_bank_unknown: "invalid",
667
+ gs_choice_source_required: "incomplete",
668
+ gs_choice_source_conflict: "invalid",
669
+ gs_choices_too_few: "incomplete",
670
+ gs_choice_id_required: "invalid",
671
+ gs_choice_id_duplicate: "invalid",
672
+ gs_choice_text_required: "incomplete",
673
+ gs_correct_choice_required: "incomplete",
674
+ gs_correct_choice_unknown: "invalid",
503
675
  // written-response
504
676
  wr_prompt_required: "incomplete",
505
677
  wr_min_words_required: "incomplete",
@@ -633,51 +805,42 @@ function checkSharedOptional(draft) {
633
805
  }
634
806
  var URL_POLICY = 'Use an https:, http:, data: or blob: address, or a path on the same site that starts with a single "/".';
635
807
  var EMBED_URL = "An embed needs the provider's full http(s) embed address, such as https://www.youtube.com/embed/VIDEO_ID.";
636
- function checkMedia(media) {
808
+ function checkMedia(media, prefix = ["media"], schema = MediaSchema) {
637
809
  const issues = [];
638
- const parsed = MediaSchema.safeParse(media, { reportInput: true });
810
+ const at = (...rest) => [...prefix, ...rest];
811
+ const parsed = schema.safeParse(media, { reportInput: true });
639
812
  const schemaIssues = parsed.success ? [] : parsed.error.issues.filter((found) => !refusesEmpty(media, found));
640
813
  const refusal = (field) => schemaIssues.find((found) => found.path[0] === field);
641
814
  const typeUnset = isUnwritten(media.type);
642
815
  if (typeUnset) {
643
- issues.push(
644
- issue("media_type_required", ["media", "type"], "Choose what kind of media this is.")
645
- );
816
+ issues.push(issue("media_type_required", at("type"), "Choose what kind of media this is."));
646
817
  }
647
818
  if (isUnwritten(media.url)) {
648
- issues.push(
649
- issue("media_url_required", ["media", "url"], "Add the address of the media file.")
650
- );
819
+ issues.push(issue("media_url_required", at("url"), "Add the address of the media file."));
651
820
  } else {
652
821
  const refused = refusal("url");
653
822
  if (refused !== void 0) {
654
823
  issues.push(
655
- issue(
656
- "media_url_invalid",
657
- ["media", "url"],
658
- refused.code === "custom" ? EMBED_URL : URL_POLICY
659
- )
824
+ issue("media_url_invalid", at("url"), refused.code === "custom" ? EMBED_URL : URL_POLICY)
660
825
  );
661
826
  }
662
827
  }
663
828
  const needsAlt = media.type === "image" || media.type === "embed";
664
829
  const alt = media.alt;
665
830
  if (needsAlt && isUnwritten(alt) || typeof alt === "string" && alt.trim() === "") {
666
- issues.push(
667
- issue("media_alt_required", ["media", "alt"], "Add a text description of the media.")
668
- );
831
+ issues.push(issue("media_alt_required", at("alt"), "Add a text description of the media."));
669
832
  }
670
833
  const captions = media.captionsUrl;
671
834
  if (typeof captions === "string" && captions.trim() === "") {
672
835
  issues.push(
673
836
  issue(
674
837
  "media_url_required",
675
- ["media", "captionsUrl"],
838
+ at("captionsUrl"),
676
839
  "Add the address of the captions file, or remove the captions."
677
840
  )
678
841
  );
679
842
  } else if (refusal("captionsUrl") !== void 0) {
680
- issues.push(issue("media_url_invalid", ["media", "captionsUrl"], URL_POLICY));
843
+ issues.push(issue("media_url_invalid", at("captionsUrl"), URL_POLICY));
681
844
  }
682
845
  for (const schemaIssue of schemaIssues) {
683
846
  const field = schemaIssue.path[0];
@@ -687,7 +850,7 @@ function checkMedia(media) {
687
850
  issues.push(
688
851
  issue(
689
852
  field === "playback" ? "media_playback_invalid" : "media_invalid",
690
- ["media", ...schemaIssue.path.map(String)],
853
+ at(...schemaIssue.path.map(String)),
691
854
  schemaIssue.message
692
855
  )
693
856
  );
@@ -888,6 +1051,301 @@ function pairsOneToOne(placeholders, blanks) {
888
1051
  return blankIds.every((id) => typeof id === "string" && placeholders.get(id) === 1);
889
1052
  }
890
1053
 
1054
+ // src/authoring/gap-select.ts
1055
+ var MIN_CHOICES = 2;
1056
+ var gapSelectAuthoring = {
1057
+ createDraft: ({ newId }) => ({
1058
+ schemaVersion: "1.0",
1059
+ type: "gap-select",
1060
+ id: newId(),
1061
+ title: "",
1062
+ passage: "",
1063
+ gaps: [],
1064
+ scoringStrategy: "all-or-nothing"
1065
+ }),
1066
+ checkDraft: checkGapSelectDraft
1067
+ };
1068
+ function checkGapSelectDraft(draft) {
1069
+ const issues = checkIdentity(draft, "gap-select");
1070
+ const passage = draft.passage;
1071
+ if (isUnwritten(passage)) {
1072
+ issues.push(issue("gs_passage_required", ["passage"], "Write the passage."));
1073
+ }
1074
+ issues.push(...checkScoringStrategy(draft));
1075
+ if (!isUnset(draft.presentation) && draft.presentation !== "dropdown") {
1076
+ issues.push(
1077
+ issue(
1078
+ "gs_presentation_invalid",
1079
+ ["presentation"],
1080
+ 'The only presentation is "dropdown". Leave it out unless you mean to set it.'
1081
+ )
1082
+ );
1083
+ }
1084
+ const banks = isUnset(draft.banks) ? [] : draft.banks;
1085
+ const bankIds = /* @__PURE__ */ new Set();
1086
+ if (Array.isArray(banks)) {
1087
+ issues.push(...checkBanks(banks, bankIds));
1088
+ }
1089
+ const gaps = isUnset(draft.gaps) ? [] : draft.gaps;
1090
+ const gapIds = [];
1091
+ if (Array.isArray(gaps)) {
1092
+ if (gaps.length === 0) {
1093
+ issues.push(
1094
+ issue(
1095
+ "gs_gaps_required",
1096
+ ["gaps"],
1097
+ "Add at least one gap, and mark where it goes in the passage with {{id}}."
1098
+ )
1099
+ );
1100
+ }
1101
+ issues.push(...checkGaps(gaps, banks, bankIds, gapIds));
1102
+ if (typeof passage === "string" && passage.trim() !== "") {
1103
+ issues.push(...checkPairing2(passage, gaps, gapIds));
1104
+ }
1105
+ }
1106
+ issues.push(...checkSharedOptional(draft));
1107
+ return issues;
1108
+ }
1109
+ function checkBanks(banks, bankIds) {
1110
+ const issues = [];
1111
+ const repeated = /* @__PURE__ */ new Set();
1112
+ for (const [index, bank] of banks.entries()) {
1113
+ if (!isRecord(bank)) {
1114
+ continue;
1115
+ }
1116
+ if (isMissingId(bank.id)) {
1117
+ issues.push(
1118
+ issue("gs_bank_id_required", ["banks", index, "id"], `Word bank ${index + 1} has no id.`)
1119
+ );
1120
+ } else if (typeof bank.id === "string") {
1121
+ if (bankIds.has(bank.id)) {
1122
+ repeated.add(bank.id);
1123
+ }
1124
+ bankIds.add(bank.id);
1125
+ }
1126
+ const name = typeof bank.id === "string" && bank.id !== "" ? `"${bank.id}"` : index + 1;
1127
+ issues.push(...checkChoiceList(bank.choices, ["banks", index, "choices"], `word bank ${name}`));
1128
+ }
1129
+ for (const id of repeated) {
1130
+ issues.push(
1131
+ issue("gs_bank_id_duplicate", ["banks"], `More than one word bank has the id "${id}".`)
1132
+ );
1133
+ }
1134
+ return issues;
1135
+ }
1136
+ function checkGaps(gaps, banks, bankIds, gapIds) {
1137
+ const issues = [];
1138
+ const atGaps = [];
1139
+ const repeated = /* @__PURE__ */ new Set();
1140
+ const seen = /* @__PURE__ */ new Set();
1141
+ for (const [index, gap] of gaps.entries()) {
1142
+ if (!isRecord(gap)) {
1143
+ continue;
1144
+ }
1145
+ const ordinal = index + 1;
1146
+ const name = typeof gap.id === "string" && gap.id !== "" ? `"${gap.id}"` : ordinal;
1147
+ if (isMissingId(gap.id)) {
1148
+ issues.push(issue("gs_gap_id_required", ["gaps", index, "id"], `Gap ${ordinal} has no id.`));
1149
+ } else if (typeof gap.id === "string") {
1150
+ if (seen.has(gap.id)) {
1151
+ repeated.add(gap.id);
1152
+ }
1153
+ seen.add(gap.id);
1154
+ gapIds.push(gap.id);
1155
+ }
1156
+ const hasOwn = !isUnset(gap.choices);
1157
+ const hasBank = !isUnwritten(gap.bankId);
1158
+ const bankBlank = typeof gap.bankId === "string" && gap.bankId.trim() === "";
1159
+ if (bankBlank) {
1160
+ issues.push(
1161
+ issue(
1162
+ "gs_choice_source_required",
1163
+ ["gaps", index, "bankId"],
1164
+ `Choose a word bank for gap ${name}, or remove the empty field.`
1165
+ )
1166
+ );
1167
+ } else if (hasOwn && hasBank) {
1168
+ atGaps.push(
1169
+ issue(
1170
+ "gs_choice_source_conflict",
1171
+ ["gaps"],
1172
+ `Gap ${name} has both its own choices and a word bank. Use one or the other.`
1173
+ )
1174
+ );
1175
+ } else if (!hasOwn && !hasBank) {
1176
+ atGaps.push(
1177
+ issue(
1178
+ "gs_choice_source_required",
1179
+ ["gaps"],
1180
+ `Give gap ${name} a list of choices, or point it at a word bank.`
1181
+ )
1182
+ );
1183
+ }
1184
+ if (hasBank && typeof gap.bankId === "string" && !bankIds.has(gap.bankId)) {
1185
+ atGaps.push(
1186
+ issue(
1187
+ "gs_bank_unknown",
1188
+ ["gaps"],
1189
+ `Gap ${name} uses the word bank "${gap.bankId}", which does not exist.`
1190
+ )
1191
+ );
1192
+ }
1193
+ if (hasOwn) {
1194
+ issues.push(...checkChoiceList(gap.choices, ["gaps", index, "choices"], `gap ${name}`));
1195
+ }
1196
+ issues.push(...checkAnswerKey(gap, index, name, banks, bankIds));
1197
+ }
1198
+ for (const id of repeated) {
1199
+ atGaps.push(issue("gs_gap_id_duplicate", ["gaps"], `More than one gap has the id "${id}".`));
1200
+ }
1201
+ return [...issues, ...atGaps];
1202
+ }
1203
+ function checkAnswerKey(gap, index, name, banks, bankIds) {
1204
+ const key = gap.correctChoiceId;
1205
+ if (isUnwritten(key)) {
1206
+ return [
1207
+ issue(
1208
+ "gs_correct_choice_required",
1209
+ ["gaps", index, "correctChoiceId"],
1210
+ `Mark the correct choice for gap ${name}.`
1211
+ )
1212
+ ];
1213
+ }
1214
+ if (typeof key !== "string") {
1215
+ return [];
1216
+ }
1217
+ const choices = resolveChoices2(gap, banks, bankIds);
1218
+ if (choices === void 0) {
1219
+ return [];
1220
+ }
1221
+ const offered = choices.some((choice) => isRecord(choice) && choice.id === key);
1222
+ return offered ? [] : [
1223
+ issue(
1224
+ "gs_correct_choice_unknown",
1225
+ ["gaps"],
1226
+ `Gap ${name} is marked correct on "${key}", which is not one of its choices.`
1227
+ )
1228
+ ];
1229
+ }
1230
+ function resolveChoices2(gap, banks, bankIds) {
1231
+ const hasOwn = !isUnset(gap.choices);
1232
+ const hasBank = !isUnwritten(gap.bankId);
1233
+ if (hasOwn === hasBank) {
1234
+ return void 0;
1235
+ }
1236
+ if (hasOwn) {
1237
+ return Array.isArray(gap.choices) ? gap.choices : void 0;
1238
+ }
1239
+ if (typeof gap.bankId !== "string" || !bankIds.has(gap.bankId) || !Array.isArray(banks)) {
1240
+ return void 0;
1241
+ }
1242
+ const bank = banks.find((entry) => isRecord(entry) && entry.id === gap.bankId);
1243
+ return isRecord(bank) && Array.isArray(bank.choices) ? bank.choices : void 0;
1244
+ }
1245
+ function checkChoiceList(choices, path, owner) {
1246
+ const issues = [];
1247
+ const list = isUnset(choices) ? [] : choices;
1248
+ if (!Array.isArray(list)) {
1249
+ return issues;
1250
+ }
1251
+ if (list.length < MIN_CHOICES) {
1252
+ issues.push(
1253
+ issue(
1254
+ "gs_choices_too_few",
1255
+ path,
1256
+ `Give ${owner} at least ${MIN_CHOICES} choices to pick from.`
1257
+ )
1258
+ );
1259
+ }
1260
+ const seen = /* @__PURE__ */ new Set();
1261
+ const repeated = /* @__PURE__ */ new Set();
1262
+ for (const [index, choice] of list.entries()) {
1263
+ if (!isRecord(choice)) {
1264
+ continue;
1265
+ }
1266
+ if (isMissingId(choice.id)) {
1267
+ issues.push(
1268
+ issue("gs_choice_id_required", [...path, index, "id"], `Choice ${index + 1} has no id.`)
1269
+ );
1270
+ } else if (typeof choice.id === "string") {
1271
+ if (seen.has(choice.id)) {
1272
+ repeated.add(choice.id);
1273
+ }
1274
+ seen.add(choice.id);
1275
+ }
1276
+ if (isUnwritten(choice.text)) {
1277
+ issues.push(
1278
+ issue(
1279
+ "gs_choice_text_required",
1280
+ [...path, index, "text"],
1281
+ `Write the text of choice ${index + 1}.`
1282
+ )
1283
+ );
1284
+ }
1285
+ }
1286
+ for (const id of repeated) {
1287
+ issues.push(issue("gs_choice_id_duplicate", path, `More than one choice has the id "${id}".`));
1288
+ }
1289
+ return issues;
1290
+ }
1291
+ function checkPairing2(passage, gaps, gapIds) {
1292
+ const issues = [];
1293
+ const placeholders = /* @__PURE__ */ new Map();
1294
+ for (const match of passage.matchAll(PLACEHOLDER_RE)) {
1295
+ const id = match[1];
1296
+ placeholders.set(id, (_nullishCoalesce(placeholders.get(id), () => ( 0))) + 1);
1297
+ }
1298
+ const ids = new Set(gapIds);
1299
+ for (const [id, count] of placeholders) {
1300
+ if (count > 1) {
1301
+ issues.push(
1302
+ issue(
1303
+ "gs_placeholder_duplicate",
1304
+ ["passage"],
1305
+ `{{${id}}} appears more than once in the passage. Each gap goes in one place.`
1306
+ )
1307
+ );
1308
+ }
1309
+ if (!ids.has(id)) {
1310
+ issues.push(
1311
+ issue(
1312
+ "gs_gap_missing",
1313
+ ["passage"],
1314
+ `The passage has {{${id}}}, but there is no gap with that id.`
1315
+ )
1316
+ );
1317
+ }
1318
+ }
1319
+ for (const id of ids) {
1320
+ if (!placeholders.has(id)) {
1321
+ issues.push(
1322
+ issue(
1323
+ "gs_placeholder_missing",
1324
+ ["passage"],
1325
+ `Gap "${id}" is not in the passage. Put {{${id}}} where it goes.`
1326
+ )
1327
+ );
1328
+ }
1329
+ }
1330
+ if (issues.length === 0 && !pairsOneToOne2(placeholders, gaps)) {
1331
+ issues.push(
1332
+ issue(
1333
+ "gs_gaps_mismatch",
1334
+ ["passage"],
1335
+ "The gaps and the {{id}} placeholders in the passage do not pair up one to one."
1336
+ )
1337
+ );
1338
+ }
1339
+ return issues;
1340
+ }
1341
+ function pairsOneToOne2(placeholders, gaps) {
1342
+ const ids = gaps.filter(isRecord).map((gap) => gap.id);
1343
+ if (new Set(ids).size !== ids.length || placeholders.size !== ids.length) {
1344
+ return false;
1345
+ }
1346
+ return ids.every((id) => typeof id === "string" && placeholders.get(id) === 1);
1347
+ }
1348
+
891
1349
  // src/authoring/multiple-choice.ts
892
1350
  var MIN_OPTIONS = 2;
893
1351
  var MAX_OPTIONS = 26;
@@ -967,6 +1425,9 @@ function checkMultipleChoiceDraft(draft) {
967
1425
  if (option.isCorrect === true) {
968
1426
  correct += 1;
969
1427
  }
1428
+ if (isRecord(option.media)) {
1429
+ issues.push(...checkOptionMedia(option.media, index, ordinal));
1430
+ }
970
1431
  }
971
1432
  for (const id of repeated) {
972
1433
  issues.push(
@@ -994,6 +1455,20 @@ function checkMultipleChoiceDraft(draft) {
994
1455
  issues.push(...checkSharedOptional(draft));
995
1456
  return issues;
996
1457
  }
1458
+ function checkOptionMedia(media, index, ordinal) {
1459
+ const path = ["options", index, "media"];
1460
+ const kind = media.type;
1461
+ if (kind === "video" || kind === "embed") {
1462
+ return [
1463
+ issue(
1464
+ "mc_option_media_kind",
1465
+ [...path, "type"],
1466
+ `Option ${ordinal} carries ${kind === "embed" ? "an embedded player" : "a video"}, which cannot be an option: its controls swallow the click that selects the answer. Use a picture or a recording.`
1467
+ )
1468
+ ];
1469
+ }
1470
+ return checkMedia(media, path, MultipleChoiceOptionMediaSchema);
1471
+ }
997
1472
 
998
1473
  // src/authoring/written-response.ts
999
1474
  var writtenResponseAuthoring = {
@@ -1181,6 +1656,38 @@ function scoreFillInTheBlanks(data, response) {
1181
1656
  return { score: scoreValue, maxScore: 1, feedback: null, details };
1182
1657
  }
1183
1658
 
1659
+ // src/scoring/activity-scorers/gap-select.ts
1660
+ function choicesFor(data, gap) {
1661
+ if (gap.choices !== void 0) {
1662
+ return gap.choices;
1663
+ }
1664
+ return _nullishCoalesce(_optionalChain([data, 'access', _13 => _13.banks, 'optionalAccess', _14 => _14.find, 'call', _15 => _15((bank) => bank.id === gap.bankId), 'optionalAccess', _16 => _16.choices]), () => ( []));
1665
+ }
1666
+ function scoreGapSelect(data, response) {
1667
+ const details = [];
1668
+ const perGapCorrect = [];
1669
+ for (const gap of data.gaps) {
1670
+ const raw = response.selections[gap.id];
1671
+ const selected = typeof raw === "string" ? raw : "";
1672
+ const choices = choicesFor(data, gap);
1673
+ const offered = choices.some((choice) => choice.id === selected);
1674
+ const answered = selected !== "" && offered;
1675
+ const correct = answered && selected === gap.correctChoiceId;
1676
+ perGapCorrect.push(correct);
1677
+ details.push({
1678
+ itemId: gap.id,
1679
+ correct,
1680
+ outcome: correct ? "correct" : answered ? "incorrect" : "incorrect-omission",
1681
+ learnerResponse: [selected],
1682
+ correctResponse: [gap.correctChoiceId],
1683
+ weight: 1
1684
+ });
1685
+ }
1686
+ const correctGaps = perGapCorrect.filter(Boolean).length;
1687
+ const scoreValue = data.scoringStrategy === "all-or-nothing" ? allOrNothingStrategy(perGapCorrect) : partialBlankStrategy(correctGaps, data.gaps.length);
1688
+ return { score: scoreValue, maxScore: 1, feedback: null, details };
1689
+ }
1690
+
1184
1691
  // src/scoring/activity-scorers/multiple-choice.ts
1185
1692
  function scoreMultipleChoice(data, response) {
1186
1693
  const optionById = new Map(data.options.map((option) => [option.id, option]));
@@ -1190,7 +1697,7 @@ function scoreMultipleChoice(data, response) {
1190
1697
  let scoreValue;
1191
1698
  if (data.scoringStrategy === "all-or-nothing") {
1192
1699
  if (data.mode === "single") {
1193
- scoreValue = response.selectedOptionIds.length === 1 && _optionalChain([optionById, 'access', _10 => _10.get, 'call', _11 => _11(response.selectedOptionIds[0]), 'optionalAccess', _12 => _12.isCorrect]) === true ? 1 : 0;
1700
+ scoreValue = response.selectedOptionIds.length === 1 && _optionalChain([optionById, 'access', _17 => _17.get, 'call', _18 => _18(response.selectedOptionIds[0]), 'optionalAccess', _19 => _19.isCorrect]) === true ? 1 : 0;
1194
1701
  } else {
1195
1702
  const correctIds = data.options.filter((o) => o.isCorrect).map((o) => o.id);
1196
1703
  const allCorrectSelected = correctIds.every((id) => selected.has(id));
@@ -1201,7 +1708,7 @@ function scoreMultipleChoice(data, response) {
1201
1708
  let incorrectSelected = 0;
1202
1709
  for (const id of selected) {
1203
1710
  const option = optionById.get(id);
1204
- if (_optionalChain([option, 'optionalAccess', _13 => _13.isCorrect])) {
1711
+ if (_optionalChain([option, 'optionalAccess', _20 => _20.isCorrect])) {
1205
1712
  correctSelected += 1;
1206
1713
  } else {
1207
1714
  incorrectSelected += 1;
@@ -1280,6 +1787,38 @@ var MULTIPLE_CHOICE_FIELD_POLICY = {
1280
1787
  id: "public",
1281
1788
  text: "public",
1282
1789
  isCorrect: "answer-key",
1790
+ feedback: "answer-key",
1791
+ // Public, and classified key by key rather than as one leaf — the lesson
1792
+ // `media`, `rubric` and `feedback` each taught: a scalar classification
1793
+ // assigns the author's object by reference without recursing, so an
1794
+ // unclassified key parked inside it survives redact() AND assertRedacted().
1795
+ // An option's picture or recording IS the thing the learner picks, so
1796
+ // stripping it would ship a row of blank options.
1797
+ media: {
1798
+ type: "public",
1799
+ url: "public",
1800
+ alt: "public",
1801
+ captionsUrl: "public"
1802
+ }
1803
+ }
1804
+ };
1805
+ var GAP_SELECT_FIELD_POLICY = {
1806
+ ...SHARED_PUBLIC_FIELDS,
1807
+ passage: "public",
1808
+ passageHtml: "public",
1809
+ presentation: "public",
1810
+ shuffleChoices: "public",
1811
+ scoringStrategy: "answer-key",
1812
+ feedback: FEEDBACK_FIELD_POLICY,
1813
+ banks: {
1814
+ id: "public",
1815
+ choices: { id: "public", text: "public" }
1816
+ },
1817
+ gaps: {
1818
+ id: "public",
1819
+ bankId: "public",
1820
+ choices: { id: "public", text: "public" },
1821
+ correctChoiceId: "answer-key",
1283
1822
  feedback: "answer-key"
1284
1823
  }
1285
1824
  };
@@ -1331,7 +1870,7 @@ var multipleChoiceType = defineActivityType({
1331
1870
  // exact optionals. Structurally identical at runtime — cast is type-level only.
1332
1871
  schema: MultipleChoiceDataSchema,
1333
1872
  scoring: { kind: "sync", score: scoreMultipleChoice },
1334
- isAnswered: (response) => (_nullishCoalesce(_optionalChain([response, 'optionalAccess', _14 => _14.selectedOptionIds, 'access', _15 => _15.length]), () => ( 0))) > 0,
1873
+ isAnswered: (response) => (_nullishCoalesce(_optionalChain([response, 'optionalAccess', _21 => _21.selectedOptionIds, 'access', _22 => _22.length]), () => ( 0))) > 0,
1335
1874
  fieldPolicy: MULTIPLE_CHOICE_FIELD_POLICY,
1336
1875
  redactedSchema: RedactedMultipleChoiceDataSchema,
1337
1876
  interop: {
@@ -1348,7 +1887,7 @@ var fillInTheBlanksType = defineActivityType({
1348
1887
  type: "fill-in-the-blanks",
1349
1888
  schema: FillInTheBlanksDataSchema,
1350
1889
  scoring: { kind: "sync", score: scoreFillInTheBlanks },
1351
- isAnswered: (response) => Object.values(_nullishCoalesce(_optionalChain([response, 'optionalAccess', _16 => _16.answers]), () => ( {}))).some((answer) => answer.trim().length > 0),
1890
+ isAnswered: (response) => Object.values(_nullishCoalesce(_optionalChain([response, 'optionalAccess', _23 => _23.answers]), () => ( {}))).some((answer) => answer.trim().length > 0),
1352
1891
  fieldPolicy: FILL_IN_THE_BLANKS_FIELD_POLICY,
1353
1892
  redactedSchema: RedactedFillInTheBlanksDataSchema,
1354
1893
  interop: {
@@ -1371,14 +1910,14 @@ var writtenResponseType = defineActivityType({
1371
1910
  kind: "deferred",
1372
1911
  reason: "requires_async_grading",
1373
1912
  partial: (data, response) => {
1374
- const wordCount = countWords(_nullishCoalesce(_optionalChain([response, 'optionalAccess', _17 => _17.text]), () => ( "")));
1913
+ const wordCount = countWords(_nullishCoalesce(_optionalChain([response, 'optionalAccess', _24 => _24.text]), () => ( "")));
1375
1914
  return {
1376
1915
  withinWordBounds: response !== void 0 && wordCount >= data.minWords && wordCount <= data.maxWords,
1377
1916
  wordCount
1378
1917
  };
1379
1918
  }
1380
1919
  },
1381
- isAnswered: (response) => (_nullishCoalesce(_optionalChain([response, 'optionalAccess', _18 => _18.text, 'access', _19 => _19.trim, 'call', _20 => _20(), 'access', _21 => _21.length]), () => ( 0))) > 0,
1920
+ isAnswered: (response) => (_nullishCoalesce(_optionalChain([response, 'optionalAccess', _25 => _25.text, 'access', _26 => _26.trim, 'call', _27 => _27(), 'access', _28 => _28.length]), () => ( 0))) > 0,
1382
1921
  fieldPolicy: WRITTEN_RESPONSE_FIELD_POLICY,
1383
1922
  redactedSchema: RedactedWrittenResponseDataSchema,
1384
1923
  interop: {
@@ -1389,9 +1928,43 @@ var writtenResponseType = defineActivityType({
1389
1928
  interactions: ["text-changed", "submitted"],
1390
1929
  authoring: writtenResponseAuthoring
1391
1930
  });
1931
+ var gapSelectType = defineActivityType({
1932
+ type: "gap-select",
1933
+ schema: GapSelectDataSchema,
1934
+ scoring: { kind: "sync", score: scoreGapSelect },
1935
+ isAnswered: (response) => Object.values(_nullishCoalesce(_optionalChain([response, 'optionalAccess', _29 => _29.selections]), () => ( {}))).some((choiceId) => choiceId !== ""),
1936
+ fieldPolicy: GAP_SELECT_FIELD_POLICY,
1937
+ redactedSchema: RedactedGapSelectDataSchema,
1938
+ interop: {
1939
+ xapiActivityTypeIri: "http://adlnet.gov/expapi/activities/cmi.interaction",
1940
+ // `matching`, not `choice` or `fill-in`. The learner pairs a set of sources
1941
+ // (the gaps) with a set of targets (the choices), which is exactly what the
1942
+ // xAPI matching interaction describes, and it is the only built-in type
1943
+ // whose pattern can name WHICH gap took which answer. `fill-in` — what the
1944
+ // Fill-in-the-Blanks descriptor uses — would flatten the gaps into an
1945
+ // ordered list of strings and lose that.
1946
+ xapiInteractionType: "matching",
1947
+ correctResponsesPattern: (data) => [
1948
+ data.gaps.map((gap) => `${gap.id}[.]${gap.correctChoiceId}`).join("[,]")
1949
+ ]
1950
+ },
1951
+ interactions: ["gap-selected", "submitted"],
1952
+ authoring: gapSelectAuthoring
1953
+ });
1392
1954
  registerActivityType(multipleChoiceType);
1393
1955
  registerActivityType(fillInTheBlanksType);
1394
1956
  registerActivityType(writtenResponseType);
1957
+ registerActivityType(gapSelectType);
1958
+
1959
+
1960
+
1961
+
1962
+
1963
+
1964
+
1965
+
1966
+
1967
+
1395
1968
 
1396
1969
 
1397
1970
 
@@ -1432,5 +2005,5 @@ registerActivityType(writtenResponseType);
1432
2005
 
1433
2006
 
1434
2007
 
1435
- exports.ActivitySchemaError = ActivitySchemaError; exports.UnknownActivityTypeError = UnknownActivityTypeError; exports.RedactedScoringError = RedactedScoringError; exports.DeferredScoringError = DeferredScoringError; exports.FeedbackSchema = FeedbackSchema; exports.MediaUrlSchema = MediaUrlSchema; exports.NativeControlHintSchema = NativeControlHintSchema; exports.MediaPlaybackSchema = MediaPlaybackSchema; exports.MediaSchema = MediaSchema; exports.RedactedMediaSchema = RedactedMediaSchema; exports.TextMatchPolicySchema = TextMatchPolicySchema; exports.BlankConfigSchema = BlankConfigSchema; exports.FillInTheBlanksDataSchema = FillInTheBlanksDataSchema; exports.DRAFT_ISSUE_SEVERITY = DRAFT_ISSUE_SEVERITY; exports.issue = issue; exports.isRecord = isRecord; exports.refusesEmpty = refusesEmpty; exports.countWords = countWords; exports.MultipleChoiceOptionSchema = MultipleChoiceOptionSchema; exports.MultipleChoiceDataSchema = MultipleChoiceDataSchema; exports.WrittenResponseRubricCriterionSchema = WrittenResponseRubricCriterionSchema; exports.WrittenResponseRubricSchema = WrittenResponseRubricSchema; exports.WrittenResponseDataSchema = WrittenResponseDataSchema; exports.RedactedMultipleChoiceOptionSchema = RedactedMultipleChoiceOptionSchema; exports.RedactedMultipleChoiceDataSchema = RedactedMultipleChoiceDataSchema; exports.RedactedBlankConfigSchema = RedactedBlankConfigSchema; exports.RedactedFillInTheBlanksDataSchema = RedactedFillInTheBlanksDataSchema; exports.RedactedWrittenResponseDataSchema = RedactedWrittenResponseDataSchema; exports.levenshteinDistance = levenshteinDistance; exports.matchText = matchText; exports.defineActivityType = defineActivityType; exports.registerActivityType = registerActivityType; exports.getActivityTypeDescriptor = getActivityTypeDescriptor; exports.registeredActivityTypes = registeredActivityTypes; exports.MEDIA_FIELD_POLICY = MEDIA_FIELD_POLICY; exports.multipleChoiceType = multipleChoiceType; exports.fillInTheBlanksType = fillInTheBlanksType; exports.writtenResponseType = writtenResponseType;
1436
- //# sourceMappingURL=chunk-CVRALNUK.cjs.map
2008
+ exports.ActivitySchemaError = ActivitySchemaError; exports.UnknownActivityTypeError = UnknownActivityTypeError; exports.RedactedScoringError = RedactedScoringError; exports.DeferredScoringError = DeferredScoringError; exports.FeedbackSchema = FeedbackSchema; exports.MediaUrlSchema = MediaUrlSchema; exports.NativeControlHintSchema = NativeControlHintSchema; exports.MediaPlaybackSchema = MediaPlaybackSchema; exports.MediaSchema = MediaSchema; exports.RedactedMediaSchema = RedactedMediaSchema; exports.TextMatchPolicySchema = TextMatchPolicySchema; exports.BlankConfigSchema = BlankConfigSchema; exports.FillInTheBlanksDataSchema = FillInTheBlanksDataSchema; exports.DRAFT_ISSUE_SEVERITY = DRAFT_ISSUE_SEVERITY; exports.issue = issue; exports.isRecord = isRecord; exports.refusesEmpty = refusesEmpty; exports.MultipleChoiceOptionMediaSchema = MultipleChoiceOptionMediaSchema; exports.MultipleChoiceOptionSchema = MultipleChoiceOptionSchema; exports.MultipleChoiceDataSchema = MultipleChoiceDataSchema; exports.countWords = countWords; exports.GapSelectChoiceSchema = GapSelectChoiceSchema; exports.GapSelectBankSchema = GapSelectBankSchema; exports.GapSelectGapSchema = GapSelectGapSchema; exports.GapSelectDataSchema = GapSelectDataSchema; exports.WrittenResponseRubricCriterionSchema = WrittenResponseRubricCriterionSchema; exports.WrittenResponseRubricSchema = WrittenResponseRubricSchema; exports.WrittenResponseDataSchema = WrittenResponseDataSchema; exports.RedactedMultipleChoiceOptionMediaSchema = RedactedMultipleChoiceOptionMediaSchema; exports.RedactedMultipleChoiceOptionSchema = RedactedMultipleChoiceOptionSchema; exports.RedactedMultipleChoiceDataSchema = RedactedMultipleChoiceDataSchema; exports.RedactedBlankConfigSchema = RedactedBlankConfigSchema; exports.RedactedFillInTheBlanksDataSchema = RedactedFillInTheBlanksDataSchema; exports.RedactedGapSelectChoiceSchema = RedactedGapSelectChoiceSchema; exports.RedactedGapSelectBankSchema = RedactedGapSelectBankSchema; exports.RedactedGapSelectGapSchema = RedactedGapSelectGapSchema; exports.RedactedGapSelectDataSchema = RedactedGapSelectDataSchema; exports.RedactedWrittenResponseDataSchema = RedactedWrittenResponseDataSchema; exports.levenshteinDistance = levenshteinDistance; exports.matchText = matchText; exports.defineActivityType = defineActivityType; exports.registerActivityType = registerActivityType; exports.getActivityTypeDescriptor = getActivityTypeDescriptor; exports.registeredActivityTypes = registeredActivityTypes; exports.MEDIA_FIELD_POLICY = MEDIA_FIELD_POLICY; exports.multipleChoiceType = multipleChoiceType; exports.fillInTheBlanksType = fillInTheBlanksType; exports.writtenResponseType = writtenResponseType;
2009
+ //# sourceMappingURL=chunk-7ODQRNR4.cjs.map