@intellectif/lk-react 7.0.1 → 7.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/CHANGELOG.md +79 -0
  2. package/README.md +5 -2
  3. package/dist/{WrittenResponse-BJL4dGsR.d.ts → WrittenResponse-BdD1-2tQ.d.ts} +3 -1
  4. package/dist/{WrittenResponse-zWT-Go1g.d.cts → WrittenResponse-Cb_Fg4fc.d.cts} +3 -1
  5. package/dist/{chunk-C3HR55JD.js → chunk-365FW4BO.js} +24 -13
  6. package/dist/chunk-365FW4BO.js.map +1 -0
  7. package/dist/{chunk-VHULVDPG.js → chunk-4WLZONV6.js} +29 -14
  8. package/dist/chunk-4WLZONV6.js.map +1 -0
  9. package/dist/{chunk-6F4X2NKL.js → chunk-6DYGQKYD.js} +5 -5
  10. package/dist/chunk-6DYGQKYD.js.map +1 -0
  11. package/dist/chunk-7CQ7NUMX.js +113 -0
  12. package/dist/chunk-7CQ7NUMX.js.map +1 -0
  13. package/dist/{chunk-6DSA75TA.cjs → chunk-7RLLJHCI.cjs} +35 -20
  14. package/dist/chunk-7RLLJHCI.cjs.map +1 -0
  15. package/dist/{chunk-FB6XKQFV.cjs → chunk-B4OR4LPL.cjs} +23 -23
  16. package/dist/chunk-B4OR4LPL.cjs.map +1 -0
  17. package/dist/{chunk-UFWF3BKA.cjs → chunk-CKATY6WH.cjs} +13 -24
  18. package/dist/chunk-CKATY6WH.cjs.map +1 -0
  19. package/dist/{chunk-52A5UNCH.js → chunk-F3EBTOOH.js} +13 -24
  20. package/dist/chunk-F3EBTOOH.js.map +1 -0
  21. package/dist/{chunk-PPJ47THY.cjs → chunk-GMPSLHGL.cjs} +27 -29
  22. package/dist/chunk-GMPSLHGL.cjs.map +1 -0
  23. package/dist/{chunk-XS2SZINH.js → chunk-GTX4IWIT.js} +10 -13
  24. package/dist/chunk-GTX4IWIT.js.map +1 -0
  25. package/dist/{chunk-3VQV7ICQ.js → chunk-MU5WELLJ.js} +18 -18
  26. package/dist/chunk-MU5WELLJ.js.map +1 -0
  27. package/dist/{chunk-ABHJW4LC.cjs → chunk-MVJQ3KVE.cjs} +11 -14
  28. package/dist/chunk-MVJQ3KVE.cjs.map +1 -0
  29. package/dist/{chunk-XGQKID2P.cjs → chunk-RWR77BOV.cjs} +29 -18
  30. package/dist/chunk-RWR77BOV.cjs.map +1 -0
  31. package/dist/chunk-UGLMKKNA.cjs +120 -0
  32. package/dist/chunk-UGLMKKNA.cjs.map +1 -0
  33. package/dist/{chunk-PGEG7UZJ.cjs → chunk-YDSFFFR4.cjs} +5 -5
  34. package/dist/chunk-YDSFFFR4.cjs.map +1 -0
  35. package/dist/{chunk-7YLY3G3E.js → chunk-ZCQXFIB3.js} +23 -25
  36. package/dist/chunk-ZCQXFIB3.js.map +1 -0
  37. package/dist/components/ActivitySequence.cjs +9 -8
  38. package/dist/components/ActivitySequence.d.cts +16 -3
  39. package/dist/components/ActivitySequence.d.ts +16 -3
  40. package/dist/components/ActivitySequence.js +8 -7
  41. package/dist/components/FillInTheBlanks.cjs +5 -4
  42. package/dist/components/FillInTheBlanks.d.cts +1 -1
  43. package/dist/components/FillInTheBlanks.d.ts +1 -1
  44. package/dist/components/FillInTheBlanks.js +4 -3
  45. package/dist/components/MultipleChoice.cjs +5 -4
  46. package/dist/components/MultipleChoice.d.cts +1 -1
  47. package/dist/components/MultipleChoice.d.ts +1 -1
  48. package/dist/components/MultipleChoice.js +4 -3
  49. package/dist/components/StimulusPanel.cjs +4 -3
  50. package/dist/components/StimulusPanel.d.cts +4 -2
  51. package/dist/components/StimulusPanel.d.ts +4 -2
  52. package/dist/components/StimulusPanel.js +3 -2
  53. package/dist/components/WrittenResponse.cjs +5 -4
  54. package/dist/components/WrittenResponse.d.cts +3 -3
  55. package/dist/components/WrittenResponse.d.ts +3 -3
  56. package/dist/components/WrittenResponse.js +4 -3
  57. package/dist/i18n/LkIntlProvider.cjs +33 -0
  58. package/dist/i18n/LkIntlProvider.cjs.map +1 -0
  59. package/dist/i18n/LkIntlProvider.d.cts +71 -0
  60. package/dist/i18n/LkIntlProvider.d.ts +71 -0
  61. package/dist/i18n/LkIntlProvider.js +4 -0
  62. package/dist/i18n/LkIntlProvider.js.map +1 -0
  63. package/dist/index.cjs +40 -15
  64. package/dist/index.cjs.map +1 -1
  65. package/dist/index.d.cts +3 -2
  66. package/dist/index.d.ts +3 -2
  67. package/dist/index.js +9 -8
  68. package/dist/index.js.map +1 -1
  69. package/dist/{types-eXX4hzSj.d.cts → strings-CfgmSQzU.d.cts} +163 -3
  70. package/dist/{types-eXX4hzSj.d.ts → strings-CfgmSQzU.d.ts} +163 -3
  71. package/dist/theme/skin.css +4 -4
  72. package/package.json +13 -3
  73. package/dist/chunk-3VQV7ICQ.js.map +0 -1
  74. package/dist/chunk-52A5UNCH.js.map +0 -1
  75. package/dist/chunk-6DSA75TA.cjs.map +0 -1
  76. package/dist/chunk-6F4X2NKL.js.map +0 -1
  77. package/dist/chunk-7YLY3G3E.js.map +0 -1
  78. package/dist/chunk-ABHJW4LC.cjs.map +0 -1
  79. package/dist/chunk-C3HR55JD.js.map +0 -1
  80. package/dist/chunk-FB6XKQFV.cjs.map +0 -1
  81. package/dist/chunk-PGEG7UZJ.cjs.map +0 -1
  82. package/dist/chunk-PPJ47THY.cjs.map +0 -1
  83. package/dist/chunk-UFWF3BKA.cjs.map +0 -1
  84. package/dist/chunk-VHULVDPG.js.map +0 -1
  85. package/dist/chunk-XGQKID2P.cjs.map +0 -1
  86. package/dist/chunk-XS2SZINH.js.map +0 -1
@@ -1,4 +1,4 @@
1
- import { ActivityData, ActivityResult, LearnerResponse, ItemOutcome, MediaPlayLedgerEntry, MediaPlayClaim, MediaPlayGrant, InteractionEvent, ThemeTokens, RedactedActivityData, ItemGroup, SequenceEntry } from '@intellectif/lk-core';
1
+ import { ActivityData, ActivityResult, LearnerResponse, ItemOutcome, MediaPlayLedgerEntry, MediaPlayClaim, MediaPlayGrant, InteractionEvent, ThemeTokens, RedactedActivityData, ItemGroup, SequenceEntry, Stimulus } from '@intellectif/lk-core';
2
2
 
3
3
  /**
4
4
  * How an activity is being presented. This is the single switch that decides
@@ -116,9 +116,26 @@ interface ActivityProps<TData extends ActivityData = ActivityData> {
116
116
  mediaBudget?: MediaBudgetBinding;
117
117
  /** Translations for the audio transport chrome. See {@link MediaTransportStrings}. */
118
118
  mediaStrings?: Partial<MediaTransportStrings>;
119
+ /**
120
+ * Overrides the SDK's chrome text for this activity, layered on whatever
121
+ * `LkIntlProvider` supplies. `mediaStrings` still works and is merged after
122
+ * this, so an existing 0.8.x call site keeps behaving as it did.
123
+ */
124
+ strings?: LkStringsOverride;
119
125
  onInteraction?: (event: InteractionEvent) => void;
120
126
  /** Per-instance token overrides, applied as inline CSS vars on the root. */
121
127
  theme?: Partial<ThemeTokens>;
128
+ /**
129
+ * BCP 47 tag stamped as `lang` on this component's root. This is the
130
+ * INTERFACE language — the SDK's own chrome renders inside that element — so
131
+ * it should carry the same value you give `<LkIntlProvider locale>`. Passing
132
+ * a different one re-declares the language of every SDK string in this
133
+ * subtree without changing the words.
134
+ *
135
+ * It is NOT `data.locale`, which labels xAPI statements only. Authored
136
+ * content in another language belongs on `stimulus.locale`, which
137
+ * `<StimulusPanel>` puts on the passage alone.
138
+ */
122
139
  locale?: string;
123
140
  disabled?: boolean;
124
141
  }
@@ -186,7 +203,12 @@ type RedactedItemGroupData = ItemGroup<RedactedActivityData> & {
186
203
  redacted: true;
187
204
  };
188
205
  /**
189
- * Every English string the SDK's audio transport renders.
206
+ * Every word the SDK's audio transport renders.
207
+ *
208
+ * Words, not characters: the `m:ss / m:ss` clock is digits and punctuation and
209
+ * is formatted by the component, because it reads identically in every locale
210
+ * this SDK targets. The scrubber's SPOKEN value does have a word in it and does
211
+ * have a key ({@link MediaTransportStrings.timeValue}).
190
212
  *
191
213
  * Supply them to translate it. These are the highest-stakes strings on a
192
214
  * listening paper — "No plays remaining" decides whether a learner believes
@@ -204,6 +226,12 @@ interface MediaTransportStrings {
204
226
  volume: string;
205
227
  speed: string;
206
228
  seek: string;
229
+ /**
230
+ * Spoken value of the scrubber, e.g. `('1:05', '4:30') => '1:05 of 4:30'`.
231
+ * Takes ALREADY-FORMATTED `m:ss` strings: a translation should not have to
232
+ * reimplement the clock to change the word between them.
233
+ */
234
+ timeValue: (elapsed: string, duration: string) => string;
207
235
  /** e.g. `(1, 2) => '1 of 2 plays remaining'`. */
208
236
  playsRemaining: (remaining: number, max: number) => string;
209
237
  noPlaysRemaining: string;
@@ -304,4 +332,136 @@ interface SequenceMediaBudget {
304
332
  strings?: Partial<MediaTransportStrings>;
305
333
  }
306
334
 
