@intellectif/lk-react 7.0.1 → 8.0.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.
- package/CHANGELOG.md +106 -0
- package/README.md +8 -3
- package/dist/{WrittenResponse-zWT-Go1g.d.cts → WrittenResponse-BJr9gBpY.d.cts} +3 -1
- package/dist/{WrittenResponse-BJL4dGsR.d.ts → WrittenResponse-Du5CZnwn.d.ts} +3 -1
- package/dist/{chunk-UFWF3BKA.cjs → chunk-2VLKSNYJ.cjs} +13 -24
- package/dist/chunk-2VLKSNYJ.cjs.map +1 -0
- package/dist/{chunk-PPJ47THY.cjs → chunk-4AHZBTBF.cjs} +27 -29
- package/dist/chunk-4AHZBTBF.cjs.map +1 -0
- package/dist/{chunk-PGEG7UZJ.cjs → chunk-4XPKKNJQ.cjs} +5 -5
- package/dist/chunk-4XPKKNJQ.cjs.map +1 -0
- package/dist/{chunk-52A5UNCH.js → chunk-7OHPBLPH.js} +13 -24
- package/dist/chunk-7OHPBLPH.js.map +1 -0
- package/dist/{chunk-ABHJW4LC.cjs → chunk-7VIXWVFN.cjs} +11 -14
- package/dist/chunk-7VIXWVFN.cjs.map +1 -0
- package/dist/chunk-AHEV52U7.cjs +122 -0
- package/dist/chunk-AHEV52U7.cjs.map +1 -0
- package/dist/{chunk-7YLY3G3E.js → chunk-BLQBFI7Z.js} +23 -25
- package/dist/chunk-BLQBFI7Z.js.map +1 -0
- package/dist/{chunk-VHULVDPG.js → chunk-EQ5BZTLV.js} +29 -14
- package/dist/chunk-EQ5BZTLV.js.map +1 -0
- package/dist/{chunk-6F4X2NKL.js → chunk-ESWHIC4Z.js} +5 -5
- package/dist/chunk-ESWHIC4Z.js.map +1 -0
- package/dist/chunk-G3QPOYDR.cjs +198 -0
- package/dist/chunk-G3QPOYDR.cjs.map +1 -0
- package/dist/{chunk-XS2SZINH.js → chunk-HEJPEJW7.js} +10 -13
- package/dist/chunk-HEJPEJW7.js.map +1 -0
- package/dist/{chunk-C3HR55JD.js → chunk-MMFHKCWF.js} +24 -13
- package/dist/chunk-MMFHKCWF.js.map +1 -0
- package/dist/{chunk-3VQV7ICQ.js → chunk-OI4IKH2Z.js} +18 -18
- package/dist/chunk-OI4IKH2Z.js.map +1 -0
- package/dist/{chunk-XGQKID2P.cjs → chunk-SGIUPGDA.cjs} +29 -18
- package/dist/chunk-SGIUPGDA.cjs.map +1 -0
- package/dist/chunk-T4CKODCJ.js +115 -0
- package/dist/chunk-T4CKODCJ.js.map +1 -0
- package/dist/{chunk-6DSA75TA.cjs → chunk-VVZ7SAE7.cjs} +35 -20
- package/dist/chunk-VVZ7SAE7.cjs.map +1 -0
- package/dist/chunk-WJTCIW5R.js +196 -0
- package/dist/chunk-WJTCIW5R.js.map +1 -0
- package/dist/{chunk-FB6XKQFV.cjs → chunk-ZDB3SLYR.cjs} +23 -23
- package/dist/chunk-ZDB3SLYR.cjs.map +1 -0
- package/dist/components/ActivityPreview.cjs +20 -0
- package/dist/components/ActivityPreview.cjs.map +1 -0
- package/dist/components/ActivityPreview.d.cts +70 -0
- package/dist/components/ActivityPreview.d.ts +70 -0
- package/dist/components/ActivityPreview.js +11 -0
- package/dist/components/ActivityPreview.js.map +1 -0
- package/dist/components/ActivitySequence.cjs +9 -8
- package/dist/components/ActivitySequence.d.cts +19 -6
- package/dist/components/ActivitySequence.d.ts +19 -6
- package/dist/components/ActivitySequence.js +8 -7
- package/dist/components/FillInTheBlanks.cjs +5 -4
- package/dist/components/FillInTheBlanks.d.cts +1 -1
- package/dist/components/FillInTheBlanks.d.ts +1 -1
- package/dist/components/FillInTheBlanks.js +4 -3
- package/dist/components/MultipleChoice.cjs +5 -4
- package/dist/components/MultipleChoice.d.cts +1 -1
- package/dist/components/MultipleChoice.d.ts +1 -1
- package/dist/components/MultipleChoice.js +4 -3
- package/dist/components/StimulusPanel.cjs +4 -3
- package/dist/components/StimulusPanel.d.cts +4 -2
- package/dist/components/StimulusPanel.d.ts +4 -2
- package/dist/components/StimulusPanel.js +3 -2
- package/dist/components/WrittenResponse.cjs +5 -4
- package/dist/components/WrittenResponse.d.cts +3 -3
- package/dist/components/WrittenResponse.d.ts +3 -3
- package/dist/components/WrittenResponse.js +4 -3
- package/dist/i18n/LkIntlProvider.cjs +33 -0
- package/dist/i18n/LkIntlProvider.cjs.map +1 -0
- package/dist/i18n/LkIntlProvider.d.cts +71 -0
- package/dist/i18n/LkIntlProvider.d.ts +71 -0
- package/dist/i18n/LkIntlProvider.js +4 -0
- package/dist/i18n/LkIntlProvider.js.map +1 -0
- package/dist/index.cjs +47 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -3
- package/dist/index.d.ts +5 -3
- package/dist/index.js +10 -8
- package/dist/index.js.map +1 -1
- package/dist/{types-eXX4hzSj.d.ts → strings-vQDqESTS.d.cts} +175 -11
- package/dist/{types-eXX4hzSj.d.cts → strings-vQDqESTS.d.ts} +175 -11
- package/dist/theme/skin.css +4 -4
- package/package.json +23 -3
- package/dist/chunk-3VQV7ICQ.js.map +0 -1
- package/dist/chunk-52A5UNCH.js.map +0 -1
- package/dist/chunk-6DSA75TA.cjs.map +0 -1
- package/dist/chunk-6F4X2NKL.js.map +0 -1
- package/dist/chunk-7YLY3G3E.js.map +0 -1
- package/dist/chunk-ABHJW4LC.cjs.map +0 -1
- package/dist/chunk-C3HR55JD.js.map +0 -1
- package/dist/chunk-FB6XKQFV.cjs.map +0 -1
- package/dist/chunk-PGEG7UZJ.cjs.map +0 -1
- package/dist/chunk-PPJ47THY.cjs.map +0 -1
- package/dist/chunk-UFWF3BKA.cjs.map +0 -1
- package/dist/chunk-VHULVDPG.js.map +0 -1
- package/dist/chunk-XGQKID2P.cjs.map +0 -1
- 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
|
}
|
|
@@ -171,14 +188,11 @@ declare function asRenderable<TData extends ActivityData>(redacted: RedactedActi
|
|
|
171
188
|
* />
|
|
172
189
|
* ```
|
|
173
190
|
*
|
|
174
|
-
* Pass `renderMode="exam"` (or `"review"`). Redacted data has no answer key,
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
-
*
|
|
178
|
-
*
|
|
179
|
-
* no such guard, so a redacted essay renders and stays answerable in
|
|
180
|
-
* `practice`. A mis-wired essay item is therefore SILENT — set `renderMode`
|
|
181
|
-
* explicitly rather than relying on the throw.
|
|
191
|
+
* Pass `renderMode="exam"` (or `"review"`). Redacted data has no answer key, and
|
|
192
|
+
* all three built-in activities throw at render in the default `practice` mode
|
|
193
|
+
* rather than fail later: `<MultipleChoice>` and `<FillInTheBlanks>` because
|
|
194
|
+
* they grade locally, and `<WrittenResponse>` because `practice` still runs its
|
|
195
|
+
* local submit path and emits a practice-mode xAPI statement.
|
|
182
196
|
*/
|
|
183
197
|
declare function asRenderableSequence(entries: readonly (RedactedActivityData | RedactedItemGroupData)[]): readonly SequenceEntry<RenderableActivity>[];
|
|
184
198
|
/** Structural shape of a `redactItemGroup()` projection, as it arrives from a server. */
|
|
@@ -186,7 +200,12 @@ type RedactedItemGroupData = ItemGroup<RedactedActivityData> & {
|
|
|
186
200
|
redacted: true;
|
|
187
201
|
};
|
|
188
202
|
/**
|
|
189
|
-
* Every
|
|
203
|
+
* Every word the SDK's audio transport renders.
|
|
204
|
+
*
|
|
205
|
+
* Words, not characters: the `m:ss / m:ss` clock is digits and punctuation and
|
|
206
|
+
* is formatted by the component, because it reads identically in every locale
|
|
207
|
+
* this SDK targets. The scrubber's SPOKEN value does have a word in it and does
|
|
208
|
+
* have a key ({@link MediaTransportStrings.timeValue}).
|
|
190
209
|
*
|
|
191
210
|
* Supply them to translate it. These are the highest-stakes strings on a
|
|
192
211
|
* listening paper — "No plays remaining" decides whether a learner believes
|
|
@@ -204,6 +223,12 @@ interface MediaTransportStrings {
|
|
|
204
223
|
volume: string;
|
|
205
224
|
speed: string;
|
|
206
225
|
seek: string;
|
|
226
|
+
/**
|
|
227
|
+
* Spoken value of the scrubber, e.g. `('1:05', '4:30') => '1:05 of 4:30'`.
|
|
228
|
+
* Takes ALREADY-FORMATTED `m:ss` strings: a translation should not have to
|
|
229
|
+
* reimplement the clock to change the word between them.
|
|
230
|
+
*/
|
|
231
|
+
timeValue: (elapsed: string, duration: string) => string;
|
|
207
232
|
/** e.g. `(1, 2) => '1 of 2 plays remaining'`. */
|
|
208
233
|
playsRemaining: (remaining: number, max: number) => string;
|
|
209
234
|
noPlaysRemaining: string;
|
|
@@ -304,4 +329,143 @@ interface SequenceMediaBudget {
|
|
|
304
329
|
strings?: Partial<MediaTransportStrings>;
|
|
305
330
|
}
|
|
306
331
|
|
|
307
|
-
|
|
332
|
+
/**
|
|
333
|
+
* Every string the SDK's own chrome renders.
|
|
334
|
+
*
|
|
335
|
+
* Until 0.8.1 these were literals, and `locale` set only the `lang` attribute —
|
|
336
|
+
* so a learner sitting a Spanish paper read a Spanish passage inside an English
|
|
337
|
+
* scaffold, and "No plays remaining" on a listening exam was untranslatable.
|
|
338
|
+
* This is the whole surface, in one place, so a consumer can replace it.
|
|
339
|
+
*
|
|
340
|
+
* **Interpolation and plurals are functions, not format strings.** A message
|
|
341
|
+
* like `"Question {n} of {total}"` needs a parser, and a parser needs plural
|
|
342
|
+
* rules for every locale the SDK does not know. A function hands both problems
|
|
343
|
+
* to the consumer, who has `Intl.PluralRules` and knows their own language:
|
|
344
|
+
*
|
|
345
|
+
* ```ts
|
|
346
|
+
* questionProgress: (index, total) => `Pregunta ${index} de ${total}`,
|
|
347
|
+
* wordCount: (n) => `${n} ${n === 1 ? 'palabra' : 'palabras'}`,
|
|
348
|
+
* ```
|
|
349
|
+
*
|
|
350
|
+
* It also lets a translation reorder its arguments, which a positional token
|
|
351
|
+
* cannot, and TypeScript checks the arity. No parser, no message catalogue
|
|
352
|
+
* format, no runtime dependency.
|
|
353
|
+
*
|
|
354
|
+
* **What is deliberately NOT here: thrown errors.** Every `throw` in this
|
|
355
|
+
* package is addressed to the developer who wired the component up, not to the
|
|
356
|
+
* learner. Translating them would make them unsearchable and would put the one
|
|
357
|
+
* audience who can act on them behind a translation. They stay English.
|
|
358
|
+
*/
|
|
359
|
+
interface LkStrings {
|
|
360
|
+
/** Multiple Choice submit button, and Written Response. */
|
|
361
|
+
submit: string;
|
|
362
|
+
/** Fill-in-the-Blanks in `practice`: the learner is checking their own work. */
|
|
363
|
+
checkAnswers: string;
|
|
364
|
+
/** Fill-in-the-Blanks in `exam`: nothing is checked on the client. */
|
|
365
|
+
submitAnswers: string;
|
|
366
|
+
/**
|
|
367
|
+
* The scored result, read by a screen reader on submit.
|
|
368
|
+
*
|
|
369
|
+
* `percent` is already rounded to a whole number. The authored overall
|
|
370
|
+
* feedback is appended SEPARATELY by the component, so a translation of this
|
|
371
|
+
* sentence never has to carry the author's words — the two were previously
|
|
372
|
+
* concatenated into one literal, which made the score sentence untranslatable
|
|
373
|
+
* without also touching authored content.
|
|
374
|
+
*/
|
|
375
|
+
scoreAnnouncement: (percent: number, passed: boolean) => string;
|
|
376
|
+
/** Prefix announced on submit, before the score sentence. */
|
|
377
|
+
answerSubmitted: string;
|
|
378
|
+
/** An answer submitted for grading that has none yet. Never rendered as 0%. */
|
|
379
|
+
notGradedYet: string;
|
|
380
|
+
/** An outcome that carries no grade and never will. */
|
|
381
|
+
noGradeAvailable: string;
|
|
382
|
+
showFeedback: string;
|
|
383
|
+
hideFeedback: string;
|
|
384
|
+
/** Accessible name of a blank's input. `ordinal` is 1-based. */
|
|
385
|
+
blankLabel: (ordinal: number) => string;
|
|
386
|
+
showHint: string;
|
|
387
|
+
hideHint: string;
|
|
388
|
+
/**
|
|
389
|
+
* Announced when an essay is handed in. Distinct from {@link LkStrings.awaitingGrade},
|
|
390
|
+
* which is the `review`-mode state of a submission made earlier: this one
|
|
391
|
+
* confirms the act, and is the only thing a learner hears at the moment they
|
|
392
|
+
* commit work that nothing on the client will grade.
|
|
393
|
+
*/
|
|
394
|
+
responseSubmitted: string;
|
|
395
|
+
/** Live word counter. Needs the count for languages whose plural rules differ. */
|
|
396
|
+
wordCount: (count: number) => string;
|
|
397
|
+
/** The authored bounds, e.g. "50–200 words". `min` is 0 when unbounded below. */
|
|
398
|
+
wordBounds: (min: number, max: number) => string;
|
|
399
|
+
/** A rubric criterion the grader marked not applicable. */
|
|
400
|
+
notApplicable: string;
|
|
401
|
+
/** A returned grade flagged for a human to look at — learner-visible by design. */
|
|
402
|
+
awaitingHumanReview: string;
|
|
403
|
+
/** Submitted, no grade back yet. */
|
|
404
|
+
awaitingGrade: string;
|
|
405
|
+
/** The grader could not produce a grade. */
|
|
406
|
+
couldNotBeGraded: string;
|
|
407
|
+
previous: string;
|
|
408
|
+
next: string;
|
|
409
|
+
/** Progress label, also the question region's accessible name. `index` is 1-based. */
|
|
410
|
+
questionProgress: (index: number, total: number) => string;
|
|
411
|
+
/** Shown when no renderer is registered for an activity type. */
|
|
412
|
+
unsupportedActivity: string;
|
|
413
|
+
/**
|
|
414
|
+
* `<ActivityPreview>`, in place of a draft that still has something missing.
|
|
415
|
+
* Author-facing, not learner-facing — but an editor is translated too.
|
|
416
|
+
*/
|
|
417
|
+
previewIncomplete: string;
|
|
418
|
+
/** `<ActivityPreview>`, in place of a draft with something wrong in it. */
|
|
419
|
+
previewInvalid: string;
|
|
420
|
+
/**
|
|
421
|
+
* Accessible name of a provider iframe whose author supplied no `alt`.
|
|
422
|
+
* `MediaSchema` requires `alt` on an embed, so validated content never
|
|
423
|
+
* reaches this — but `validateActivity` is opt-in, and a screen-reader
|
|
424
|
+
* learner meeting an unvalidated one should not meet it in English.
|
|
425
|
+
*/
|
|
426
|
+
embeddedMedia: string;
|
|
427
|
+
/**
|
|
428
|
+
* Accessible name of the stimulus region when the author gave it no title.
|
|
429
|
+
* Keyed by `Stimulus['kind']`, because "Recording" and "Passage" do not share
|
|
430
|
+
* a gender or an article in every language.
|
|
431
|
+
*/
|
|
432
|
+
stimulusKind: Record<Stimulus['kind'], string>;
|
|
433
|
+
/**
|
|
434
|
+
* How far the material carries, e.g. "Questions 3–8". Called with equal
|
|
435
|
+
* `first` and `last` for a single question, so a translation can choose its
|
|
436
|
+
* own singular rather than receive a pre-pluralised English one.
|
|
437
|
+
*/
|
|
438
|
+
stimulusRange: (first: number, last: number) => string;
|
|
439
|
+
/** Production fallback when an activity throws during render. */
|
|
440
|
+
activityFailed: string;
|
|
441
|
+
/** The same, when the activity's title is known. */
|
|
442
|
+
activityFailedNamed: (title: string) => string;
|
|
443
|
+
/** Production fallback when the activity has no title to name. */
|
|
444
|
+
activityFailedUnnamed: string;
|
|
445
|
+
/**
|
|
446
|
+
* The listening-paper transport. Shipped in 0.8.0 as its own `mediaStrings`
|
|
447
|
+
* prop, which still works — this is the same shape, reachable from the one
|
|
448
|
+
* provider so a consumer does not wire two mechanisms.
|
|
449
|
+
*/
|
|
450
|
+
media: MediaTransportStrings;
|
|
451
|
+
}
|
|
452
|
+
/** A partial override, nested one level for {@link LkStrings.media}. */
|
|
453
|
+
type LkStringsOverride = Partial<Omit<LkStrings, 'media' | 'stimulusKind'>> & {
|
|
454
|
+
media?: Partial<MediaTransportStrings>;
|
|
455
|
+
stimulusKind?: Partial<Record<Stimulus['kind'], string>>;
|
|
456
|
+
};
|
|
457
|
+
/**
|
|
458
|
+
* English defaults, byte-identical to what every component rendered before the
|
|
459
|
+
* strings surface existed. Supplying no override changes nothing on screen.
|
|
460
|
+
*/
|
|
461
|
+
declare const DEFAULT_STRINGS: LkStrings;
|
|
462
|
+
/**
|
|
463
|
+
* Layers an override onto a base dictionary.
|
|
464
|
+
*
|
|
465
|
+
* Two levels deep and no further, because the shape is two levels deep. A
|
|
466
|
+
* generic deep merge would silently accept a nested object where a function
|
|
467
|
+
* belongs and fail at render instead of at the call site.
|
|
468
|
+
*/
|
|
469
|
+
declare function mergeStrings(base: LkStrings, override?: LkStringsOverride): LkStrings;
|
|
470
|
+
|
|
471
|
+
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 };
|
|
@@ -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
|
}
|
|
@@ -171,14 +188,11 @@ declare function asRenderable<TData extends ActivityData>(redacted: RedactedActi
|
|
|
171
188
|
* />
|
|
172
189
|
* ```
|
|
173
190
|
*
|
|
174
|
-
* Pass `renderMode="exam"` (or `"review"`). Redacted data has no answer key,
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
-
*
|
|
178
|
-
*
|
|
179
|
-
* no such guard, so a redacted essay renders and stays answerable in
|
|
180
|
-
* `practice`. A mis-wired essay item is therefore SILENT — set `renderMode`
|
|
181
|
-
* explicitly rather than relying on the throw.
|
|
191
|
+
* Pass `renderMode="exam"` (or `"review"`). Redacted data has no answer key, and
|
|
192
|
+
* all three built-in activities throw at render in the default `practice` mode
|
|
193
|
+
* rather than fail later: `<MultipleChoice>` and `<FillInTheBlanks>` because
|
|
194
|
+
* they grade locally, and `<WrittenResponse>` because `practice` still runs its
|
|
195
|
+
* local submit path and emits a practice-mode xAPI statement.
|
|
182
196
|
*/
|
|
183
197
|
declare function asRenderableSequence(entries: readonly (RedactedActivityData | RedactedItemGroupData)[]): readonly SequenceEntry<RenderableActivity>[];
|
|
184
198
|
/** Structural shape of a `redactItemGroup()` projection, as it arrives from a server. */
|
|
@@ -186,7 +200,12 @@ type RedactedItemGroupData = ItemGroup<RedactedActivityData> & {
|
|
|
186
200
|
redacted: true;
|
|
187
201
|
};
|
|
188
202
|
/**
|
|
189
|
-
* Every
|
|
203
|
+
* Every word the SDK's audio transport renders.
|
|
204
|
+
*
|
|
205
|
+
* Words, not characters: the `m:ss / m:ss` clock is digits and punctuation and
|
|
206
|
+
* is formatted by the component, because it reads identically in every locale
|
|
207
|
+
* this SDK targets. The scrubber's SPOKEN value does have a word in it and does
|
|
208
|
+
* have a key ({@link MediaTransportStrings.timeValue}).
|
|
190
209
|
*
|
|
191
210
|
* Supply them to translate it. These are the highest-stakes strings on a
|
|
192
211
|
* listening paper — "No plays remaining" decides whether a learner believes
|
|
@@ -204,6 +223,12 @@ interface MediaTransportStrings {
|
|
|
204
223
|
volume: string;
|
|
205
224
|
speed: string;
|
|
206
225
|
seek: string;
|
|
226
|
+
/**
|
|
227
|
+
* Spoken value of the scrubber, e.g. `('1:05', '4:30') => '1:05 of 4:30'`.
|
|
228
|
+
* Takes ALREADY-FORMATTED `m:ss` strings: a translation should not have to
|
|
229
|
+
* reimplement the clock to change the word between them.
|
|
230
|
+
*/
|
|
231
|
+
timeValue: (elapsed: string, duration: string) => string;
|
|
207
232
|
/** e.g. `(1, 2) => '1 of 2 plays remaining'`. */
|
|
208
233
|
playsRemaining: (remaining: number, max: number) => string;
|
|
209
234
|
noPlaysRemaining: string;
|
|
@@ -304,4 +329,143 @@ interface SequenceMediaBudget {
|
|
|
304
329
|
strings?: Partial<MediaTransportStrings>;
|
|
305
330
|
}
|
|
306
331
|
|
|
307
|
-
|
|
332
|
+
/**
|
|
333
|
+
* Every string the SDK's own chrome renders.
|
|
334
|
+
*
|
|
335
|
+
* Until 0.8.1 these were literals, and `locale` set only the `lang` attribute —
|
|
336
|
+
* so a learner sitting a Spanish paper read a Spanish passage inside an English
|
|
337
|
+
* scaffold, and "No plays remaining" on a listening exam was untranslatable.
|
|
338
|
+
* This is the whole surface, in one place, so a consumer can replace it.
|
|
339
|
+
*
|
|
340
|
+
* **Interpolation and plurals are functions, not format strings.** A message
|
|
341
|
+
* like `"Question {n} of {total}"` needs a parser, and a parser needs plural
|
|
342
|
+
* rules for every locale the SDK does not know. A function hands both problems
|
|
343
|
+
* to the consumer, who has `Intl.PluralRules` and knows their own language:
|
|
344
|
+
*
|
|
345
|
+
* ```ts
|
|
346
|
+
* questionProgress: (index, total) => `Pregunta ${index} de ${total}`,
|
|
347
|
+
* wordCount: (n) => `${n} ${n === 1 ? 'palabra' : 'palabras'}`,
|
|
348
|
+
* ```
|
|
349
|
+
*
|
|
350
|
+
* It also lets a translation reorder its arguments, which a positional token
|
|
351
|
+
* cannot, and TypeScript checks the arity. No parser, no message catalogue
|
|
352
|
+
* format, no runtime dependency.
|
|
353
|
+
*
|
|
354
|
+
* **What is deliberately NOT here: thrown errors.** Every `throw` in this
|
|
355
|
+
* package is addressed to the developer who wired the component up, not to the
|
|
356
|
+
* learner. Translating them would make them unsearchable and would put the one
|
|
357
|
+
* audience who can act on them behind a translation. They stay English.
|
|
358
|
+
*/
|
|
359
|
+
interface LkStrings {
|
|
360
|
+
/** Multiple Choice submit button, and Written Response. */
|
|
361
|
+
submit: string;
|
|
362
|
+
/** Fill-in-the-Blanks in `practice`: the learner is checking their own work. */
|
|
363
|
+
checkAnswers: string;
|
|
364
|
+
/** Fill-in-the-Blanks in `exam`: nothing is checked on the client. */
|
|
365
|
+
submitAnswers: string;
|
|
366
|
+
/**
|
|
367
|
+
* The scored result, read by a screen reader on submit.
|
|
368
|
+
*
|
|
369
|
+
* `percent` is already rounded to a whole number. The authored overall
|
|
370
|
+
* feedback is appended SEPARATELY by the component, so a translation of this
|
|
371
|
+
* sentence never has to carry the author's words — the two were previously
|
|
372
|
+
* concatenated into one literal, which made the score sentence untranslatable
|
|
373
|
+
* without also touching authored content.
|
|
374
|
+
*/
|
|
375
|
+
scoreAnnouncement: (percent: number, passed: boolean) => string;
|
|
376
|
+
/** Prefix announced on submit, before the score sentence. */
|
|
377
|
+
answerSubmitted: string;
|
|
378
|
+
/** An answer submitted for grading that has none yet. Never rendered as 0%. */
|
|
379
|
+
notGradedYet: string;
|
|
380
|
+
/** An outcome that carries no grade and never will. */
|
|
381
|
+
noGradeAvailable: string;
|
|
382
|
+
showFeedback: string;
|
|
383
|
+
hideFeedback: string;
|
|
384
|
+
/** Accessible name of a blank's input. `ordinal` is 1-based. */
|
|
385
|
+
blankLabel: (ordinal: number) => string;
|
|
386
|
+
showHint: string;
|
|
387
|
+
hideHint: string;
|
|
388
|
+
/**
|
|
389
|
+
* Announced when an essay is handed in. Distinct from {@link LkStrings.awaitingGrade},
|
|
390
|
+
* which is the `review`-mode state of a submission made earlier: this one
|
|
391
|
+
* confirms the act, and is the only thing a learner hears at the moment they
|
|
392
|
+
* commit work that nothing on the client will grade.
|
|
393
|
+
*/
|
|
394
|
+
responseSubmitted: string;
|
|
395
|
+
/** Live word counter. Needs the count for languages whose plural rules differ. */
|
|
396
|
+
wordCount: (count: number) => string;
|
|
397
|
+
/** The authored bounds, e.g. "50–200 words". `min` is 0 when unbounded below. */
|
|
398
|
+
wordBounds: (min: number, max: number) => string;
|
|
399
|
+
/** A rubric criterion the grader marked not applicable. */
|
|
400
|
+
notApplicable: string;
|
|
401
|
+
/** A returned grade flagged for a human to look at — learner-visible by design. */
|
|
402
|
+
awaitingHumanReview: string;
|
|
403
|
+
/** Submitted, no grade back yet. */
|
|
404
|
+
awaitingGrade: string;
|
|
405
|
+
/** The grader could not produce a grade. */
|
|
406
|
+
couldNotBeGraded: string;
|
|
407
|
+
previous: string;
|
|
408
|
+
next: string;
|
|
409
|
+
/** Progress label, also the question region's accessible name. `index` is 1-based. */
|
|
410
|
+
questionProgress: (index: number, total: number) => string;
|
|
411
|
+
/** Shown when no renderer is registered for an activity type. */
|
|
412
|
+
unsupportedActivity: string;
|
|
413
|
+
/**
|
|
414
|
+
* `<ActivityPreview>`, in place of a draft that still has something missing.
|
|
415
|
+
* Author-facing, not learner-facing — but an editor is translated too.
|
|
416
|
+
*/
|
|
417
|
+
previewIncomplete: string;
|
|
418
|
+
/** `<ActivityPreview>`, in place of a draft with something wrong in it. */
|
|
419
|
+
previewInvalid: string;
|
|
420
|
+
/**
|
|
421
|
+
* Accessible name of a provider iframe whose author supplied no `alt`.
|
|
422
|
+
* `MediaSchema` requires `alt` on an embed, so validated content never
|
|
423
|
+
* reaches this — but `validateActivity` is opt-in, and a screen-reader
|
|
424
|
+
* learner meeting an unvalidated one should not meet it in English.
|
|
425
|
+
*/
|
|
426
|
+
embeddedMedia: string;
|
|
427
|
+
/**
|
|
428
|
+
* Accessible name of the stimulus region when the author gave it no title.
|
|
429
|
+
* Keyed by `Stimulus['kind']`, because "Recording" and "Passage" do not share
|
|
430
|
+
* a gender or an article in every language.
|
|
431
|
+
*/
|
|
432
|
+
stimulusKind: Record<Stimulus['kind'], string>;
|
|
433
|
+
/**
|
|
434
|
+
* How far the material carries, e.g. "Questions 3–8". Called with equal
|
|
435
|
+
* `first` and `last` for a single question, so a translation can choose its
|
|
436
|
+
* own singular rather than receive a pre-pluralised English one.
|
|
437
|
+
*/
|
|
438
|
+
stimulusRange: (first: number, last: number) => string;
|
|
439
|
+
/** Production fallback when an activity throws during render. */
|
|
440
|
+
activityFailed: string;
|
|
441
|
+
/** The same, when the activity's title is known. */
|
|
442
|
+
activityFailedNamed: (title: string) => string;
|
|
443
|
+
/** Production fallback when the activity has no title to name. */
|
|
444
|
+
activityFailedUnnamed: string;
|
|
445
|
+
/**
|
|
446
|
+
* The listening-paper transport. Shipped in 0.8.0 as its own `mediaStrings`
|
|
447
|
+
* prop, which still works — this is the same shape, reachable from the one
|
|
448
|
+
* provider so a consumer does not wire two mechanisms.
|
|
449
|
+
*/
|
|
450
|
+
media: MediaTransportStrings;
|
|
451
|
+
}
|
|
452
|
+
/** A partial override, nested one level for {@link LkStrings.media}. */
|
|
453
|
+
type LkStringsOverride = Partial<Omit<LkStrings, 'media' | 'stimulusKind'>> & {
|
|
454
|
+
media?: Partial<MediaTransportStrings>;
|
|
455
|
+
stimulusKind?: Partial<Record<Stimulus['kind'], string>>;
|
|
456
|
+
};
|
|
457
|
+
/**
|
|
458
|
+
* English defaults, byte-identical to what every component rendered before the
|
|
459
|
+
* strings surface existed. Supplying no override changes nothing on screen.
|
|
460
|
+
*/
|
|
461
|
+
declare const DEFAULT_STRINGS: LkStrings;
|
|
462
|
+
/**
|
|
463
|
+
* Layers an override onto a base dictionary.
|
|
464
|
+
*
|
|
465
|
+
* Two levels deep and no further, because the shape is two levels deep. A
|
|
466
|
+
* generic deep merge would silently accept a nested object where a function
|
|
467
|
+
* belongs and fail at render instead of at the call site.
|
|
468
|
+
*/
|
|
469
|
+
declare function mergeStrings(base: LkStrings, override?: LkStringsOverride): LkStrings;
|
|
470
|
+
|
|
471
|
+
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 };
|
package/dist/theme/skin.css
CHANGED
|
@@ -260,7 +260,7 @@
|
|
|
260
260
|
}
|
|
261
261
|
|
|
262
262
|
.lk-mc-option-feedback {
|
|
263
|
-
margin-
|
|
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-
|
|
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
|
-
|
|
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
|
-
|
|
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": "
|
|
3
|
+
"version": "8.0.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",
|
|
@@ -43,6 +43,16 @@
|
|
|
43
43
|
"default": "./dist/index.cjs"
|
|
44
44
|
}
|
|
45
45
|
},
|
|
46
|
+
"./components/ActivityPreview": {
|
|
47
|
+
"import": {
|
|
48
|
+
"types": "./dist/components/ActivityPreview.d.ts",
|
|
49
|
+
"default": "./dist/components/ActivityPreview.js"
|
|
50
|
+
},
|
|
51
|
+
"require": {
|
|
52
|
+
"types": "./dist/components/ActivityPreview.d.cts",
|
|
53
|
+
"default": "./dist/components/ActivityPreview.cjs"
|
|
54
|
+
}
|
|
55
|
+
},
|
|
46
56
|
"./components/ActivitySequence": {
|
|
47
57
|
"import": {
|
|
48
58
|
"types": "./dist/components/ActivitySequence.d.ts",
|
|
@@ -113,6 +123,16 @@
|
|
|
113
123
|
"default": "./dist/hooks/useXAPI.cjs"
|
|
114
124
|
}
|
|
115
125
|
},
|
|
126
|
+
"./i18n/LkIntlProvider": {
|
|
127
|
+
"import": {
|
|
128
|
+
"types": "./dist/i18n/LkIntlProvider.d.ts",
|
|
129
|
+
"default": "./dist/i18n/LkIntlProvider.js"
|
|
130
|
+
},
|
|
131
|
+
"require": {
|
|
132
|
+
"types": "./dist/i18n/LkIntlProvider.d.cts",
|
|
133
|
+
"default": "./dist/i18n/LkIntlProvider.cjs"
|
|
134
|
+
}
|
|
135
|
+
},
|
|
116
136
|
"./theme/ThemeProvider": {
|
|
117
137
|
"import": {
|
|
118
138
|
"types": "./dist/theme/ThemeProvider.d.ts",
|
|
@@ -137,7 +157,7 @@
|
|
|
137
157
|
"peerDependencies": {
|
|
138
158
|
"react": "^19",
|
|
139
159
|
"react-dom": "^19",
|
|
140
|
-
"@intellectif/lk-core": "^0.
|
|
160
|
+
"@intellectif/lk-core": "^0.9.0"
|
|
141
161
|
},
|
|
142
162
|
"devDependencies": {
|
|
143
163
|
"@axe-core/react": "^4.10.0",
|
|
@@ -156,7 +176,7 @@
|
|
|
156
176
|
"tsup": "^8.3.0",
|
|
157
177
|
"vitest": "^4.1.6",
|
|
158
178
|
"vitest-axe": "^0.1.0",
|
|
159
|
-
"@intellectif/lk-core": "0.
|
|
179
|
+
"@intellectif/lk-core": "0.9.0"
|
|
160
180
|
},
|
|
161
181
|
"license": "MIT",
|
|
162
182
|
"engines": {
|