307
- export { type ActivityProps as A, type HtmlSanitizer as H, type MediaBudgetBinding as M, type RenderMode as R, type SequenceMediaBudget as S, type MediaTransportStrings as a, type Renderable as b, type RenderableActivity as c, asRenderable as d, asRenderableSequence as e };
335
+ /**
336
+ * Every string the SDK's own chrome renders.
337
+ *
338
+ * Until 0.8.1 these were literals, and `locale` set only the `lang` attribute —
339
+ * so a learner sitting a Spanish paper read a Spanish passage inside an English
340
+ * scaffold, and "No plays remaining" on a listening exam was untranslatable.
341
+ * This is the whole surface, in one place, so a consumer can replace it.
342
+ *
343
+ * **Interpolation and plurals are functions, not format strings.** A message
344
+ * like `"Question {n} of {total}"` needs a parser, and a parser needs plural
345
+ * rules for every locale the SDK does not know. A function hands both problems
346
+ * to the consumer, who has `Intl.PluralRules` and knows their own language:
347
+ *
348
+ * ```ts
349
+ * questionProgress: (index, total) => `Pregunta ${index} de ${total}`,
350
+ * wordCount: (n) => `${n} ${n === 1 ? 'palabra' : 'palabras'}`,
351
+ * ```
352
+ *
353
+ * It also lets a translation reorder its arguments, which a positional token
354
+ * cannot, and TypeScript checks the arity. No parser, no message catalogue
355
+ * format, no runtime dependency.
356
+ *
357
+ * **What is deliberately NOT here: thrown errors.** Every `throw` in this
358
+ * package is addressed to the developer who wired the component up, not to the
359
+ * learner. Translating them would make them unsearchable and would put the one
360
+ * audience who can act on them behind a translation. They stay English.
361
+ */
362
+ interface LkStrings {
363
+ /** Multiple Choice submit button, and Written Response. */
364
+ submit: string;
365
+ /** Fill-in-the-Blanks in `practice`: the learner is checking their own work. */
366
+ checkAnswers: string;
367
+ /** Fill-in-the-Blanks in `exam`: nothing is checked on the client. */
368
+ submitAnswers: string;
369
+ /**
370
+ * The scored result, read by a screen reader on submit.
371
+ *
372
+ * `percent` is already rounded to a whole number. The authored overall
373
+ * feedback is appended SEPARATELY by the component, so a translation of this
374
+ * sentence never has to carry the author's words — the two were previously
375
+ * concatenated into one literal, which made the score sentence untranslatable
376
+ * without also touching authored content.
377
+ */
378
+ scoreAnnouncement: (percent: number, passed: boolean) => string;
379
+ /** Prefix announced on submit, before the score sentence. */
380
+ answerSubmitted: string;
381
+ /** An answer submitted for grading that has none yet. Never rendered as 0%. */
382
+ notGradedYet: string;
383
+ /** An outcome that carries no grade and never will. */
384
+ noGradeAvailable: string;
385
+ showFeedback: string;
386
+ hideFeedback: string;
387
+ /** Accessible name of a blank's input. `ordinal` is 1-based. */
388
+ blankLabel: (ordinal: number) => string;
389
+ showHint: string;
390
+ hideHint: string;
391
+ /**
392
+ * Announced when an essay is handed in. Distinct from {@link LkStrings.awaitingGrade},
393
+ * which is the `review`-mode state of a submission made earlier: this one
394
+ * confirms the act, and is the only thing a learner hears at the moment they
395
+ * commit work that nothing on the client will grade.
396
+ */
397
+ responseSubmitted: string;
398
+ /** Live word counter. Needs the count for languages whose plural rules differ. */
399
+ wordCount: (count: number) => string;
400
+ /** The authored bounds, e.g. "50–200 words". `min` is 0 when unbounded below. */
401
+ wordBounds: (min: number, max: number) => string;
402
+ /** A rubric criterion the grader marked not applicable. */
403
+ notApplicable: string;
404
+ /** A returned grade flagged for a human to look at — learner-visible by design. */
405
+ awaitingHumanReview: string;
406
+ /** Submitted, no grade back yet. */
407
+ awaitingGrade: string;
408
+ /** The grader could not produce a grade. */
409
+ couldNotBeGraded: string;
410
+ previous: string;
411
+ next: string;
412
+ /** Progress label, also the question region's accessible name. `index` is 1-based. */
413
+ questionProgress: (index: number, total: number) => string;
414
+ /** Shown when no renderer is registered for an activity type. */
415
+ unsupportedActivity: string;
416
+ /**
417
+ * Accessible name of a provider iframe whose author supplied no `alt`.
418
+ * `MediaSchema` requires `alt` on an embed, so validated content never
419
+ * reaches this — but `validateActivity` is opt-in, and a screen-reader
420
+ * learner meeting an unvalidated one should not meet it in English.
421
+ */
422
+ embeddedMedia: string;
423
+ /**
424
+ * Accessible name of the stimulus region when the author gave it no title.
425
+ * Keyed by `Stimulus['kind']`, because "Recording" and "Passage" do not share
426
+ * a gender or an article in every language.
427
+ */
428
+ stimulusKind: Record<Stimulus['kind'], string>;
429
+ /**
430
+ * How far the material carries, e.g. "Questions 3–8". Called with equal
431
+ * `first` and `last` for a single question, so a translation can choose its
432
+ * own singular rather than receive a pre-pluralised English one.
433
+ */
434
+ stimulusRange: (first: number, last: number) => string;
435
+ /** Production fallback when an activity throws during render. */
436
+ activityFailed: string;
437
+ /** The same, when the activity's title is known. */
438
+ activityFailedNamed: (title: string) => string;
439
+ /** Production fallback when the activity has no title to name. */
440
+ activityFailedUnnamed: string;
441
+ /**
442
+ * The listening-paper transport. Shipped in 0.8.0 as its own `mediaStrings`
443
+ * prop, which still works — this is the same shape, reachable from the one
444
+ * provider so a consumer does not wire two mechanisms.
445
+ */
446
+ media: MediaTransportStrings;
447
+ }
448
+ /** A partial override, nested one level for {@link LkStrings.media}. */
449
+ type LkStringsOverride = Partial<Omit<LkStrings, 'media' | 'stimulusKind'>> & {
450
+ media?: Partial<MediaTransportStrings>;
451
+ stimulusKind?: Partial<Record<Stimulus['kind'], string>>;
452
+ };
453
+ /**
454
+ * English defaults, byte-identical to what every component rendered before the
455
+ * strings surface existed. Supplying no override changes nothing on screen.
456
+ */
457
+ declare const DEFAULT_STRINGS: LkStrings;
458
+ /**
459
+ * Layers an override onto a base dictionary.
460
+ *
461
+ * Two levels deep and no further, because the shape is two levels deep. A
462
+ * generic deep merge would silently accept a nested object where a function
463
+ * belongs and fail at render instead of at the call site.
464
+ */
465
+ declare function mergeStrings(base: LkStrings, override?: LkStringsOverride): LkStrings;
466
+
467
+ export { type ActivityProps as A, DEFAULT_STRINGS as D, type HtmlSanitizer as H, type LkStrings as L, type MediaBudgetBinding as M, type RenderMode as R, type SequenceMediaBudget as S, type LkStringsOverride as a, type MediaTransportStrings as b, type Renderable as c, type RenderableActivity as d, asRenderable as e, asRenderableSequence as f, mergeStrings as m };
@@ -260,7 +260,7 @@
260
260
  }
261
261
 
262
262
  .lk-mc-option-feedback {
263
- margin-left: auto;
263
+ margin-inline-start: auto;
264
264
  font-size: var(--lk-font-size-sm);
265
265
  color: var(--lk-color-text-muted);
266
266
  }
@@ -338,7 +338,7 @@
338
338
 
339
339
  /* Inline per-blank feedback shown after submission. */
340
340
  .lk-fib-blank-feedback {
341
- margin-left: var(--lk-spacing-xs);
341
+ margin-inline-start: var(--lk-spacing-xs);
342
342
  font-size: var(--lk-font-size-sm);
343
343
  color: var(--lk-color-text-muted);
344
344
  }
@@ -376,7 +376,7 @@
376
376
  .lk-fib-blank [aria-live]:not(:empty) {
377
377
  position: absolute;
378
378
  bottom: calc(100% + 6px);
379
- left: 0;
379
+ inset-inline-start: 0;
380
380
  z-index: 1;
381
381
  max-width: 16rem;
382
382
  padding: var(--lk-spacing-xs) var(--lk-spacing-sm);
@@ -393,7 +393,7 @@
393
393
  content: "";
394
394
  position: absolute;
395
395
  top: 100%;
396
- left: var(--lk-spacing-sm);
396
+ inset-inline-start: var(--lk-spacing-sm);
397
397
  border: 5px solid transparent;
398
398
  border-top-color: var(--lk-color-text);
399
399
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intellectif/lk-react",
3
- "version": "7.0.1",
3
+ "version": "7.1.0",
4
4
  "description": "Accessible React 19 assessment components — multiple choice, fill-in-the-blanks, written response — with a resumable exam/review pager, theming, and xAPI delivery.",
5
5
  "keywords": [
6
6
  "react",
@@ -113,6 +113,16 @@
113
113
  "default": "./dist/hooks/useXAPI.cjs"
114
114
  }
115
115
  },
116
+ "./i18n/LkIntlProvider": {
117
+ "import": {
118
+ "types": "./dist/i18n/LkIntlProvider.d.ts",
119
+ "default": "./dist/i18n/LkIntlProvider.js"
120
+ },
121
+ "require": {
122
+ "types": "./dist/i18n/LkIntlProvider.d.cts",
123
+ "default": "./dist/i18n/LkIntlProvider.cjs"
124
+ }
125
+ },
116
126
  "./theme/ThemeProvider": {
117
127
  "import": {
118
128
  "types": "./dist/theme/ThemeProvider.d.ts",
@@ -137,7 +147,7 @@
137
147
  "peerDependencies": {
138
148
  "react": "^19",
139
149
  "react-dom": "^19",
140
- "@intellectif/lk-core": "^0.8.1"
150
+ "@intellectif/lk-core": "^0.8.2"
141
151
  },
142
152
  "devDependencies": {
143
153
  "@axe-core/react": "^4.10.0",
@@ -156,7 +166,7 @@
156
166
  "tsup": "^8.3.0",
157
167
  "vitest": "^4.1.6",
158
168
  "vitest-axe": "^0.1.0",
159
- "@intellectif/lk-core": "0.8.1"
169
+ "@intellectif/lk-core": "0.8.2"
160
170
  },
161
171
  "license": "MIT",
162
172
  "engines": {
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/ActivitySequence/ActivitySequence.tsx"],"names":["current"],"mappings":"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","file":"chunk-3VQV7ICQ.js","sourcesContent":["'use client';\n\nimport {\n type ActivityMedia,\n type ActivityResult,\n flattenSequence,\n type InteractionEvent,\n type ItemOutcome,\n isItemGroup,\n type LearnerResponse,\n resolvePlaybackPolicy,\n type SequenceEntry,\n type SequenceSlot,\n type SequenceSlotGroup,\n slotMediaKey,\n stimulusMediaKey,\n type ThemeTokens,\n} from '@intellectif/lk-core';\nimport { type ComponentType, useEffect, useMemo, useRef, useState } from 'react';\nimport { randomSessionId } from '../_internal.js';\nimport { FillInTheBlanks } from '../FillInTheBlanks/index.js';\nimport { MultipleChoice } from '../MultipleChoice/index.js';\nimport { StimulusPanel } from '../StimulusPanel/index.js';\nimport type {\n ActivityProps,\n HtmlSanitizer,\n MediaBudgetBinding,\n RenderableActivity,\n RenderMode,\n SequenceMediaBudget,\n} from '../types.js';\nimport { WrittenResponse, type WrittenResponseSubmission } from '../WrittenResponse/index.js';\n\n/**\n * What one finished slot in a sequence produced. Two kinds, because two kinds\n * of activity exist: those the SDK scores at submit time, and those a grader\n * scores later. Collapsing them would mean inventing a score for ungraded\n * work — the exact defect the deferred outcome exists to prevent.\n */\nexport type SequenceItemOutcome =\n | {\n kind: 'scored';\n /** Presented position. */\n index: number;\n /** Slot identity from `flattenSequence` — stable under shuffling; the `slotId` to score with. */\n slotId: string;\n activityId: string;\n result: ActivityResult;\n }\n | {\n kind: 'submitted';\n index: number;\n slotId: string;\n activityId: string;\n /** Ungraded submission; the grade arrives asynchronously. */\n submission: WrittenResponseSubmission;\n }\n | {\n kind: 'restored';\n index: number;\n slotId: string;\n activityId: string;\n /**\n * The answer as restored, when one was. A slot can be submitted with no\n * stored response (a blank the learner committed), so this is optional —\n * inventing one would be worse than admitting it is absent.\n */\n response?: LearnerResponse;\n }\n | {\n kind: 'responded';\n index: number;\n slotId: string;\n activityId: string;\n /**\n * The raw answer, with no grade of any kind. This is what an `exam` slot\n * produces: the client is forbidden to score, so a response is all there\n * is until the server grades it.\n */\n response: LearnerResponse;\n };\n\n/**\n * A component that can render one activity inside a sequence. Register one per\n * activity `type` to put a consumer-defined type on screen — the React half of\n * the activity-type registry, matching `registerActivityType` in lk-core.\n */\nexport type ActivityRenderer = ComponentType<ActivityProps>;\n\nexport interface ActivitySequenceProps {\n /**\n * The ordered set of entries to present, one question at a time: loose\n * activities, and item groups — a shared passage, recording or image with\n * the questions that refer to it. A group's stimulus stays on screen\n * alongside each of its questions.\n *\n * Accepts `redact()` projections. For data straight off a server, pass it\n * through `asRenderableSequence()` — see that helper for why a cast is\n * unavoidable there — and set `renderMode=\"exam\"`.\n */\n activities: readonly SequenceEntry<RenderableActivity>[];\n /**\n * Renderers for activity types beyond the built-ins, keyed by `data.type`.\n * A key matching a built-in overrides it, so a consumer can replace the\n * bundled renderer without forking the sequencer.\n */\n renderers?: Readonly<Record<string, ActivityRenderer>>;\n /**\n * Called whenever an individual activity is scored at submit time — the\n * incremental persist hook.\n *\n * `slotId` is the identity to store the result against, NOT `index`.\n * `index` is where the question was PRESENTED, which is not stable: it moves\n * under shuffling, and it already differs from the slot identity whenever a\n * group is present (the second entry of a sequence is presented at index 1\n * but is slot `\"1.0\"` if it is a group's first question). `composeAssessmentScore`\n * scores by `slotId`, so persisting `index` cannot be reconciled with it.\n */\n onActivityComplete?: (result: ActivityResult, index: number, slotId: string) => void;\n /**\n * Called on every submit with the learner's raw response and the identity of\n * the slot that produced it — before any grading, in every render mode.\n *\n * **This is the only response channel an `exam` sequence has.** In `exam`\n * mode the components never grade, so `onActivityComplete` cannot fire and\n * `onComplete` never will; without this prop nothing a learner submitted\n * would reach the consumer at all. Persist `response` against `slotId`.\n */\n onSubmit?: (\n response: LearnerResponse,\n slot: { slotId: string; index: number; activityId: string },\n ) => void;\n /**\n * Called once every activity has been completed, with the scored results.\n *\n * Fires only when EVERY item produced a score. A set containing a\n * deferred-graded activity (a written response) can never satisfy that, so\n * use `onFinished` for mixed sets — it is the general completion signal and\n * reports both kinds of outcome. It also carries `slotId` per item, which\n * this callback's bare array cannot.\n */\n onComplete?: (results: ActivityResult[]) => void;\n /**\n * Called once every activity has been completed, whether it was scored at\n * submit time or submitted for later grading. Use this for any set that\n * mixes graded and deferred-graded activities.\n */\n onFinished?: (items: SequenceItemOutcome[]) => void;\n /** Forwarded to each activity. */\n onInteraction?: (event: InteractionEvent) => void;\n /** Forwarded to each activity. `exam` and `review` disable local scoring. */\n renderMode?: RenderMode;\n /**\n * Binds every budgeted recording in this sequence to a play ledger the\n * consumer persists.\n *\n * The SDK refuses a play; it does not remember one. `plays` seeds the counts\n * at mount (pass `restoreMediaPlayLedger(plan, stored).entries`), and\n * `onPlayConsumed` is how a play becomes durable — see\n * {@link SequenceMediaBudget}.\n */\n mediaBudget?: SequenceMediaBudget;\n /**\n * `entries` shuffles the top-level entries; a group moves as one block, and\n * the order INSIDE a group follows the group's own `shuffle` setting.\n * Default `none`: authored order.\n */\n shuffle?: 'none' | 'entries';\n /**\n * Seed for every shuffle in this sequence (`shuffle: 'entries'` and any\n * group with `shuffle: 'within-group'`). Supply the attempt id, so the\n * server's `flattenSequence(entries, { seed })` derives the same order this\n * pager shows.\n *\n * **Required whenever this sequence shuffles** — `shuffle: 'entries'`, or any\n * group with `shuffle: 'within-group'` — in `exam` or `review` mode; omitting\n * it then throws. An order nobody can reproduce cannot be reconciled with a\n * recorded attempt, and failing at render is the only way that mistake\n * surfaces before a learner sits the paper. A sequence that does not shuffle\n * needs no seed in any mode.\n *\n * Note the guard does NOT cover an activity's own `data.shuffle`:\n * `<MultipleChoice>` falls back to a per-mount seed there in every mode, so\n * pass this whenever any item shuffles its options.\n *\n * In `practice` mode it stays optional: a random per-mount seed is used,\n * stable within the mount and deliberately not reproducible. That fallback is\n * also not SSR-safe (server and client would invent different orders and\n * hydration would mismatch), so supply a seed for any server-rendered\n * sequence regardless of mode.\n */\n shuffleSeed?: string;\n /**\n * Where to open. Defaults to the first question; pass a stored\n * `AttemptState.index` to reopen an interrupted attempt where it was left.\n * Read at mount only, like any `default*` prop.\n */\n defaultIndex?: number;\n /**\n * Fires whenever the learner moves. Persist it and the next resume reopens\n * on the right question — without it, the pager's position is the one piece\n * of an attempt a consumer cannot recover.\n */\n onIndexChange?: (index: number) => void;\n /**\n * Answers to restore, keyed by `slotId`, seeded into each slot as its\n * `defaultValue`. This is the other half of resume: `AttemptState.responses`\n * goes straight in.\n *\n * Read at mount only. To restore a different attempt, remount with a `key`.\n */\n responses?: Readonly<Record<string, LearnerResponse>>;\n /**\n * Slots the learner had already submitted, from `AttemptState.submittedSlotIds`.\n * Each is mounted already submitted, so a resumed paper does not reopen a\n * locked question as answerable — without this a learner can change and\n * re-submit work they had already committed.\n *\n * Read at mount only, alongside `responses`.\n */\n submittedSlotIds?: readonly string[];\n /**\n * Server-computed outcomes keyed by `slotId`, forwarded to each slot. In\n * `review` mode this is what marks correctness — the client never scores, so\n * without it a review render has nothing to show.\n */\n outcomes?: Readonly<Record<string, ItemOutcome>>;\n /** Renders author-supplied rich text; forwarded to the stimulus panel and every activity. */\n sanitizeHtml?: HtmlSanitizer;\n theme?: Partial<ThemeTokens>;\n locale?: string;\n disabled?: boolean;\n}\n\n/** One stimulus panel to keep mounted: the group, and the presented span of its questions. */\ninterface StimulusMount {\n /** Authored entry position — unique per group in the sequence even if two groups share an id. */\n entryKey: string;\n group: SequenceSlotGroup;\n first: number;\n last: number;\n}\n\n/** The authored-entry prefix of a slot id: `\"3\"` for `\"3\"` and for `\"3.1\"`. */\nfunction entryKeyOf(slot: SequenceSlot<RenderableActivity>): string {\n return slot.slotId.split('.')[0] ?? slot.slotId;\n}\n\n/**\n * A pane that is kept MOUNTED but shown only when it is the current one, and\n * that stops any media it contains on the way out.\n *\n * `hidden` alone is not enough. It resolves to `display: none`, and CSS does\n * not touch playback: an `<audio>` or `<video>` inside a hidden subtree keeps\n * playing to the end. (The HTML spec pauses a media element when it is\n * REMOVED from the document — a different thing, and the thing this pager\n * deliberately does not do, because unmounting is what used to destroy the\n * learner's answers.) So a recording started for one question would carry on\n * underneath the next, which for a listening paper means the passage plays\n * while the learner is somewhere else entirely.\n *\n * Pausing preserves `currentTime`: coming back to a group resumes where the\n * learner left off. Nothing auto-plays on the way in — starting audio the\n * learner did not ask for is its own defect.\n *\n * `embed` media (a provider iframe) cannot be paused this way: controlling a\n * third-party player needs its own JS API, which the SDK has no reliable\n * access to because the author supplies the embed URL. Use `audio`/`video`\n * media for anything that must stop when the learner navigates.\n */\nfunction SequencePane({\n className,\n hidden,\n children,\n}: {\n className: string;\n hidden: boolean;\n children: React.ReactNode;\n}): React.JSX.Element {\n const ref = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n if (!hidden) {\n return;\n }\n const node = ref.current;\n if (node === null) {\n return;\n }\n for (const media of node.querySelectorAll('audio, video')) {\n const element = media as HTMLMediaElement;\n if (!element.paused) {\n element.pause();\n }\n }\n }, [hidden]);\n\n return (\n <div className={className} hidden={hidden} ref={ref}>\n {children}\n </div>\n );\n}\n\n/**\n * Presents a set of activities as an in-place pager: one question visible at a\n * time, navigated with Prev/Next (no page scrolling). Linear free navigation —\n * the learner may move back and forth; activities keep their own submit and\n * scoring. On navigation, focus moves to the question region and the position\n * is announced via an aria-live region.\n *\n * Item groups are flattened into consecutive questions; the group's stimulus\n * is mounted ONCE, beside the question region rather than inside it, and shown\n * alongside every question in the group — so a passage stays put, and a\n * recording keeps its position, as the learner moves between its questions.\n */\n/**\n * Whether an activity shuffles its own options, and therefore needs a seed of\n * its own. Read structurally: `shuffle` is a per-type content field, not part\n * of the sequence entry contract.\n */\nfunction shufflesItsOwnOptions(entry: unknown): boolean {\n return (entry as { shuffle?: unknown } | null)?.shuffle === true;\n}\n\nexport function ActivitySequence({\n activities,\n renderers,\n onActivityComplete,\n onComplete,\n onFinished,\n onSubmit,\n onInteraction,\n renderMode = 'practice',\n shuffle,\n shuffleSeed,\n defaultIndex,\n onIndexChange,\n responses,\n submittedSlotIds,\n outcomes,\n sanitizeHtml,\n theme,\n locale,\n disabled,\n mediaBudget,\n}: ActivitySequenceProps): React.JSX.Element {\n const sessionIdRef = useRef<string | null>(null);\n const mediaPlays = mediaBudget?.plays;\n\n // The presented order comes from lk-core, never computed here: the server\n // that records an attempt calls the same function with the same seed.\n const shuffleEntries = shuffle === 'entries';\n // Every shuffle this sequence can produce, not just the ones it performs\n // itself. An ACTIVITY's own `data.shuffle` is the third source: MultipleChoice\n // falls back to a per-mount seed when none reaches it, so an unseeded item\n // shuffle used to render happily under `exam` and hand the learner a\n // different option order on every mount — exactly the unreproducible\n // arrangement this guard exists to refuse, arriving through the one door it\n // did not watch.\n const needsSeed =\n shuffleEntries ||\n activities.some((entry) =>\n isItemGroup(entry)\n ? entry.shuffle === 'within-group' || entry.items.some(shufflesItsOwnOptions)\n : shufflesItsOwnOptions(entry),\n );\n\n // An unreproducible order is a practice-only affordance. Under `exam` or\n // `review` the server has to be able to rebuild exactly what the learner\n // saw, so a missing seed is an error rather than something to paper over —\n // the same reason `flattenSequence` refuses to invent one.\n if (needsSeed && shuffleSeed === undefined && renderMode !== 'practice') {\n throw new Error(\n `ActivitySequence: renderMode \"${renderMode}\" requires a \\`shuffleSeed\\` when shuffling ` +\n '(shuffle=\"entries\", or a group with shuffle: \"within-group\"). Pass the attempt id, so ' +\n \"the server's flattenSequence(entries, { seed }) reproduces the order the learner saw.\",\n );\n }\n\n // Lazily created only when shuffling in practice without a caller seed (same\n // contract as MultipleChoice.shuffleSeed): a per-mount session seed, stable\n // across re-renders, never reproducible.\n const seed = needsSeed\n ? // biome-ignore lint/suspicious/noAssignInExpressions: sanctioned lazy ref initialization\n (shuffleSeed ?? (sessionIdRef.current ??= randomSessionId()))\n : undefined;\n const slots = useMemo(\n () => flattenSequence(activities, { shuffleEntries, ...(seed !== undefined ? { seed } : {}) }),\n [activities, shuffleEntries, seed],\n );\n\n // Clamped rather than trusted: a stored position from a paper that has since\n // lost its last question would open the pager on a slot that is not there,\n // which renders as an empty shell with no way forward.\n const [index, setIndex] = useState(() => {\n // NaN passes through Math.min/Math.max unchanged, so it survived every\n // clamp and indexed the slots with NaN — the pager then rendered the empty\n // shell this clamp exists to prevent. `NaN` is a `number`, so the prop type\n // gives no protection, and `Number(row.last_index)` on a NULL column\n // produces exactly that.\n const requested = Math.trunc(defaultIndex ?? 0);\n if (!Number.isFinite(requested)) {\n return 0;\n }\n return Math.min(Math.max(0, requested), Math.max(0, slots.length - 1));\n });\n // Outcomes live in a ref, not state: nothing renders from them, and a ref is\n // written SYNCHRONOUSLY. Reading them from a `useState` closure meant two\n // slots completing in the same tick both saw the pre-update array — the\n // second overwrote the first, so one answer vanished and completion never\n // fired. Every slot is mounted at once here, so same-tick completions are\n // reachable (a custom renderer that completes from a mount effect).\n const outcomesRef = useRef<(SequenceItemOutcome | null)[] | null>(null);\n if (outcomesRef.current === null) {\n // Slots the learner had already submitted are seeded as `restored`. They\n // will not submit again — they mount locked — so leaving them null meant\n // `onFinished` waited forever on outcomes that could never arrive, and a\n // resumed attempt could never signal completion however many of the\n // remaining questions the learner answered. Seeding fires no callback: the\n // attempt was already this far along before this mount existed.\n const submittedAtMount = new Set(submittedSlotIds ?? []);\n outcomesRef.current = slots.map((slot) =>\n submittedAtMount.has(slot.slotId)\n ? {\n kind: 'restored' as const,\n index: slot.index,\n slotId: slot.slotId,\n activityId: slot.activity.id,\n ...(responses !== undefined && Object.hasOwn(responses, slot.slotId)\n ? { response: responses[slot.slotId] as LearnerResponse }\n : {}),\n }\n : null,\n );\n }\n const regionRef = useRef<HTMLDivElement>(null);\n const mountedRef = useRef(false);\n\n // Reset pager position and outcomes when the presented SET changes (B8):\n // without this, results from the previous set leak into the new one and can\n // fire completion with a mixed old/new array. Keyed on the set's CONTENT\n // identity (ordered slot and activity ids), not the array reference — a\n // parent that re-creates a structurally identical array on every render\n // (inline literals, .map() in render) must not wipe in-progress answers.\n // JSON.stringify (not join) so ids containing the separator cannot collide.\n const setKey = JSON.stringify(slots.map((slot) => [slot.slotId, slot.activity.id]));\n const [prevSetKey, setPrevSetKey] = useState(setKey);\n // The set this component mounted with. `responses` seeds slots BY slotId,\n // and slot ids are short and repeat across papers (\"0\", \"1.0\"), so applying\n // them after the set changed would drop one paper's answers under another\n // paper's questions — a review modal stepping to the next attempt did\n // exactly that. Seeding stops at the first set change; to show a different\n // attempt, remount with a `key`, which is what \"read at mount only\" means.\n // `mediaBudget.resumeKey` participates so an invigilator can hand a play\n // back — re-seeding the budgets in place — without remounting the pager and\n // costing the learner their focus, their scroll position and an unsaved\n // answer.\n const seedKey = `${setKey}::${mediaBudget?.resumeKey ?? ''}`;\n const mountSetKeyRef = useRef(setKey);\n const mountSeedKeyRef = useRef(seedKey);\n const seedsApply = mountSetKeyRef.current === setKey;\n const mediaSeedsApply = mountSeedKeyRef.current === seedKey || seedsApply;\n if (mountSeedKeyRef.current !== seedKey) {\n mountSeedKeyRef.current = seedKey;\n }\n const submitted = useMemo(() => new Set(submittedSlotIds ?? []), [submittedSlotIds]);\n if (prevSetKey !== setKey) {\n setPrevSetKey(setKey);\n setIndex(0);\n outcomesRef.current = slots.map(() => null);\n }\n\n // After a navigation (not the initial mount) move focus to the question\n // region so screen-reader / keyboard users land on the new question.\n // biome-ignore lint/correctness/useExhaustiveDependencies: index is the intended trigger\n useEffect(() => {\n if (mountedRef.current) {\n regionRef.current?.focus();\n } else {\n mountedRef.current = true;\n }\n }, [index]);\n\n // Report where the pager actually IS, from an effect rather than from the\n // navigation handler. Two positions the learner never chose are still\n // positions a consumer has to persist: a `defaultIndex` that was clamped\n // (stored 99, showing 2) and the reset a set change performs. Reporting only\n // clicks left storage disagreeing with the screen, and the next autosave\n // then wrote a position the plan does not have.\n const reportedIndexRef = useRef<number | null>(null);\n useEffect(() => {\n if (reportedIndexRef.current === index) {\n return;\n }\n const first = reportedIndexRef.current === null;\n reportedIndexRef.current = index;\n // On mount, stay silent unless the requested position was corrected.\n if (first && index === Math.trunc(defaultIndex ?? 0)) {\n return;\n }\n onIndexChange?.(index);\n }, [index, defaultIndex, onIndexChange]);\n\n // One panel per group, spanning the presented positions of its questions.\n const stimulusMounts = useMemo<StimulusMount[]>(() => {\n const mounts = new Map<string, StimulusMount>();\n for (const slot of slots) {\n if (slot.group === undefined) {\n continue;\n }\n const entryKey = entryKeyOf(slot);\n const existing = mounts.get(entryKey);\n if (existing === undefined) {\n mounts.set(entryKey, { entryKey, group: slot.group, first: slot.index, last: slot.index });\n } else {\n existing.last = slot.index;\n }\n }\n return [...mounts.values()];\n }, [slots]);\n\n const total = slots.length;\n const current = slots[index];\n\n const go = (next: number): void => {\n setIndex(next);\n };\n\n /**\n * Records a finished slot and fires completion callbacks when the set is\n * done. A slot that already holds an outcome is FINAL: re-recording it would\n * fire completion a second time for one attempt, so a consumer persisting on\n * `onFinished` would write the same attempt twice.\n */\n const record = (outcome: SequenceItemOutcome): void => {\n const current = outcomesRef.current;\n if (current === null || current[outcome.index] != null) {\n return;\n }\n const next = current.slice();\n next[outcome.index] = outcome;\n // Commit before firing anything: a second record() in the same tick must\n // see this one.\n outcomesRef.current = next;\n\n if (outcome.kind === 'scored') {\n onActivityComplete?.(outcome.result, outcome.index, outcome.slotId);\n }\n\n if (!next.every((item): item is SequenceItemOutcome => item !== null)) {\n return;\n }\n onFinished?.(next);\n\n // `onComplete` predates deferred grading and promises ActivityResult[].\n // Fire it only when every slot really was scored, rather than inventing a\n // result for work nobody has graded.\n const scored: ActivityResult[] = [];\n for (const item of next) {\n if (item.kind !== 'scored') {\n return;\n }\n scored.push(item.result);\n }\n onComplete?.(scored);\n };\n\n if (!current) {\n return <div className=\"lk-seq\" />;\n }\n\n // Every budgeted recording this paper presents, and the key it is budgeted\n // under. Derived from the resolved policy, so the pager, the plan and the\n // schema cannot disagree about what is budgeted.\n const budgetedMedia = slots.flatMap((slot) => {\n const found: { key: string; url: string }[] = [];\n const own = (slot.activity as { media?: ActivityMedia }).media;\n if (own !== undefined && resolvePlaybackPolicy(own).maxPlays !== null) {\n found.push({ key: slotMediaKey(slot.slotId), url: own.url });\n }\n const stimulus = slot.group?.stimulus.media;\n if (stimulus !== undefined && resolvePlaybackPolicy(stimulus).maxPlays !== null) {\n found.push({ key: stimulusMediaKey(slot.slotId), url: stimulus.url });\n }\n return found;\n });\n\n const budgetEnforced = mediaBudget?.enforced ?? renderMode !== 'review';\n\n if (budgetedMedia.length > 0 && renderMode === 'exam' && budgetEnforced) {\n if (mediaBudget?.onPlayConsumed === undefined) {\n throw new Error(\n 'ActivitySequence: renderMode \"exam\" with media that declares `maxPlays` requires ' +\n '`mediaBudget.onPlayConsumed`. The SDK persists nothing, so without it the count lives ' +\n 'only in this mount: a refresh silently restores the full budget, and a paper that ' +\n 'grants unlimited plays while showing \"2 plays remaining\" is indistinguishable — to the ' +\n 'learner and to an appeal — from one that works. Budgeted media: ' +\n `${[...new Set(budgetedMedia.map((m) => m.key))].join(', ')}.`,\n );\n }\n const resuming =\n (responses !== undefined && Object.keys(responses).length > 0) ||\n (submittedSlotIds !== undefined && submittedSlotIds.length > 0);\n if (resuming && mediaBudget.plays === undefined) {\n throw new Error(\n 'ActivitySequence: this is a resumed attempt (`responses`/`submittedSlotIds` were ' +\n 'supplied) but `mediaBudget.plays` is absent. A resume that omits the ledger hands the ' +\n 'learner a fresh budget at exactly the moment it matters. Pass ' +\n '`restoreMediaPlayLedger(plan, stored).entries`, or `{}` to state explicitly that ' +\n 'nothing was spent.',\n );\n }\n }\n\n // One recording must not carry two budgets, in any mode — six questions each\n // holding the same clip at maxPlays 2 is twelve plays of one recording.\n const keysByUrl = new Map<string, Set<string>>();\n for (const { key, url } of budgetedMedia) {\n const keys = keysByUrl.get(url) ?? new Set<string>();\n keys.add(key);\n keysByUrl.set(url, keys);\n }\n for (const [url, keys] of keysByUrl) {\n if (keys.size > 1) {\n throw new Error(\n `ActivitySequence: media ${JSON.stringify(url)} is budgeted under ${keys.size} separate ` +\n `keys (${[...keys].join(', ')}), so one recording grants ${keys.size} × maxPlays. Put ` +\n \"the questions that share a recording in an item group — a group's stimulus is one \" +\n 'recording with one budget.',\n );\n }\n }\n\n /** The per-media binding handed to a component or to the stimulus panel. */\n const bindingFor = (\n key: string,\n slot: { slotId: string; index: number; activity: { id: string } },\n ): MediaBudgetBinding | undefined => {\n if (mediaBudget === undefined) {\n return undefined;\n }\n const entry =\n mediaSeedsApply && mediaPlays !== undefined && Object.hasOwn(mediaPlays, key)\n ? mediaPlays[key]\n : undefined;\n return {\n key,\n slotId: slot.slotId,\n index: slot.index,\n activityId: slot.activity.id,\n ...(entry !== undefined ? { entry } : {}),\n ...(mediaBudget.enforced !== undefined ? { enforced: mediaBudget.enforced } : {}),\n ...(mediaBudget.onPlayConsumed !== undefined\n ? { onPlayConsumed: mediaBudget.onPlayConsumed }\n : {}),\n ...(mediaBudget.onPlayRefunded !== undefined\n ? { onPlayRefunded: mediaBudget.onPlayRefunded }\n : {}),\n ...(mediaBudget.onPosition !== undefined ? { onPosition: mediaBudget.onPosition } : {}),\n ...(mediaBudget.strings !== undefined ? { strings: mediaBudget.strings } : {}),\n };\n };\n\n const forwarded = {\n ...(onInteraction ? { onInteraction } : {}),\n renderMode,\n ...(sanitizeHtml ? { sanitizeHtml } : {}),\n ...(theme ? { theme } : {}),\n ...(locale ? { locale } : {}),\n ...(disabled ? { disabled } : {}),\n };\n\n const renderSlot = (slot: SequenceSlot<RenderableActivity>): React.JSX.Element => {\n const activity = slot.activity;\n const { slotId, index: slotIndex } = slot;\n const activityId = activity.id;\n const restored =\n seedsApply && responses !== undefined && Object.hasOwn(responses, slotId)\n ? responses[slotId]\n : undefined;\n const slotOutcome =\n outcomes !== undefined && Object.hasOwn(outcomes, slotId) ? outcomes[slotId] : undefined;\n const slotBinding = bindingFor(slotMediaKey(slotId), slot);\n const childProps = {\n // `defaultValue`, not `value`: the learner must be able to keep editing\n // a restored answer. A controlled `value` would freeze it unless the\n // consumer also threaded state back, which is not what resume means.\n ...(restored !== undefined ? { defaultValue: restored } : {}),\n ...(seedsApply && submitted.has(slotId) ? { defaultSubmitted: true } : {}),\n ...(slotOutcome !== undefined ? { outcome: slotOutcome } : {}),\n onComplete: (result: ActivityResult) =>\n record({ kind: 'scored', index: slotIndex, slotId, activityId, result }),\n onSubmit: (response: LearnerResponse) => {\n onSubmit?.(response, { slotId, index: slotIndex, activityId });\n // Outside `practice` the components do not grade, so a raw response is\n // the ONLY outcome this slot will ever produce — record it, or the set\n // could never complete and `onFinished` would be dead in exam mode.\n // Written responses are excluded because they report something richer\n // through `onSubmitted` a moment later, and the first outcome wins.\n if (renderMode !== 'practice' && activity.type !== 'written-response') {\n record({ kind: 'responded', index: slotIndex, slotId, activityId, response });\n }\n },\n ...forwarded,\n ...(slotBinding !== undefined ? { mediaBudget: slotBinding } : {}),\n };\n\n const CustomRenderer = renderers?.[activity.type];\n if (CustomRenderer) {\n return <CustomRenderer data={activity} {...childProps} />;\n }\n if (activity.type === 'multiple-choice') {\n return (\n <MultipleChoice\n data={activity}\n // The sequence seed reaches the OPTION shuffle too. Without it a\n // resumed or reviewed item invented a fresh per-mount order, so the\n // learner saw their answers against a different arrangement than the\n // one they sat — the exact reproducibility the attempt id is for.\n {...(shuffleSeed !== undefined ? { shuffleSeed } : {})}\n {...childProps}\n />\n );\n }\n if (activity.type === 'fill-in-the-blanks') {\n return <FillInTheBlanks data={activity} {...childProps} />;\n }\n if (activity.type === 'written-response') {\n // Derived from the SAME bag the other two get, minus the one prop this\n // component genuinely does not have. Listing the props by hand is what\n // made this the branch left behind three separate times —\n // `defaultSubmitted`, the redacted-in-`practice` guard and the media\n // budget were each added to `childProps` and each silently missed here.\n // Subtracting from the shared bag cannot forget a prop that is added to\n // it; listing them can.\n const { onComplete: _neverGrades, ...writtenResponseProps } = childProps;\n return (\n <WrittenResponse\n data={activity}\n {...writtenResponseProps}\n onSubmitted={(submission) =>\n record({ kind: 'submitted', index: slotIndex, slotId, activityId, submission })\n }\n />\n );\n }\n return (\n <div className=\"lk-seq-unsupported\" role=\"note\">\n This activity type has no renderer. Supply one through the renderers prop.\n </div>\n );\n };\n\n const currentEntryKey = entryKeyOf(current);\n\n return (\n <div className=\"lk-seq\" lang={locale}>\n <div className=\"lk-seq-progress\" aria-live=\"polite\" role=\"status\">\n Question {index + 1} of {total}\n </div>\n\n {/*\n Each group's stimulus is mounted ONCE and only hidden while the current\n question is outside its group. That — not re-rendering it per slot — is\n what makes the passage the same DOM node throughout, and what lets a\n recording keep its position between questions of one group.\n\n It sits BESIDE the question region, not inside it. Inside, every\n navigation dropped focus onto a container whose first content was the\n whole passage: the region promised \"Question 3 of 5\" and then began\n with the passage, keyboard users tabbed through the stimulus (audio\n controls included) before reaching the question, and the passage was a\n landmark nested inside another landmark. As a sibling it stays a\n landmark the learner can jump back to, while focus lands on the\n question that was actually navigated to.\n */}\n {stimulusMounts.map(({ entryKey, group, first, last }) => {\n // `setKey` leads for the same reason it does on a slot pane: entry keys\n // are short and repeat across papers, so a set change to a DIFFERENT\n // paper that happens to reuse an entry key would not remount this\n // panel — and a mount-only budget seed would leak into the next paper.\n const firstSlot = slots[first];\n const stimulusBinding =\n firstSlot === undefined\n ? undefined\n : bindingFor(stimulusMediaKey(firstSlot.slotId), firstSlot);\n return (\n <SequencePane\n className=\"lk-seq-stimulus\"\n hidden={entryKey !== currentEntryKey}\n key={`${setKey}::group-${entryKey}`}\n >\n <StimulusPanel\n stimulus={group.stimulus}\n range={{ first: first + 1, last: last + 1 }}\n renderMode={renderMode}\n {...(sanitizeHtml ? { sanitizeHtml } : {})}\n {...(locale ? { locale } : {})}\n {...(onInteraction ? { onInteraction } : {})}\n {...(disabled ? { disabled } : {})}\n {...(stimulusBinding !== undefined ? { mediaBudget: stimulusBinding } : {})}\n />\n </SequencePane>\n );\n })}\n\n <section\n className=\"lk-seq-question\"\n ref={regionRef}\n tabIndex={-1}\n aria-label={`Question ${index + 1} of ${total}`}\n >\n {/*\n Every slot stays MOUNTED; only the current one is visible. Rendering\n just the current activity unmounted the others, so navigating back to\n an answered question showed it blank and re-answerable — the learner\n silently lost their answer and a second submit fired completion\n again. `hidden` removes the inactive slots from the accessibility\n tree and from tab order, so only the current question is reachable.\n */}\n {slots.map((slot) => {\n // The slot id (authored position) plus the activity id: the same\n // activity may legitimately fill two slots, and each must keep its\n // own answer state; and a different activity in the same slot must\n // remount rather than inherit the previous one's state.\n //\n // `setKey` leads, so a set change remounts every child. Without it a\n // pure REORDER changed `setKey` — clearing the outcomes — while each\n // child kept its key and therefore its `completed` state. Those\n // slots could not be answered again and no outcome would ever refill\n // them, so the sequence could never finish. A reset has to reset\n // both halves or neither. A parent re-creating a structurally\n // identical array leaves `setKey` untouched, so answers still\n // survive that.\n const slotKey = `${setKey}::slot-${slot.slotId}-${slot.activity.id}`;\n return (\n <SequencePane className=\"lk-seq-slot\" hidden={slot.index !== index} key={slotKey}>\n {renderSlot(slot)}\n </SequencePane>\n );\n })}\n </section>\n\n <div className=\"lk-seq-nav\">\n <button\n type=\"button\"\n className=\"lk-seq-prev\"\n onClick={() => go(index - 1)}\n disabled={index === 0}\n >\n Previous\n </button>\n <button\n type=\"button\"\n className=\"lk-seq-next\"\n onClick={() => go(index + 1)}\n disabled={index === total - 1}\n >\n Next\n </button>\n </div>\n </div>\n );\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/shared/AudioTransport.tsx","../src/components/shared/ActivityMedia.tsx"],"names":["jsx","jsxs"],"mappings":"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","file":"chunk-52A5UNCH.js","sourcesContent":["'use client';\n\nimport type {\n ActivityMedia as ActivityMediaData,\n MediaPlayClaim,\n ResolvedPlaybackPolicy,\n} from '@intellectif/lk-core';\nimport { useCallback, useEffect, useId, useRef, useState } from 'react';\nimport type { MediaBudgetBinding, MediaTransportStrings } from '../types.js';\n\n/**\n * The SDK's own audio player, used whenever a policy has something to enforce.\n *\n * The browser's control bar cannot express a spent budget — its play button\n * stays enabled — and a control that looks operable and does nothing is a\n * WCAG 3.2.2 / 4.1.3 failure, so an enforcing policy replaces the bar rather\n * than lying with it. What replaces it is a SUPERSET minus what the policy\n * removes on purpose: play/pause, elapsed and total time, mute, volume, an\n * optional speed control, an optional scrubber, and a live plays-remaining\n * status. Volume and mute are never restricted by any policy — they affect no\n * assessment property, and a learner in a lab with a locked OS volume has no\n * other lever once the native bar is gone.\n */\n\nconst DEFAULT_STRINGS: MediaTransportStrings = {\n play: 'Play',\n pause: 'Pause',\n preparing: 'Preparing…',\n mute: 'Mute',\n unmute: 'Unmute',\n volume: 'Volume',\n speed: 'Playback speed',\n seek: 'Seek',\n playsRemaining: (remaining, max) =>\n `${remaining} of ${max} play${max === 1 ? '' : 's'} remaining`,\n noPlaysRemaining: 'No plays remaining',\n lastPlayConfirm: 'This is your last play. Start it now?',\n lastPlayStart: 'Start last play',\n lastPlayCancel: 'Not yet',\n seekBlocked: 'Rewinding is not available for this recording.',\n rateBlocked: 'Playback speed is fixed for this recording.',\n playFailed: 'The recording could not be started. Try again.',\n};\n\n/** A resume is a start within a quarter-second of where playback last stopped. */\nconst RESUME_EPSILON = 0.25;\n/** Below this, a seek is the browser settling rather than the learner moving. */\nconst SEEK_EPSILON = 0.35;\n/** A charged play that errors before this much audio played produced nothing. */\nconst NO_PROGRESS = 0.25;\n\nconst formatTime = (seconds: number): string => {\n if (!Number.isFinite(seconds) || seconds < 0) {\n return '0:00';\n }\n const whole = Math.floor(seconds);\n return `${Math.floor(whole / 60)}:${String(whole % 60).padStart(2, '0')}`;\n};\n\nexport interface AudioTransportProps {\n media: ActivityMediaData;\n policy: ResolvedPlaybackPolicy;\n renderMode?: 'practice' | 'exam' | 'review';\n disabled?: boolean;\n locale?: string;\n mediaBudget?: MediaBudgetBinding;\n mediaStrings?: Partial<MediaTransportStrings>;\n onInteraction?: (event: {\n type: string;\n activityId: string;\n timestamp: number;\n payload: Record<string, unknown>;\n }) => void;\n}\n\nexport function AudioTransport({\n media,\n policy,\n renderMode = 'practice',\n disabled = false,\n locale,\n mediaBudget,\n mediaStrings,\n onInteraction,\n}: AudioTransportProps): React.JSX.Element {\n const elementRef = useRef<HTMLAudioElement>(null);\n const statusId = useId();\n\n const suppliedStrings = mediaBudget?.strings ?? mediaStrings;\n const s: MediaTransportStrings = { ...DEFAULT_STRINGS, ...mediaStrings, ...mediaBudget?.strings };\n\n const enforced = mediaBudget?.enforced ?? renderMode !== 'review';\n const budgeted = enforced && policy.maxPlays !== null && mediaBudget !== undefined;\n const maxPlays = policy.maxPlays ?? 0;\n\n // Written synchronously inside handlers and mirrored into state for\n // rendering, for the same reason `ActivitySequence` keeps `outcomesRef` in a\n // ref: two events in one tick would otherwise both read the pre-update value.\n const usedRef = useRef(mediaBudget?.entry?.plays ?? 0);\n const pendingRef = useRef(false);\n const resumeAtRef = useRef<number | null>(null);\n const highWaterRef = useRef(0);\n const chargeRef = useRef<{ claim: MediaPlayClaim; at: number } | null>(null);\n const seededRef = useRef(false);\n const lastPositionRef = useRef(-1);\n // Marks a `play()` this component itself issued after charging. The element's\n // `play` event is where the budget is enforced — so that a media key or a\n // scripted `play()` is charged too — which means our OWN start re-enters it.\n // In the confirmed tier `pendingRef` covered that; in the optimistic tier\n // nothing did, and `remaining` is still the pre-charge value in that closure,\n // so a single press was charged twice.\n const selfStartRef = useRef(false);\n\n const [used, setUsed] = useState(usedRef.current);\n const [playing, setPlaying] = useState(false);\n const [pending, setPending] = useState(false);\n const [elapsed, setElapsed] = useState(0);\n const [duration, setDuration] = useState(0);\n const [muted, setMuted] = useState(false);\n const [volume, setVolume] = useState(1);\n const [confirming, setConfirming] = useState(false);\n const [notice, setNotice] = useState('');\n // A repeated identical message must announce again, so the alert node is\n // REPLACED rather than mutated. Screen readers ignore an unchanged one.\n const [noticeNonce, setNoticeNonce] = useState(0);\n\n const remaining = Math.max(0, maxPlays - used);\n\n const announce = useCallback((message: string) => {\n setNotice(message);\n setNoticeNonce((n) => n + 1);\n }, []);\n\n const emit = useCallback(\n (type: string, playsUsed: number) => {\n if (!budgeted || mediaBudget === undefined || onInteraction === undefined) {\n return;\n }\n onInteraction({\n type,\n activityId: mediaBudget.activityId ?? mediaBudget.slotId,\n timestamp: Date.now(),\n payload: {\n mediaKey: mediaBudget.key,\n mediaType: 'audio',\n playsUsed,\n maxPlays,\n playsRemaining: Math.max(0, maxPlays - playsUsed),\n },\n });\n },\n [budgeted, mediaBudget, maxPlays, onInteraction],\n );\n\n const reportPosition = useCallback(\n (seconds: number) => {\n mediaBudget?.onPosition?.(mediaBudget.key, seconds);\n },\n [mediaBudget],\n );\n\n /** A start within a hair of where playback last stopped is a resume, not a play. */\n const isResume = useCallback((element: HTMLAudioElement): boolean => {\n const at = resumeAtRef.current;\n return at !== null && Math.abs(element.currentTime - at) < RESUME_EPSILON;\n }, []);\n\n const buildClaim = useCallback((): MediaPlayClaim | null => {\n if (mediaBudget === undefined) {\n return null;\n }\n return {\n key: mediaBudget.key,\n previousPlaysUsed: usedRef.current,\n playsUsed: usedRef.current + 1,\n maxPlays,\n playsRemaining: Math.max(0, maxPlays - (usedRef.current + 1)),\n slotId: mediaBudget.slotId,\n index: mediaBudget.index,\n ...(mediaBudget.activityId !== undefined ? { activityId: mediaBudget.activityId } : {}),\n };\n }, [mediaBudget, maxPlays]);\n\n const refuse = useCallback(() => {\n announce(s.noPlaysRemaining);\n emit('media-play-refused', usedRef.current);\n }, [announce, emit, s.noPlaysRemaining]);\n\n const startPlayback = useCallback(() => {\n const element = elementRef.current;\n if (element === null) {\n return;\n }\n highWaterRef.current = element.currentTime;\n selfStartRef.current = true;\n void element.play().catch(() => {\n selfStartRef.current = false;\n /* autoplay policies and races surface through the `error` handler */\n });\n }, []);\n\n const commit = useCallback(\n (claim: MediaPlayClaim) => {\n const element = elementRef.current;\n chargeRef.current = { claim, at: element?.currentTime ?? 0 };\n usedRef.current = claim.playsUsed;\n setUsed(claim.playsUsed);\n emit('media-play-consumed', claim.playsUsed);\n },\n [emit],\n );\n\n /**\n * Charges a play, then starts it.\n *\n * The confirmed tier holds playback until the consumer's atomic write\n * settles, which is the only construction that catches a second tab: two\n * mounts seeded at 0 both claim 1, and only the server can tell them apart.\n */\n const claimAndStart = useCallback(\n (element: HTMLAudioElement, alreadyPlaying: boolean) => {\n const claim = buildClaim();\n if (claim === null) {\n return;\n }\n const result = mediaBudget?.onPlayConsumed?.(claim);\n\n if (result !== undefined && typeof (result as Promise<unknown>).then === 'function') {\n if (alreadyPlaying) {\n element.pause();\n }\n pendingRef.current = true;\n setPending(true);\n void (result as Promise<{ playsUsed: number } | undefined>)\n .then((grant) => {\n const granted =\n grant !== undefined && Number.isInteger(grant.playsUsed)\n ? grant.playsUsed\n : claim.playsUsed;\n if (granted > maxPlays) {\n usedRef.current = granted;\n setUsed(granted);\n refuse();\n return;\n }\n commit({ ...claim, playsUsed: granted });\n startPlayback();\n })\n .catch(() => {\n // Nothing charged: the learner may retry.\n announce(s.playFailed);\n emit('media-play-errored', usedRef.current);\n })\n .finally(() => {\n pendingRef.current = false;\n setPending(false);\n });\n return;\n }\n\n commit(claim);\n if (!alreadyPlaying) {\n startPlayback();\n }\n },\n [\n buildClaim,\n mediaBudget,\n maxPlays,\n commit,\n startPlayback,\n refuse,\n announce,\n emit,\n s.playFailed,\n ],\n );\n\n const handleButton = useCallback(() => {\n const element = elementRef.current;\n if (element === null || pendingRef.current || disabled) {\n return;\n }\n if (!element.paused) {\n element.pause();\n return;\n }\n if (!budgeted || isResume(element)) {\n startPlayback();\n return;\n }\n if (remaining === 0) {\n refuse();\n return;\n }\n if (remaining === 1 && !confirming) {\n setConfirming(true);\n return;\n }\n setConfirming(false);\n claimAndStart(element, false);\n }, [disabled, budgeted, isResume, startPlayback, remaining, refuse, confirming, claimAndStart]);\n\n // Seed the restored position once metadata is known, BEFORE the seek guard\n // can see it — otherwise the guard reverts the very restore it was given.\n const handleLoadedMetadata = useCallback(() => {\n const element = elementRef.current;\n if (element === null) {\n return;\n }\n setDuration(Number.isFinite(element.duration) ? element.duration : 0);\n const at = mediaBudget?.entry?.at;\n if (!seededRef.current && typeof at === 'number' && Number.isFinite(at) && at > 0) {\n seededRef.current = true;\n highWaterRef.current = at;\n resumeAtRef.current = at;\n element.currentTime = at;\n setElapsed(at);\n }\n }, [mediaBudget]);\n\n /**\n * The budget lives on the ELEMENT's `play` event, not on the button, so a\n * hardware media key, a browser extension or a scripted `el.play()` is\n * charged too — and an exhausted budget stops playback inside the event,\n * before a sample is audible.\n */\n const handlePlay = useCallback(() => {\n const element = elementRef.current;\n if (element === null) {\n return;\n }\n if (disabled) {\n element.pause();\n return;\n }\n if (selfStartRef.current) {\n selfStartRef.current = false;\n setPlaying(true);\n return;\n }\n if (pendingRef.current) {\n setPlaying(true);\n return;\n }\n if (isResume(element)) {\n setPlaying(true);\n resumeAtRef.current = null;\n return;\n }\n if (!budgeted) {\n setPlaying(true);\n return;\n }\n if (remaining === 0) {\n element.pause();\n refuse();\n return;\n }\n setPlaying(true);\n claimAndStart(element, true);\n }, [disabled, isResume, budgeted, remaining, refuse, claimAndStart]);\n\n const handlePause = useCallback(() => {\n const element = elementRef.current;\n setPlaying(false);\n if (element === null) {\n return;\n }\n // Every pause records the position — learner, browser, or the pager's own\n // pause as a pane hides. Without this, paging through a six-question\n // listening group and back would exhaust the budget by navigation alone.\n resumeAtRef.current = element.currentTime;\n reportPosition(element.currentTime);\n }, [reportPosition]);\n\n const handleEnded = useCallback(() => {\n setPlaying(false);\n resumeAtRef.current = null;\n highWaterRef.current = 0;\n chargeRef.current = null;\n setElapsed(0);\n reportPosition(0);\n }, [reportPosition]);\n\n const handleTimeUpdate = useCallback(() => {\n const element = elementRef.current;\n if (element === null) {\n return;\n }\n setElapsed(element.currentTime);\n highWaterRef.current = Math.max(highWaterRef.current, element.currentTime);\n const whole = Math.floor(element.currentTime);\n if (whole !== lastPositionRef.current) {\n lastPositionRef.current = whole;\n reportPosition(element.currentTime);\n }\n }, [reportPosition]);\n\n const handleSeeking = useCallback(() => {\n const element = elementRef.current;\n if (element === null || policy.seek !== 'none') {\n return;\n }\n if (Math.abs(element.currentTime - highWaterRef.current) > SEEK_EPSILON) {\n element.currentTime = highWaterRef.current;\n announce(s.seekBlocked);\n }\n }, [policy.seek, announce, s.seekBlocked]);\n\n const handleRateChange = useCallback(() => {\n const element = elementRef.current;\n if (element === null || policy.rate !== 'fixed' || element.playbackRate === 1) {\n return;\n }\n element.playbackRate = 1;\n announce(s.rateBlocked);\n }, [policy.rate, announce, s.rateBlocked]);\n\n const handleError = useCallback(() => {\n const charge = chargeRef.current;\n const element = elementRef.current;\n // Ordering matters: an element can error with nothing charged (a bad URL\n // the learner never pressed play on), so the charge must be proven to\n // exist before its position is read.\n const progressed =\n charge !== null && element !== null && element.currentTime - charge.at >= NO_PROGRESS;\n if (charge !== null && !progressed) {\n if (mediaBudget?.onPlayRefunded !== undefined) {\n usedRef.current = charge.claim.previousPlaysUsed;\n setUsed(charge.claim.previousPlaysUsed);\n mediaBudget.onPlayRefunded(charge.claim);\n emit('media-play-refunded', charge.claim.previousPlaysUsed);\n } else {\n emit('media-play-errored', usedRef.current);\n }\n }\n chargeRef.current = null;\n setPlaying(false);\n announce(s.playFailed);\n }, [mediaBudget, emit, announce, s.playFailed]);\n\n // Keep the element's volume in step with the controls.\n useEffect(() => {\n const element = elementRef.current;\n if (element !== null) {\n element.volume = volume;\n element.muted = muted;\n }\n }, [volume, muted]);\n\n const exhausted = budgeted && remaining === 0;\n const buttonLabel = pending ? s.preparing : playing ? s.pause : s.play;\n\n return (\n <figure className=\"lk-media\" data-controls=\"minimal\">\n {/* biome-ignore lint/a11y/useMediaCaption: captions are optional in the data contract — a <track> is rendered when captionsUrl is provided; absence is the author's documented choice (Req 14.5) */}\n <audio\n ref={elementRef}\n className=\"lk-media-el\"\n preload=\"metadata\"\n aria-label={media.alt || undefined}\n onLoadedMetadata={handleLoadedMetadata}\n onPlay={handlePlay}\n onPause={handlePause}\n onEnded={handleEnded}\n onTimeUpdate={handleTimeUpdate}\n onSeeking={handleSeeking}\n onRateChange={handleRateChange}\n onError={handleError}\n >\n <source src={media.url} />\n {media.captionsUrl ? <track kind=\"captions\" src={media.captionsUrl} default /> : null}\n </audio>\n\n {/* A timeline runs with the audio, not with the script. Forcing LTR here\n fixes seek direction for RTL locales by construction; the surrounding\n text keeps the document direction. */}\n <div\n className=\"lk-media-transport\"\n dir=\"ltr\"\n {...(suppliedStrings !== undefined && locale !== undefined ? { lang: locale } : {})}\n >\n <button\n type=\"button\"\n className=\"lk-media-play\"\n onClick={handleButton}\n // NEVER `disabled`: a natively disabled button leaves the focus\n // order, so a learner who tabs into nothing is told nothing — and\n // disabling it mid-attempt would drop focus to <body>. Kept\n // focusable, it re-announces the refusal when pressed.\n aria-disabled={exhausted || disabled || undefined}\n aria-busy={pending || undefined}\n aria-label={exhausted ? `${s.play} — ${s.noPlaysRemaining}` : undefined}\n aria-describedby={budgeted ? statusId : undefined}\n >\n {buttonLabel}\n </button>\n\n <span className=\"lk-media-time\">\n {formatTime(elapsed)} / {formatTime(duration)}\n </span>\n\n {policy.seek === 'allow' ? (\n <input\n type=\"range\"\n className=\"lk-media-scrub\"\n min={0}\n max={duration || 0}\n step={0.1}\n value={Math.min(elapsed, duration || 0)}\n aria-label={s.seek}\n aria-valuetext={`${formatTime(elapsed)} of ${formatTime(duration)}`}\n onChange={(event) => {\n const element = elementRef.current;\n if (element !== null) {\n element.currentTime = Number(event.target.value);\n }\n }}\n />\n ) : null}\n\n <button\n type=\"button\"\n className=\"lk-media-mute\"\n aria-pressed={muted}\n onClick={() => setMuted((m) => !m)}\n >\n {muted ? s.unmute : s.mute}\n </button>\n\n <input\n type=\"range\"\n className=\"lk-media-volume\"\n min={0}\n max={1}\n step={0.05}\n value={volume}\n aria-label={s.volume}\n onChange={(event) => setVolume(Number(event.target.value))}\n />\n\n {policy.rate === 'allow' ? (\n <select\n className=\"lk-media-rate\"\n aria-label={s.speed}\n defaultValue=\"1\"\n onChange={(event) => {\n const element = elementRef.current;\n if (element !== null) {\n element.playbackRate = Number(event.target.value);\n }\n }}\n >\n {['0.75', '1', '1.25', '1.5'].map((rate) => (\n <option key={rate} value={rate}>\n {rate}×\n </option>\n ))}\n </select>\n ) : null}\n </div>\n\n {budgeted ? (\n <p className=\"lk-media-plays\" id={statusId} role=\"status\">\n {remaining > 0 ? s.playsRemaining(remaining, maxPlays) : s.noPlaysRemaining}\n </p>\n ) : null}\n\n {/* Separate from the polite status on purpose: a refusal is a direct\n response to a user action and must not be the update a screen reader\n drops when two polite regions compete. */}\n <p className=\"lk-media-notice\" role=\"alert\" key={noticeNonce}>\n {notice}\n </p>\n\n {confirming ? (\n <fieldset className=\"lk-media-confirm\">\n <legend>{s.lastPlayConfirm}</legend>\n <button\n type=\"button\"\n className=\"lk-media-confirm-start\"\n // biome-ignore lint/a11y/noAutofocus: focus must land on the confirm action the press opened, or a keyboard learner is stranded on a button whose meaning just changed\n autoFocus\n onClick={() => {\n const element = elementRef.current;\n setConfirming(false);\n if (element !== null) {\n claimAndStart(element, false);\n }\n }}\n >\n {s.lastPlayStart}\n </button>\n <button\n type=\"button\"\n className=\"lk-media-confirm-cancel\"\n onClick={() => setConfirming(false)}\n >\n {s.lastPlayCancel}\n </button>\n </fieldset>\n ) : null}\n </figure>\n );\n}\n","import type { ActivityMedia as ActivityMediaData, InteractionEvent } from '@intellectif/lk-core';\nimport { resolvePlaybackPolicy } from '@intellectif/lk-core';\nimport type { MediaBudgetBinding, MediaTransportStrings, RenderMode } from '../types.js';\nimport { AudioTransport } from './AudioTransport.js';\n\n/**\n * Presentational media block shown above a question or passage. URL-only:\n * the SDK does not host media (consumer responsibility). Accessibility:\n * images use `alt` (schema requires it non-empty); audio/video expose an\n * optional label and a captions `<track>` when `captionsUrl` is provided;\n * `embed` renders a responsive sandboxed iframe with `alt` as its required\n * accessible `title` (Req 14.5).\n *\n * Audio additionally carries a playback policy (`media.playback`). With no\n * policy the emitted element is character-identical to what every release\n * before 0.8.0 produced; with something to enforce, the browser's control bar\n * is replaced by {@link AudioTransport}.\n */\nexport interface ActivityMediaProps {\n media: ActivityMediaData;\n renderMode?: RenderMode;\n disabled?: boolean;\n locale?: string;\n mediaBudget?: MediaBudgetBinding;\n mediaStrings?: Partial<MediaTransportStrings>;\n onInteraction?: (event: InteractionEvent) => void;\n}\n\nexport function ActivityMedia(props: ActivityMediaProps): React.JSX.Element {\n const { media, renderMode = 'practice' } = props;\n\n if (media.type === 'image') {\n return (\n <figure className=\"lk-media\">\n <img className=\"lk-media-el\" src={media.url} alt={media.alt ?? ''} />\n </figure>\n );\n }\n\n if (media.type === 'audio') {\n const policy = resolvePlaybackPolicy(media);\n\n // BEFORE the branch below, not after it. A budgeted policy always resolves\n // to `controls: 'minimal'`, so a guard placed after that early return\n // could never fire for the one case it exists to catch — the transport\n // rendered happily with no binding, counting nothing, while the paper\n // believed it had a budget.\n if (policy.maxPlays !== null && renderMode === 'exam' && props.mediaBudget === undefined) {\n throw new Error(\n `ActivityMedia: audio ${JSON.stringify(media.url)} declares maxPlays and is rendered in ` +\n 'renderMode \"exam\" without a `mediaBudget` binding. Nothing would persist the count, so ' +\n 'a refresh restores the full budget while the page says plays remain. Render it through ' +\n '<ActivitySequence mediaBudget={…}> or pass `mediaBudget` yourself.',\n );\n }\n\n // REVIEW ALWAYS GETS THE NATIVE BAR. The paper is graded; a learner\n // reviewing it cannot change an answer by listening again, so taking away\n // their scrubber, their speed control and their browser-localized controls\n // buys nothing — and costs a learner with a processing disability the\n // ability to work out what they got wrong.\n if (policy.controls === 'minimal' && renderMode !== 'review') {\n return <AudioTransport {...props} media={media} policy={policy} />;\n }\n\n const hints = renderMode === 'review' ? [] : policy.nativeControlHints;\n return (\n <figure className=\"lk-media\">\n {/* biome-ignore lint/a11y/useMediaCaption: captions are optional in the data contract — a <track> is rendered when captionsUrl is provided; absence is the author's documented choice (Req 14.5) */}\n <audio\n className=\"lk-media-el\"\n controls\n aria-label={media.alt || undefined}\n {...(hints.length > 0\n ? {\n controlsList: hints\n .map((hint) => (hint === 'hide-download' ? 'nodownload' : 'noplaybackrate'))\n .join(' '),\n }\n : {})}\n >\n <source src={media.url} />\n {media.captionsUrl ? <track kind=\"captions\" src={media.captionsUrl} default /> : null}\n </audio>\n </figure>\n );\n }\n\n if (media.type === 'embed') {\n return (\n <figure className=\"lk-media\">\n <div className=\"lk-media-embed\">\n <iframe\n className=\"lk-media-el\"\n src={media.url}\n title={media.alt ?? 'Embedded media'}\n loading=\"lazy\"\n referrerPolicy=\"strict-origin-when-cross-origin\"\n // The docblock always promised a sandboxed iframe; now it is one.\n // allow-scripts + allow-same-origin are required by provider\n // players (YouTube/Vimeo); top-navigation, forms, downloads and\n // popups stay blocked. Scheme allow-listing in MediaSchema is the\n // primary defense; this is depth.\n sandbox=\"allow-scripts allow-same-origin allow-presentation\"\n allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\"\n allowFullScreen\n />\n </div>\n </figure>\n );\n }\n\n return (\n <figure className=\"lk-media\">\n {/* biome-ignore lint/a11y/useMediaCaption: captions are optional in the data contract — a <track> is rendered when captionsUrl is provided; absence is the author's documented choice (Req 14.5) */}\n <video className=\"lk-media-el\" controls aria-label={media.alt || undefined}>\n <source src={media.url} />\n {media.captionsUrl ? <track kind=\"captions\" src={media.captionsUrl} default /> : null}\n </video>\n </figure>\n );\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/FillInTheBlanks/FillInTheBlanks.tsx","../src/components/FillInTheBlanks/index.tsx"],"names":["useMemo","isDevelopment","assertRedacted","result","validateActivity","ActivitySchemaError","useActivityState","useState","useRef","useEffect","score","xAPIBuilder","ANONYMOUS_ACTOR","objectIdFor","xapiDefinitionFor","jsxs","jsx","ActivityMedia","Fragment","FeedbackRegion","FillInTheBlanks","ActivityErrorBoundary"],"mappings":"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","file":"chunk-6DSA75TA.cjs","sourcesContent":["'use client';\n\nimport {\n ActivitySchemaError,\n assertRedacted,\n type FillInTheBlanksData,\n type FillInTheBlanksLearnerResponse,\n type LearnerResponse,\n type ScoringDetail,\n type ScoringResult,\n score,\n validateActivity,\n xAPIBuilder,\n xapiDefinitionFor,\n} from '@intellectif/lk-core';\nimport { type CSSProperties, useEffect, useMemo, useRef, useState } from 'react';\nimport { useActivityState } from '../../hooks/useActivityState.js';\nimport { ANONYMOUS_ACTOR, isDevelopment, objectIdFor } from '../_internal.js';\nimport { ActivityMedia } from '../shared/ActivityMedia.js';\nimport { FeedbackRegion } from '../shared/FeedbackRegion.js';\nimport type { ActivityProps } from '../types.js';\n\nexport interface FillInTheBlanksProps extends ActivityProps<FillInTheBlanksData> {\n /**\n * After submission, replace each incorrectly-answered blank with its first\n * accepted answer, styled distinctly (Req 5.7). Component-specific prop: it\n * is presentation, not activity data, and is outside the fixed Req-3.1 set.\n *\n * Ignored in `exam` mode — revealing the key is precisely what an exam must\n * not do, and on a `redact()` projection there is no key to reveal. In\n * `review` mode it applies only to blanks the supplied `outcome` marks\n * incorrect, and only when the caller passed data that still carries the key\n * (e.g. `redact(data, { reveal: 'after-submit' })`).\n */\n showCorrectAnswers?: boolean;\n}\n\nconst PLACEHOLDER = /\\{\\{\\s*([^{}]+?)\\s*\\}\\}/g;\n\ntype Segment = { kind: 'text'; value: string } | { kind: 'blank'; id: string; ordinal: number };\n\nfunction parsePassage(passage: string): Segment[] {\n const segments: Segment[] = [];\n const re = new RegExp(PLACEHOLDER);\n let last = 0;\n let ordinal = 0;\n let match = re.exec(passage);\n while (match !== null) {\n if (match.index > last) {\n segments.push({ kind: 'text', value: passage.slice(last, match.index) });\n }\n ordinal += 1;\n segments.push({ kind: 'blank', id: match[1] as string, ordinal });\n last = match.index + match[0].length;\n match = re.exec(passage);\n }\n if (last < passage.length) {\n segments.push({ kind: 'text', value: passage.slice(last) });\n }\n return segments;\n}\n\n/** Shared empty map: keeps the \"no response supplied\" path allocation-free. */\nconst NO_ANSWERS: Record<string, string> = {};\n\n/**\n * Reads the answer map out of a `LearnerResponse`. A response of another\n * activity type is treated as \"no answers\" rather than throwing: a controlled\n * host that has not yet swapped its state on an activity change would\n * otherwise crash the attempt.\n */\nfunction answersOf(response: LearnerResponse | undefined): Record<string, string> {\n return response !== undefined && response.type === 'fill-in-the-blanks'\n ? response.answers\n : NO_ANSWERS;\n}\n\n/**\n * Per-item correctness keyed by blank id. Prefers the unambiguous `outcome`\n * field (always written by the built-in scorers since lk-core 0.3.0) and\n * falls back to the deprecated `correct` boolean so a 0.2-era\n * consumer-constructed `ItemOutcome` still marks correctly in `review`.\n */\nfunction correctnessByItem(details: readonly ScoringDetail[]): Map<string, boolean> {\n const map = new Map<string, boolean>();\n for (const detail of details) {\n map.set(\n detail.itemId,\n detail.outcome !== undefined ? detail.outcome === 'correct' : detail.correct,\n );\n }\n return map;\n}\n\nexport function FillInTheBlanks({\n data,\n onComplete,\n onSubmit,\n onChange,\n onInteraction,\n value,\n defaultValue,\n defaultSubmitted,\n renderMode = 'practice',\n outcome,\n sanitizeHtml,\n mediaBudget,\n mediaStrings,\n theme,\n locale,\n disabled,\n showCorrectAnswers,\n}: FillInTheBlanksProps) {\n const isExam = renderMode === 'exam';\n const isReview = renderMode === 'review';\n\n // Dev-only boundary validation (Req 2.3); throws in render so the wrapping\n // ActivityErrorBoundary catches it. Re-runs only when data changes.\n const devError = useMemo(() => {\n if (!isDevelopment()) {\n return null;\n }\n if (data.redacted === true) {\n // A redact() projection cannot satisfy the full content schema — the\n // answer key is gone by design. Validate it against the STRICT redacted\n // schema instead, which additionally proves the payload is learner-safe.\n try {\n assertRedacted(data);\n return null;\n } catch (error) {\n return error instanceof Error ? error : new Error(String(error));\n }\n }\n const result = validateActivity('fill-in-the-blanks', data);\n return result.success ? null : new ActivitySchemaError('fill-in-the-blanks', result.errors);\n }, [data]);\n\n const { state, start, complete, getTimeSpent, reset } = useActivityState(\n defaultSubmitted === true ? 'completed' : 'idle',\n );\n // Controlled when `value` is supplied: the answers rendered are ALWAYS the\n // caller's, and internal state is never read. Uncontrolled otherwise,\n // seeded from `defaultValue`.\n const isControlled = value !== undefined;\n const [internalAnswers, setInternalAnswers] = useState<Record<string, string>>(() =>\n answersOf(defaultValue),\n );\n const [revealed, setRevealed] = useState<Set<string>>(() => new Set());\n const [result, setResult] = useState<ScoringResult | null>(null);\n const [summary, setSummary] = useState<string | null>(null);\n const [feedbackHidden, setFeedbackHidden] = useState(false);\n\n const answers = isControlled ? answersOf(value) : internalAnswers;\n\n // `defaultValue` is read only by the reset effect, and it is commonly an\n // inline object literal — putting it in that effect's deps would re-seed\n // (and wipe) the learner's typing on every render. Mirror it into a ref in\n // its own effect, which commits before the reset effect below.\n const defaultValueRef = useRef(defaultValue);\n useEffect(() => {\n defaultValueRef.current = defaultValue;\n }, [defaultValue]);\n\n // Mirror `defaultSubmitted` so the reset below returns to the SEEDED state.\n // Resetting unconditionally to idle unlocked an item the learner had already\n // committed — which made `defaultSubmitted` a no-op here, since this effect\n // runs right after the first paint.\n const defaultSubmittedRef = useRef(defaultSubmitted);\n useEffect(() => {\n defaultSubmittedRef.current = defaultSubmitted;\n }, [defaultSubmitted]);\n\n // Identity guard so the MOUNT run is a no-op. Without it this effect fires\n // after the first paint and undoes every seed it was just given; with it,\n // only a real `data` change resets. A parent that rebuilds structurally\n // identical entries in render (`activities={raw.map(redact)}`) must not\n // unlock or revert a restored answer — the pager already remounts a slot\n // whose activity actually changed, via its key.\n const lastDataRef = useRef(data);\n // `data` is an intentional reset trigger (Req 3.7), not read in the body.\n // biome-ignore lint/correctness/useExhaustiveDependencies: data is the reset trigger (Req 3.7)\n useEffect(() => {\n if (lastDataRef.current === data) {\n return;\n }\n lastDataRef.current = data;\n setInternalAnswers(answersOf(defaultValueRef.current));\n setRevealed(new Set());\n setResult(null);\n setSummary(null);\n setFeedbackHidden(false);\n reset(defaultSubmittedRef.current === true ? 'completed' : 'idle');\n }, [data, reset]);\n\n /*\n * RICH TEXT — deliberate, documented limitation (see `sanitizeHtml`).\n *\n * Every other activity can render its `*Html` field through the caller's\n * sanitiser, because the rich text is a LEAF: the sanitised string is\n * handed to one `dangerouslySetInnerHTML` node and nothing is inserted into\n * it. A fill-in-the-blanks passage is not a leaf — it is the CONTAINER of\n * the React-controlled `<input>`s, one per `{{id}}` placeholder.\n *\n * Placing those inputs inside sanitised HTML means slicing the sanitised\n * string at the placeholders and injecting each slice as its own HTML\n * fragment, which is unsafe and broken on both counts:\n * 1. Broken: the slices are unbalanced. `<p>A {{x}} B</p>` yields `<p>A `\n * and ` B</p>`; the fragment parser auto-closes both, so the input\n * lands OUTSIDE the paragraph and the authored block structure is\n * destroyed.\n * 2. Unsafe: the sanitiser's guarantee covers the document it returned,\n * not arbitrary substrings of it. A placeholder inside an attribute\n * (`<img alt=\"{{x}}\">`) splits mid-attribute, and re-parsing the\n * halves in a fresh context is exactly the mutation (mXSS) class of\n * transformation that voids a sanitiser's output guarantee. An exam\n * runner must never depend on that.\n *\n * The safe alternative — parse the sanitised string once into a detached\n * DOM, split only TEXT nodes at placeholders, and portal the inputs into\n * anchors — needs a DOM at render time (no SSR) and a large imperative\n * subsystem. Until that lands, the plain-text `passage` is rendered (always\n * escaped by React), which is correct, answerable, and safe in every mode.\n * `data.passageHtml` is carried and redacted by lk-core regardless, so no\n * authored content is lost — only this component declines to render it.\n */\n useEffect(() => {\n if (isDevelopment() && sanitizeHtml !== undefined && data.passageHtml !== undefined) {\n console.warn(\n '[lk-react] FillInTheBlanks ignores `passageHtml`: the passage hosts the answer inputs, ' +\n 'so rich HTML cannot be split at {{placeholders}} without voiding the sanitiser. ' +\n 'Rendering the plain `passage` instead.',\n );\n }\n }, [sanitizeHtml, data.passageHtml]);\n\n const segments = useMemo(() => parsePassage(data.passage), [data.passage]);\n const blankById = useMemo(() => new Map(data.blanks.map((b) => [b.id, b])), [data.blanks]);\n\n if (devError) {\n throw devError;\n }\n // Practice grades locally, which a redacted projection cannot support: the\n // submit handler below calls `score()`, which throws RedactedScoringError —\n // from an event handler, where no error boundary can catch it, AFTER the\n // learner has typed their answers. Fail loudly at render instead, in\n // production too. This mirrors the identical guard in MultipleChoice;\n // without it the two built-ins disagreed about the same accident, and\n // wiring an exam item into the self-grading mode is precisely what\n // `renderMode` exists to prevent.\n if (data.redacted === true && renderMode === 'practice') {\n throw new Error(\n `Fill-in-the-Blanks \"${data.id}\" received redacted activity data in renderMode \"practice\", ` +\n 'which grades locally and has no answer key to grade against. ' +\n 'Render redacted data with renderMode=\"exam\" (server grades) or \"review\" (pass `outcome`).',\n );\n }\n\n const submitted = state === 'completed';\n const inactive = disabled === true || submitted || isReview;\n\n /**\n * Whether the component may show answer-key-derived information (per-blank\n * correctness, authored feedback, correct answers). NEVER in `exam`; in\n * `practice` only after the component scored locally; in `review` only once\n * the caller supplies a SCORED outcome — an item that is still awaiting a\n * grade must not have its key revealed just because it is being read back.\n */\n const revealing = isReview ? outcome?.status === 'scored' : !isExam && submitted;\n\n const fireInteraction = (\n type: 'blank-filled' | 'hint-requested' | 'submitted',\n payload: Record<string, unknown>,\n ): void => {\n onInteraction?.({ type, activityId: data.id, timestamp: Date.now(), payload });\n };\n\n const handleChange = (blankId: string, text: string): void => {\n if (inactive) {\n return;\n }\n if (state === 'idle') {\n start();\n }\n const next = { ...answers, [blankId]: text };\n if (!isControlled) {\n setInternalAnswers(next);\n }\n onChange?.({ type: 'fill-in-the-blanks', answers: next });\n fireInteraction('blank-filled', { blankId, value: text });\n };\n\n const toggleHint = (blankId: string): void => {\n if (inactive) {\n return;\n }\n if (state === 'idle') {\n start();\n }\n setRevealed((prev) => {\n const next = new Set(prev);\n if (next.has(blankId)) {\n next.delete(blankId);\n return next;\n }\n next.add(blankId);\n return next;\n });\n // Only a reveal is a \"hint requested\"; hiding is not a new request.\n if (!revealed.has(blankId)) {\n fireInteraction('hint-requested', { blankId });\n }\n };\n\n const handleSubmit = (event: React.FormEvent): void => {\n event.preventDefault();\n if (inactive) {\n return;\n }\n const response: FillInTheBlanksLearnerResponse = { type: 'fill-in-the-blanks', answers };\n // Fires in every submitting mode and always BEFORE onComplete, so an exam\n // runner can persist the raw response regardless of local grading.\n onSubmit?.(response);\n\n if (isExam) {\n // EXAM: the client neither grades nor reveals. No score()/evaluate(), no\n // read of any answer-key field, no onComplete — and no xAPI statement,\n // because its `correctResponsesPattern` IS the answer key. The server\n // owns the grade; it emits the statement.\n complete();\n setSummary('Answer submitted.');\n fireInteraction('submitted', { answers });\n return;\n }\n\n // PRACTICE: unchanged v1 behaviour. `data` carries the key here — exam\n // mode is the only path that may be handed a redact() projection.\n const scoringResult = score('fill-in-the-blanks', data as FillInTheBlanksData, response);\n complete();\n const timeSpent = getTimeSpent();\n const xapiStatement = xAPIBuilder.buildAnsweredStatement({\n actor: ANONYMOUS_ACTOR,\n object: {\n id: objectIdFor(data.id),\n name: { [data.locale ?? 'en-US']: data.title },\n ...xapiDefinitionFor(data),\n },\n scoringResult,\n timeSpentMs: timeSpent,\n response: JSON.stringify(answers),\n });\n setResult(scoringResult);\n onComplete?.({\n score: scoringResult.score,\n maxScore: scoringResult.maxScore,\n passed: scoringResult.passed,\n timeSpent,\n xapiStatement,\n });\n // Core selects the authored overall feedback on `passed` (B3 fix).\n const overall = scoringResult.feedback;\n setSummary(\n `Answer submitted. Score ${Math.round(scoringResult.score * 100)}%. ${\n scoringResult.passed ? 'Passed.' : 'Not passed.'\n }${overall ? ` ${overall}` : ''}`,\n );\n fireInteraction('submitted', { answers, score: scoringResult.score });\n };\n\n const correctByBlank = useMemo(() => {\n if (isExam) {\n // Nothing is ever marked client-side in an exam.\n return new Map<string, boolean>();\n }\n if (isReview) {\n // Correctness comes ONLY from the server-computed outcome; the component\n // never scores in review, so an absent/ungraded outcome marks nothing.\n return outcome?.status === 'scored'\n ? correctnessByItem(outcome.details)\n : new Map<string, boolean>();\n }\n return correctnessByItem(result?.details ?? []);\n }, [isExam, isReview, outcome, result]);\n\n /** Review has no local score: its summary is read off `outcome`, or nothing. */\n const reviewSummary = useMemo(() => {\n if (!isReview || outcome === undefined) {\n return null;\n }\n if (outcome.status === 'scored') {\n return `Score ${Math.round(outcome.score * 100)}%. ${\n outcome.passed ? 'Passed.' : 'Not passed.'\n }${outcome.feedback ? ` ${outcome.feedback}` : ''}`;\n }\n return outcome.status === 'deferred' ? 'Not graded yet.' : 'No grade available.';\n }, [isReview, outcome]);\n\n return (\n <form\n className=\"lk-fib\"\n aria-label={data.title}\n lang={locale}\n data-render-mode={renderMode}\n style={theme as CSSProperties | undefined}\n onSubmit={handleSubmit}\n >\n {data.media ? (\n <ActivityMedia\n media={data.media}\n renderMode={renderMode}\n {...(mediaBudget !== undefined ? { mediaBudget } : {})}\n {...(mediaStrings !== undefined ? { mediaStrings } : {})}\n {...(onInteraction !== undefined ? { onInteraction } : {})}\n {...(locale !== undefined ? { locale } : {})}\n />\n ) : null}\n <fieldset disabled={inactive}>\n <p className=\"lk-fib-passage\">\n {segments.map((seg, i) => {\n if (seg.kind === 'text') {\n // biome-ignore lint/suspicious/noArrayIndexKey: passage segments are positional and static\n return <span key={`t${i}`}>{seg.value}</span>;\n }\n const blank = blankById.get(seg.id);\n if (!blank) {\n return null;\n }\n // `Renderable` only widens `scoringStrategy` at the type level, so\n // the answer-key fields still LOOK required here while a redact()\n // projection omits them at runtime. Read them defensively, and\n // never at all in exam mode.\n const acceptedAnswers: string[] | undefined = isExam\n ? undefined\n : blank.acceptedAnswers;\n const blankFeedback = isExam ? undefined : blank.feedback;\n const correctness = correctByBlank.get(seg.id);\n const hintId = `${data.id}-hint-${seg.id}`;\n const correctAnswer = acceptedAnswers?.[0];\n if (revealing && showCorrectAnswers && correctness === false && correctAnswer) {\n return (\n <span key={seg.id} className=\"lk-fib-answer\" data-correct=\"false\">\n {correctAnswer}\n </span>\n );\n }\n return (\n <span key={seg.id} className=\"lk-fib-blank\">\n <input\n type=\"text\"\n aria-label={`Fill in blank ${seg.ordinal}`}\n aria-describedby={blank.hint ? hintId : undefined}\n value={answers[seg.id] ?? ''}\n disabled={inactive}\n aria-disabled={inactive || undefined}\n data-correct={\n revealing && correctness !== undefined ? String(correctness) : undefined\n }\n onChange={(e) => handleChange(seg.id, e.target.value)}\n />\n {/*\n Hints stay available in exam mode by design: lk-core's field\n policy classifies `blanks.hint` as `public`, so it survives\n redact() and is not part of the answer key.\n */}\n {blank.hint ? (\n <>\n {/*\n Default affordance is an icon; the accessible NAME stays\n text via aria-label (screen readers + tests rely on it).\n The glyph is restylable/replaceable via .lk-fib-hint-btn.\n */}\n <button\n type=\"button\"\n className=\"lk-fib-hint-btn\"\n aria-controls={hintId}\n aria-expanded={revealed.has(seg.id)}\n aria-label={revealed.has(seg.id) ? 'Hide hint' : 'Show hint'}\n disabled={inactive}\n onClick={() => toggleHint(seg.id)}\n >\n <svg\n className=\"lk-fib-hint-icon\"\n viewBox=\"0 0 24 24\"\n width=\"16\"\n height=\"16\"\n aria-hidden=\"true\"\n focusable=\"false\"\n >\n <path\n fill=\"currentColor\"\n d=\"M9 21h6v-1H9v1Zm3-19a7 7 0 0 0-4 12.74V17h8v-2.26A7 7 0 0 0 12 2Z\"\n />\n </svg>\n </button>\n {/*\n Not role=\"tooltip\": a real ARIA tooltip is a named\n hover/focus popup with strict WCAG 1.4.13 constraints.\n This is an accessible click-to-toggle disclosure that is\n the input's aria-describedby target and is announced via\n aria-live; the skin gives it a tooltip-like *visual*\n without the tooltip *semantics*. (Refines Task 15.3.)\n */}\n <span id={hintId} aria-live=\"polite\">\n {revealed.has(seg.id) ? blank.hint : ''}\n </span>\n </>\n ) : null}\n {revealing && !feedbackHidden && blankFeedback ? (\n <span\n className=\"lk-fib-blank-feedback\"\n role=\"note\"\n data-correct={correctness !== undefined ? String(correctness) : undefined}\n >\n {blankFeedback}\n </span>\n ) : null}\n </span>\n );\n })}\n </p>\n {/* Review is read-only: there is nothing to submit. */}\n {isReview ? null : (\n <button type=\"submit\" disabled={inactive}>\n {isExam ? 'Submit answers' : 'Check answers'}\n </button>\n )}\n </fieldset>\n {revealing && data.blanks.some((b) => b.feedback) ? (\n <button\n type=\"button\"\n className=\"lk-fib-feedback-toggle\"\n aria-expanded={!feedbackHidden}\n onClick={() => setFeedbackHidden((h) => !h)}\n >\n {feedbackHidden ? 'Show feedback' : 'Hide feedback'}\n </button>\n ) : null}\n <FeedbackRegion id={`${data.id}-feedback`}>\n {isReview ? reviewSummary : summary}\n </FeedbackRegion>\n </form>\n );\n}\n","'use client';\n\nimport { ActivityErrorBoundary } from '../ActivityErrorBoundary.js';\nimport {\n FillInTheBlanks as FillInTheBlanksCore,\n type FillInTheBlanksProps,\n} from './FillInTheBlanks.js';\n\nexport type { FillInTheBlanksProps } from './FillInTheBlanks.js';\n\n/**\n * Public Fill-in-the-Blanks activity: the core wrapped in\n * `ActivityErrorBoundary` so a render failure (incl. the dev schema-validation\n * throw) degrades to an accessible fallback instead of crashing the host.\n */\nexport function FillInTheBlanks(props: FillInTheBlanksProps) {\n return (\n <ActivityErrorBoundary activityTitle={props.data?.title}>\n <FillInTheBlanksCore {...props} />\n </ActivityErrorBoundary>\n );\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/ActivityErrorBoundary.tsx","../src/components/_internal.ts","../src/components/shared/FeedbackRegion.tsx"],"names":["jsx"],"mappings":";;;;AAIA,SAAS,YAAA,GAAwB;AAC/B,EAAA,MAAM,CAAA,GAAI,UAAA;AACV,EAAA,OAAO,CAAA,CAAE,OAAA,EAAS,GAAA,EAAK,QAAA,KAAa,YAAA;AACtC;AAkBO,IAAM,qBAAA,GAAN,cAAoC,SAAA,CAGzC;AAAA,EACA,KAAA,GAAoC,EAAE,KAAA,EAAO,IAAA,EAAK;AAAA,EAElD,OAAO,yBAAyB,KAAA,EAA0C;AACxE,IAAA,OAAO,EAAE,KAAA,EAAM;AAAA,EACjB;AAAA,EAEA,MAAA,GAAoB;AAClB,IAAA,MAAM,EAAE,KAAA,EAAM,GAAI,IAAA,CAAK,KAAA;AACvB,IAAA,IAAI,CAAC,KAAA,EAAO;AACV,MAAA,OAAO,KAAK,KAAA,CAAM,QAAA;AAAA,IACpB;AAEA,IAAA,IAAI,CAAC,cAAa,EAAG;AACnB,MAAA,uBACE,IAAA,CAAC,KAAA,EAAA,EAAI,IAAA,EAAK,OAAA,EACR,QAAA,EAAA;AAAA,wBAAA,GAAA,CAAC,YAAO,QAAA,EAAA,2BAAA,EAAyB,CAAA;AAAA,wBACjC,GAAA,CAAC,KAAA,EAAA,EAAK,QAAA,EAAA,KAAA,CAAM,KAAA,IAAS,MAAM,OAAA,EAAQ;AAAA,OAAA,EACrC,CAAA;AAAA,IAEJ;AAEA,IAAA,uBACE,GAAA,CAAC,KAAA,EAAA,EAAI,IAAA,EAAK,OAAA,EACP,QAAA,EAAA,IAAA,CAAK,KAAA,CAAM,aAAA,GACR,CAAA,CAAA,EAAI,IAAA,CAAK,KAAA,CAAM,aAAa,CAAA,yBAAA,CAAA,GAC5B,uCAAA,EACN,CAAA;AAAA,EAEJ;AACF;;;ACvDO,SAAS,aAAA,GAAyB;AACvC,EAAA,MAAM,CAAA,GAAI,UAAA;AACV,EAAA,OAAO,CAAA,CAAE,OAAA,EAAS,GAAA,EAAK,QAAA,KAAa,YAAA;AACtC;AAOO,IAAM,eAAA,GAA6B;AAAA,EACxC,UAAA,EAAY,OAAA;AAAA,EACZ,OAAA,EAAS,EAAE,QAAA,EAAU,6CAAA,EAA+C,MAAM,WAAA;AAC5E;AAGO,SAAS,YAAY,EAAA,EAAoB;AAC9C,EAAA,OAAO,CAAA,0BAAA,EAA6B,kBAAA,CAAmB,EAAE,CAAC,CAAA,CAAA;AAC5D;AAQO,SAAS,eAAA,GAA0B;AACxC,EAAA,MAAM,IAAK,UAAA,CAA0D,MAAA;AACrE,EAAA,IAAI,GAAG,UAAA,EAAY;AACjB,IAAA,OAAO,EAAE,UAAA,EAAW;AAAA,EACtB;AACA,EAAA,OAAO,KAAK,IAAA,CAAK,MAAA,GAAS,QAAA,CAAS,EAAE,EAAE,KAAA,CAAM,CAAC,CAAC,CAAA,EAAG,IAAA,CAAK,QAAO,CAAE,QAAA,CAAS,EAAE,CAAA,CAAE,KAAA,CAAM,CAAC,CAAC,CAAA,CAAA;AACvF;ACrBO,SAAS,cAAA,CAAe,EAAE,EAAA,EAAI,SAAA,EAAW,UAAS,EAAwB;AAC/E,EAAA,uBACEA,IAAC,KAAA,EAAA,EAAI,EAAA,EAAQ,WAAsB,WAAA,EAAU,QAAA,EAAS,aAAA,EAAY,MAAA,EAC/D,QAAA,EACH,CAAA;AAEJ","file":"chunk-6F4X2NKL.js","sourcesContent":["'use client';\n\nimport { Component, type ReactNode } from 'react';\n\nfunction isProduction(): boolean {\n const g = globalThis as { process?: { env?: Record<string, string | undefined> } };\n return g.process?.env?.NODE_ENV === 'production';\n}\n\nexport interface ActivityErrorBoundaryProps {\n /** Shown in the production fallback when a render error occurs. */\n activityTitle?: string;\n children: ReactNode;\n}\n\ninterface ActivityErrorBoundaryState {\n error: Error | null;\n}\n\n/**\n * Catches render errors in an activity subtree. In development it renders the\n * full error stack to aid debugging; in production it renders an accessible,\n * generic fallback (optionally naming the activity) so a single broken\n * activity never crashes the host application.\n */\nexport class ActivityErrorBoundary extends Component<\n ActivityErrorBoundaryProps,\n ActivityErrorBoundaryState\n> {\n state: ActivityErrorBoundaryState = { error: null };\n\n static getDerivedStateFromError(error: Error): ActivityErrorBoundaryState {\n return { error };\n }\n\n render(): ReactNode {\n const { error } = this.state;\n if (!error) {\n return this.props.children;\n }\n\n if (!isProduction()) {\n return (\n <div role=\"alert\">\n <strong>Activity failed to render</strong>\n <pre>{error.stack ?? error.message}</pre>\n </div>\n );\n }\n\n return (\n <div role=\"alert\">\n {this.props.activityTitle\n ? `\"${this.props.activityTitle}\" could not be displayed.`\n : 'This activity could not be displayed.'}\n </div>\n );\n }\n}\n","import type { XAPIActor } from '@intellectif/lk-core';\n\n/** Dev = anything other than an explicit production NODE_ENV (browser-safe). */\nexport function isDevelopment(): boolean {\n const g = globalThis as { process?: { env?: Record<string, string | undefined> } };\n return g.process?.env?.NODE_ENV !== 'production';\n}\n\n/**\n * Activity components cannot know the learner's identity (Req 3.1 fixes the\n * prop set). They emit a structurally-valid statement with this anonymous\n * actor; real identity is applied by the useXAPI/LRS layer (XAPIConfig.actor).\n */\nexport const ANONYMOUS_ACTOR: XAPIActor = {\n objectType: 'Agent',\n account: { homePage: 'https://github.com/intellectif/learning-kit', name: 'anonymous' },\n};\n\n/** `data.id` is not an IRI; xAPI object ids must be. Wrap it as a URN. */\nexport function objectIdFor(id: string): string {\n return `urn:learning-kit:activity:${encodeURIComponent(id)}`;\n}\n\n/**\n * Random per-mount session id for the shuffle seed. `crypto.randomUUID` is\n * unavailable outside secure contexts (plain-http LAN/staging hosts), so a\n * Math.random fallback keeps the component from crashing there — the seed\n * only randomises presentation order, so cryptographic strength is not needed.\n */\nexport function randomSessionId(): string {\n const c = (globalThis as { crypto?: { randomUUID?: () => string } }).crypto;\n if (c?.randomUUID) {\n return c.randomUUID();\n }\n return `s-${Math.random().toString(36).slice(2)}${Math.random().toString(36).slice(2)}`;\n}\n","import type { ReactNode } from 'react';\n\nexport interface FeedbackRegionProps {\n /** Identifier so activity inputs can reference this region via aria. */\n id?: string;\n className?: string;\n children?: ReactNode;\n}\n\n/**\n * A polite ARIA live region. Screen readers announce content injected here\n * (score, feedback, state changes) without moving focus. Pure/presentational\n * — safe to render in any environment.\n */\nexport function FeedbackRegion({ id, className, children }: FeedbackRegionProps) {\n return (\n <div id={id} className={className} aria-live=\"polite\" aria-atomic=\"true\">\n {children}\n </div>\n );\n}\n"]}