@lessonkit/react 1.1.0 → 1.2.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/README.md +17 -4
- package/block-catalog.v3.json +1504 -0
- package/block-contract.v3.json +110 -0
- package/dist/index.cjs +2247 -700
- package/dist/index.d.cts +196 -28
- package/dist/index.d.ts +196 -28
- package/dist/index.js +2322 -775
- package/package.json +13 -9
package/dist/index.js
CHANGED
|
@@ -1,29 +1,6 @@
|
|
|
1
1
|
// src/components.tsx
|
|
2
|
-
import { useEffect as
|
|
3
|
-
import { visuallyHiddenStyle } from "@lessonkit/accessibility";
|
|
4
|
-
|
|
5
|
-
// src/assessment/scoring.ts
|
|
6
|
-
function resolvePassingThreshold(passingScore, maxScore) {
|
|
7
|
-
return passingScore ?? maxScore;
|
|
8
|
-
}
|
|
9
|
-
function meetsPassingThreshold(score, maxScore, passingScore) {
|
|
10
|
-
const threshold = resolvePassingThreshold(passingScore, maxScore);
|
|
11
|
-
return score >= threshold;
|
|
12
|
-
}
|
|
13
|
-
function scoreFromCustom(custom, fallbackCorrect, fallbackMax = 1, passingScore) {
|
|
14
|
-
const maxScore = custom?.maxScore ?? fallbackMax;
|
|
15
|
-
if (custom?.passed !== void 0) {
|
|
16
|
-
const score2 = custom.passed ? custom.score ?? maxScore : custom.score ?? 0;
|
|
17
|
-
return { score: score2, maxScore, passed: custom.passed };
|
|
18
|
-
}
|
|
19
|
-
if (custom?.maxScore != null && custom.maxScore > 0 && custom.score != null) {
|
|
20
|
-
const passed2 = meetsPassingThreshold(custom.score, custom.maxScore, passingScore);
|
|
21
|
-
return { score: custom.score, maxScore: custom.maxScore, passed: passed2 };
|
|
22
|
-
}
|
|
23
|
-
const score = fallbackCorrect ? maxScore : 0;
|
|
24
|
-
const passed = meetsPassingThreshold(score, maxScore, passingScore);
|
|
25
|
-
return { score, maxScore, passed };
|
|
26
|
-
}
|
|
2
|
+
import { useEffect as useEffect4, useId as useId2, useMemo as useMemo6, useRef as useRef4, useState as useState4 } from "react";
|
|
3
|
+
import { visuallyHiddenStyle as visuallyHiddenStyle2 } from "@lessonkit/accessibility";
|
|
27
4
|
|
|
28
5
|
// src/context.tsx
|
|
29
6
|
import { createContext } from "react";
|
|
@@ -38,7 +15,39 @@ import {
|
|
|
38
15
|
useState
|
|
39
16
|
} from "react";
|
|
40
17
|
import { createLessonkitRuntime, createTrackingClient as createTrackingClient2, assertValidId } from "@lessonkit/core";
|
|
18
|
+
|
|
19
|
+
// src/runtime/observability.ts
|
|
41
20
|
import { createInMemoryXAPIQueue } from "@lessonkit/xapi";
|
|
21
|
+
function createXapiQueueFromObservability(observability) {
|
|
22
|
+
const opts = {};
|
|
23
|
+
if (observability?.onXapiQueueDepth) {
|
|
24
|
+
opts.onDepth = observability.onXapiQueueDepth;
|
|
25
|
+
}
|
|
26
|
+
if (observability?.onXapiQueueCap) {
|
|
27
|
+
opts.onCap = observability.onXapiQueueCap;
|
|
28
|
+
}
|
|
29
|
+
return createInMemoryXAPIQueue(opts);
|
|
30
|
+
}
|
|
31
|
+
function wrapTrackingSink(sink, observability) {
|
|
32
|
+
if (!sink || !observability?.onTelemetrySinkError) return sink;
|
|
33
|
+
const onError = observability.onTelemetrySinkError;
|
|
34
|
+
return ((event) => {
|
|
35
|
+
try {
|
|
36
|
+
const result = sink(event);
|
|
37
|
+
if (result != null && typeof result.catch === "function") {
|
|
38
|
+
return result.catch((err) => {
|
|
39
|
+
onError(err, { sinkId: "tracking" });
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
return result;
|
|
43
|
+
} catch (err) {
|
|
44
|
+
onError(err, { sinkId: "tracking" });
|
|
45
|
+
return void 0;
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// src/provider/useLessonkitProviderRuntime.ts
|
|
42
51
|
import { telemetryEventToXAPIStatement as telemetryEventToXAPIStatement3 } from "@lessonkit/xapi";
|
|
43
52
|
|
|
44
53
|
// src/runtime/emitTelemetry.ts
|
|
@@ -59,7 +68,16 @@ import {
|
|
|
59
68
|
mapLessonkitTelemetryToBridgeAction,
|
|
60
69
|
telemetryEventToLessonkit
|
|
61
70
|
} from "@lessonkit/lxpack/bridge";
|
|
62
|
-
|
|
71
|
+
var BRIDGE_MISS_EVENT_NAMES = /* @__PURE__ */ new Set([
|
|
72
|
+
"course_completed",
|
|
73
|
+
"lesson_completed",
|
|
74
|
+
"assessment_completed",
|
|
75
|
+
"quiz_completed"
|
|
76
|
+
]);
|
|
77
|
+
function forwardTelemetryToLxpack(event, mode = "auto", opts) {
|
|
78
|
+
if (mode === "auto" && opts?.onBridgeMiss && BRIDGE_MISS_EVENT_NAMES.has(event.name) && !getLxpackBridge()) {
|
|
79
|
+
opts.onBridgeMiss(event);
|
|
80
|
+
}
|
|
63
81
|
forwardTelemetryToBridge(event, mode);
|
|
64
82
|
}
|
|
65
83
|
|
|
@@ -90,7 +108,9 @@ function createLegacyPipeline(opts, extraSinks = []) {
|
|
|
90
108
|
{
|
|
91
109
|
id: "lxpack-bridge",
|
|
92
110
|
emit(event) {
|
|
93
|
-
forwardTelemetryToLxpack(event, opts.lxpackBridge
|
|
111
|
+
forwardTelemetryToLxpack(event, opts.lxpackBridge, {
|
|
112
|
+
onBridgeMiss: opts.onLxpackBridgeMiss
|
|
113
|
+
});
|
|
94
114
|
}
|
|
95
115
|
},
|
|
96
116
|
...extraSinks
|
|
@@ -117,7 +137,8 @@ function emitTelemetry(tracking, xapi, event, opts) {
|
|
|
117
137
|
const legacy = {
|
|
118
138
|
tracking,
|
|
119
139
|
xapi,
|
|
120
|
-
lxpackBridge: opts?.lxpackBridge ?? "auto"
|
|
140
|
+
lxpackBridge: opts?.lxpackBridge ?? "auto",
|
|
141
|
+
onLxpackBridgeMiss: opts?.onLxpackBridgeMiss
|
|
121
142
|
};
|
|
122
143
|
emitThroughPipeline(event, legacy, opts?.extraSinks);
|
|
123
144
|
}
|
|
@@ -207,7 +228,9 @@ async function emitCourseStartedNonTrackingPipeline(opts) {
|
|
|
207
228
|
xapiStatementSent = true;
|
|
208
229
|
}
|
|
209
230
|
}
|
|
210
|
-
forwardTelemetryToLxpack(opts.event, opts.lxpackBridge
|
|
231
|
+
forwardTelemetryToLxpack(opts.event, opts.lxpackBridge, {
|
|
232
|
+
onBridgeMiss: opts.onLxpackBridgeMiss
|
|
233
|
+
});
|
|
211
234
|
const emitCtx = {
|
|
212
235
|
courseId: opts.event.courseId,
|
|
213
236
|
sessionId: opts.event.sessionId,
|
|
@@ -218,56 +241,25 @@ async function emitCourseStartedNonTrackingPipeline(opts) {
|
|
|
218
241
|
}
|
|
219
242
|
|
|
220
243
|
// src/runtime/plugins.ts
|
|
221
|
-
import { createPluginRegistry } from "@lessonkit/core";
|
|
244
|
+
import { buildPluginContext as buildPluginContextFromCore, createPluginRegistry } from "@lessonkit/core";
|
|
222
245
|
function createReactPluginHost(plugins) {
|
|
223
246
|
if (!plugins?.length) return null;
|
|
224
247
|
return createPluginRegistry(plugins);
|
|
225
248
|
}
|
|
226
249
|
function buildPluginContext(opts) {
|
|
227
|
-
return
|
|
228
|
-
courseId: opts.courseId,
|
|
229
|
-
sessionId: opts.sessionId,
|
|
230
|
-
attemptId: opts.attemptId,
|
|
231
|
-
user: opts.user
|
|
232
|
-
};
|
|
250
|
+
return buildPluginContextFromCore(opts);
|
|
233
251
|
}
|
|
234
252
|
function emitTelemetryWithPlugins(opts) {
|
|
235
253
|
const next = opts.pluginHost ? opts.pluginHost.runTelemetry(opts.event, opts.pluginCtx) : opts.event;
|
|
236
254
|
if (next === null) return;
|
|
237
255
|
emitTelemetry(opts.tracking, opts.xapi, next, {
|
|
238
256
|
lxpackBridge: opts.lxpackBridge ?? "auto",
|
|
239
|
-
extraSinks: opts.extraSinks
|
|
240
|
-
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// src/runtime/telemetry.ts
|
|
244
|
-
import { createTrackingClient } from "@lessonkit/core";
|
|
245
|
-
function createTrackingClientFromConfig(config) {
|
|
246
|
-
if (config.tracking?.enabled === false) return createTrackingClient();
|
|
247
|
-
if (config.tracking?.createClient) return config.tracking.createClient();
|
|
248
|
-
return createTrackingClient({
|
|
249
|
-
sink: config.tracking?.sink,
|
|
250
|
-
batchSink: config.tracking?.batchSink,
|
|
251
|
-
batch: config.tracking?.batch
|
|
257
|
+
extraSinks: opts.extraSinks,
|
|
258
|
+
onLxpackBridgeMiss: opts.onLxpackBridgeMiss
|
|
252
259
|
});
|
|
253
260
|
}
|
|
254
|
-
async function disposeTrackingClient(client) {
|
|
255
|
-
try {
|
|
256
|
-
await client?.flush?.();
|
|
257
|
-
} catch {
|
|
258
|
-
}
|
|
259
|
-
try {
|
|
260
|
-
await client?.dispose?.();
|
|
261
|
-
} catch {
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
261
|
|
|
265
|
-
// src/provider/
|
|
266
|
-
var useIsoLayoutEffect = (
|
|
267
|
-
/* v8 ignore next -- SSR uses useEffect when window is unavailable */
|
|
268
|
-
typeof window !== "undefined" ? useLayoutEffect : useEffect
|
|
269
|
-
);
|
|
270
|
-
var defaultStorage = createSessionStoragePort();
|
|
262
|
+
// src/provider/courseStarted/emit.ts
|
|
271
263
|
var courseStartedTrackingFlightKey = null;
|
|
272
264
|
function isTrackingActive(tracking) {
|
|
273
265
|
return tracking?.enabled !== false;
|
|
@@ -322,6 +314,7 @@ async function emitCourseStartedPipelineOnly(opts) {
|
|
|
322
314
|
event: opts.event,
|
|
323
315
|
xapi: opts.xapi,
|
|
324
316
|
lxpackBridge: opts.lxpackBridge,
|
|
317
|
+
onLxpackBridgeMiss: opts.onLxpackBridgeMiss,
|
|
325
318
|
extraSinks: opts.extraSinks,
|
|
326
319
|
skipXapi: opts.skipXapi
|
|
327
320
|
});
|
|
@@ -374,6 +367,7 @@ async function emitCourseStartedToTrackingOnly(opts) {
|
|
|
374
367
|
event,
|
|
375
368
|
xapi: null,
|
|
376
369
|
lxpackBridge: opts.lxpackBridge,
|
|
370
|
+
onLxpackBridgeMiss: opts.onLxpackBridgeMiss,
|
|
377
371
|
extraSinks: opts.extraSinks,
|
|
378
372
|
skipXapi: true
|
|
379
373
|
});
|
|
@@ -427,6 +421,35 @@ function assertTrackingSinkConfig(tracking) {
|
|
|
427
421
|
"[lessonkit] tracking.sink and tracking.batchSink cannot both be set; use batchSink alone for batched delivery"
|
|
428
422
|
);
|
|
429
423
|
}
|
|
424
|
+
|
|
425
|
+
// src/runtime/telemetry.ts
|
|
426
|
+
import { createTrackingClient } from "@lessonkit/core";
|
|
427
|
+
function createTrackingClientFromConfig(config) {
|
|
428
|
+
if (config.tracking?.enabled === false) return createTrackingClient();
|
|
429
|
+
if (config.tracking?.createClient) return config.tracking.createClient();
|
|
430
|
+
return createTrackingClient({
|
|
431
|
+
sink: config.tracking?.sink,
|
|
432
|
+
batchSink: config.tracking?.batchSink,
|
|
433
|
+
batch: config.tracking?.batch
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
async function disposeTrackingClient(client) {
|
|
437
|
+
try {
|
|
438
|
+
await client?.flush?.();
|
|
439
|
+
} catch {
|
|
440
|
+
}
|
|
441
|
+
try {
|
|
442
|
+
await client?.dispose?.();
|
|
443
|
+
} catch {
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// src/provider/useLessonkitProviderRuntime.ts
|
|
448
|
+
var useIsoLayoutEffect = (
|
|
449
|
+
/* v8 ignore next -- SSR uses useEffect when window is unavailable */
|
|
450
|
+
typeof window !== "undefined" ? useLayoutEffect : useEffect
|
|
451
|
+
);
|
|
452
|
+
var defaultStorage = createSessionStoragePort();
|
|
430
453
|
function useLessonkitProviderRuntime(config) {
|
|
431
454
|
const normalizedCourseId = useMemo(
|
|
432
455
|
() => assertValidId(config.courseId, "courseId"),
|
|
@@ -437,6 +460,14 @@ function useLessonkitProviderRuntime(config) {
|
|
|
437
460
|
[config, normalizedCourseId]
|
|
438
461
|
);
|
|
439
462
|
const useV2Runtime = normalizedConfig.runtimeVersion !== "v1";
|
|
463
|
+
useEffect(() => {
|
|
464
|
+
if (useV2Runtime) return;
|
|
465
|
+
const g = globalThis;
|
|
466
|
+
if (typeof g.process !== "undefined" && g.process.env?.NODE_ENV === "production") return;
|
|
467
|
+
console.warn(
|
|
468
|
+
'[lessonkit] LessonkitProvider runtimeVersion "v1" is deprecated; omit or use "v2" (default). v1 will be removed in LessonKit 2.0.'
|
|
469
|
+
);
|
|
470
|
+
}, [useV2Runtime]);
|
|
440
471
|
const extraSinksRef = useRef(normalizedConfig.sinks);
|
|
441
472
|
extraSinksRef.current = normalizedConfig.sinks;
|
|
442
473
|
const headlessRef = useRef(null);
|
|
@@ -455,7 +486,16 @@ function useLessonkitProviderRuntime(config) {
|
|
|
455
486
|
courseIdRef.current = normalizedCourseId;
|
|
456
487
|
const lxpackBridgeModeRef = useRef(normalizedConfig.lxpack?.bridge ?? "auto");
|
|
457
488
|
lxpackBridgeModeRef.current = normalizedConfig.lxpack?.bridge ?? "auto";
|
|
458
|
-
const
|
|
489
|
+
const observabilityRef = useRef(normalizedConfig.observability);
|
|
490
|
+
observabilityRef.current = normalizedConfig.observability;
|
|
491
|
+
const onLxpackBridgeMiss = useCallback((event) => {
|
|
492
|
+
observabilityRef.current?.onLxpackBridgeMiss?.(event);
|
|
493
|
+
}, []);
|
|
494
|
+
const pluginsFingerprint = normalizedConfig.plugins?.map((p) => `${p.id}\0${p.version}`).join("|") ?? "";
|
|
495
|
+
const pluginHost = useMemo(
|
|
496
|
+
() => createReactPluginHost(normalizedConfig.plugins),
|
|
497
|
+
[pluginsFingerprint]
|
|
498
|
+
);
|
|
459
499
|
const pluginHostRef = useRef(pluginHost);
|
|
460
500
|
pluginHostRef.current = pluginHost;
|
|
461
501
|
const progressRef = useRef(createProgressController());
|
|
@@ -471,7 +511,8 @@ function useLessonkitProviderRuntime(config) {
|
|
|
471
511
|
headlessRef.current = createLessonkitRuntime({
|
|
472
512
|
courseId: normalizedCourseId,
|
|
473
513
|
runtimeVersion: "v2",
|
|
474
|
-
session: normalizedConfig.session
|
|
514
|
+
session: normalizedConfig.session,
|
|
515
|
+
plugins: pluginHostRef.current ?? normalizedConfig.plugins
|
|
475
516
|
});
|
|
476
517
|
progressRef.current = headlessRef.current.progress;
|
|
477
518
|
} else {
|
|
@@ -485,7 +526,8 @@ function useLessonkitProviderRuntime(config) {
|
|
|
485
526
|
headlessRef.current = createLessonkitRuntime({
|
|
486
527
|
courseId: normalizedCourseId,
|
|
487
528
|
runtimeVersion: "v2",
|
|
488
|
-
session: normalizedConfig.session
|
|
529
|
+
session: normalizedConfig.session,
|
|
530
|
+
plugins: pluginHostRef.current ?? normalizedConfig.plugins
|
|
489
531
|
});
|
|
490
532
|
}
|
|
491
533
|
if (prevCourseIdForProgressRef.current !== normalizedCourseId) {
|
|
@@ -509,7 +551,7 @@ function useLessonkitProviderRuntime(config) {
|
|
|
509
551
|
}, []);
|
|
510
552
|
const activeLessonIdRef = useRef(progress.activeLessonId);
|
|
511
553
|
activeLessonIdRef.current = progress.activeLessonId;
|
|
512
|
-
const xapiQueueRef = useRef(
|
|
554
|
+
const xapiQueueRef = useRef(createXapiQueueFromObservability(normalizedConfig.observability));
|
|
513
555
|
const xapiRef = useRef(null);
|
|
514
556
|
const [xapi, setXapi] = useState(null);
|
|
515
557
|
const prevXapiCourseIdRef = useRef(normalizedCourseId);
|
|
@@ -530,7 +572,7 @@ function useLessonkitProviderRuntime(config) {
|
|
|
530
572
|
}
|
|
531
573
|
void xapiRef.current?.flush();
|
|
532
574
|
}
|
|
533
|
-
xapiQueueRef.current =
|
|
575
|
+
xapiQueueRef.current = createXapiQueueFromObservability(observabilityRef.current);
|
|
534
576
|
prevXapiCourseIdRef.current = courseId;
|
|
535
577
|
xapiCourseStartedSentOnClientRef.current = false;
|
|
536
578
|
}
|
|
@@ -609,7 +651,7 @@ function useLessonkitProviderRuntime(config) {
|
|
|
609
651
|
);
|
|
610
652
|
useIsoLayoutEffect(() => {
|
|
611
653
|
const prev = trackingRef.current;
|
|
612
|
-
const baseSink = normalizedConfig.tracking?.sink;
|
|
654
|
+
const baseSink = wrapTrackingSink(normalizedConfig.tracking?.sink, observabilityRef.current);
|
|
613
655
|
const userBatchSink = normalizedConfig.tracking?.batchSink;
|
|
614
656
|
assertTrackingSinkConfig(normalizedConfig.tracking);
|
|
615
657
|
const sink = pluginHostRef.current && baseSink ? (
|
|
@@ -659,6 +701,7 @@ function useLessonkitProviderRuntime(config) {
|
|
|
659
701
|
attemptId: attemptIdRef.current,
|
|
660
702
|
user: userRef.current,
|
|
661
703
|
lxpackBridge: lxpackBridgeModeRef.current,
|
|
704
|
+
onLxpackBridgeMiss,
|
|
662
705
|
extraSinks: extraSinksRef.current,
|
|
663
706
|
skipXapi: xapiCourseStartedSentOnClientRef.current,
|
|
664
707
|
onXapiStatementSent: () => {
|
|
@@ -700,9 +743,10 @@ function useLessonkitProviderRuntime(config) {
|
|
|
700
743
|
user: userRef.current
|
|
701
744
|
}),
|
|
702
745
|
lxpackBridge: lxpackBridgeModeRef.current,
|
|
746
|
+
onLxpackBridgeMiss,
|
|
703
747
|
extraSinks: extraSinksRef.current
|
|
704
748
|
});
|
|
705
|
-
}, []);
|
|
749
|
+
}, [onLxpackBridgeMiss]);
|
|
706
750
|
const emitLifecycleEvent = useCallback(
|
|
707
751
|
(name, data, lessonId) => {
|
|
708
752
|
const event = tryBuildTelemetryEvent({
|
|
@@ -758,12 +802,13 @@ function useLessonkitProviderRuntime(config) {
|
|
|
758
802
|
attemptId: attemptIdRef.current,
|
|
759
803
|
user: userRef.current,
|
|
760
804
|
lxpackBridge: lxpackBridgeModeRef.current,
|
|
805
|
+
onLxpackBridgeMiss,
|
|
761
806
|
extraSinks: extraSinksRef.current
|
|
762
807
|
});
|
|
763
808
|
courseStartedEmittedToSinkRef.current = isCourseStartedSinkSettled(result);
|
|
764
809
|
}
|
|
765
810
|
})();
|
|
766
|
-
}, [normalizedCourseId, normalizedConfig.tracking?.enabled, syncProgress]);
|
|
811
|
+
}, [normalizedCourseId, normalizedConfig.tracking?.enabled, syncProgress, onLxpackBridgeMiss]);
|
|
767
812
|
const emitLessonCompleted = useCallback(
|
|
768
813
|
(lessonId, durationMs) => {
|
|
769
814
|
track("lesson_completed", { lessonId, durationMs }, { lessonId });
|
|
@@ -812,6 +857,22 @@ function useLessonkitProviderRuntime(config) {
|
|
|
812
857
|
})();
|
|
813
858
|
};
|
|
814
859
|
}, []);
|
|
860
|
+
useEffect(() => {
|
|
861
|
+
if (typeof document === "undefined") return;
|
|
862
|
+
const flushOnExit = () => {
|
|
863
|
+
void xapiRef.current?.flush();
|
|
864
|
+
void trackingRef.current?.flush?.();
|
|
865
|
+
};
|
|
866
|
+
const onVisibilityChange = () => {
|
|
867
|
+
if (document.visibilityState === "hidden") flushOnExit();
|
|
868
|
+
};
|
|
869
|
+
document.addEventListener("visibilitychange", onVisibilityChange);
|
|
870
|
+
window.addEventListener("pagehide", flushOnExit);
|
|
871
|
+
return () => {
|
|
872
|
+
document.removeEventListener("visibilitychange", onVisibilityChange);
|
|
873
|
+
window.removeEventListener("pagehide", flushOnExit);
|
|
874
|
+
};
|
|
875
|
+
}, []);
|
|
815
876
|
const setActiveLesson = useCallback(
|
|
816
877
|
(lessonId) => {
|
|
817
878
|
if (useV2Runtime && headlessRef.current) {
|
|
@@ -875,20 +936,34 @@ function useLessonkitProviderRuntime(config) {
|
|
|
875
936
|
session: normalizedConfig.session
|
|
876
937
|
});
|
|
877
938
|
}
|
|
878
|
-
}, [
|
|
939
|
+
}, [
|
|
940
|
+
useV2Runtime,
|
|
941
|
+
normalizedCourseId,
|
|
942
|
+
sessionAttemptId,
|
|
943
|
+
sessionConfiguredId,
|
|
944
|
+
sessionUserKey,
|
|
945
|
+
normalizedConfig.session
|
|
946
|
+
]);
|
|
947
|
+
useEffect(() => {
|
|
948
|
+
if (!useV2Runtime || !headlessRef.current) return;
|
|
949
|
+
headlessRef.current.updateConfig({
|
|
950
|
+
plugins: pluginHostRef.current ?? normalizedConfig.plugins
|
|
951
|
+
});
|
|
952
|
+
}, [useV2Runtime, pluginHost]);
|
|
879
953
|
useEffect(() => {
|
|
880
|
-
|
|
954
|
+
const host = useV2Runtime ? headlessRef.current?.pluginHost ?? null : pluginHost;
|
|
955
|
+
if (!host) return;
|
|
881
956
|
const ctx = buildPluginContext({
|
|
882
957
|
courseId: courseIdRef.current,
|
|
883
958
|
sessionId: sessionIdRef.current,
|
|
884
959
|
attemptId: attemptIdRef.current,
|
|
885
960
|
user: userRef.current
|
|
886
961
|
});
|
|
887
|
-
|
|
962
|
+
host.setupAll(ctx);
|
|
888
963
|
return () => {
|
|
889
|
-
|
|
964
|
+
host.disposeAll();
|
|
890
965
|
};
|
|
891
|
-
}, [pluginHost, normalizedCourseId, sessionAttemptId, sessionConfiguredId, sessionUserKey]);
|
|
966
|
+
}, [pluginHost, useV2Runtime, normalizedCourseId, sessionAttemptId, sessionConfiguredId, sessionUserKey]);
|
|
892
967
|
useEffect(() => {
|
|
893
968
|
const nextConfigured = normalizedConfig.session?.sessionId;
|
|
894
969
|
const prevConfigured = prevConfiguredSessionIdRef.current;
|
|
@@ -1051,465 +1126,669 @@ function getLessonMountCount(lessonId) {
|
|
|
1051
1126
|
return mountCounts.get(lessonId) ?? 0;
|
|
1052
1127
|
}
|
|
1053
1128
|
|
|
1054
|
-
// src/components.tsx
|
|
1055
|
-
import {
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
}
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
[props.config, courseId]
|
|
1065
|
-
);
|
|
1066
|
-
return /* @__PURE__ */ jsx2(LessonkitProvider, { config: providerConfig, children: /* @__PURE__ */ jsxs("section", { "aria-label": props.title, children: [
|
|
1067
|
-
/* @__PURE__ */ jsx2("h1", { children: props.title }),
|
|
1068
|
-
/* @__PURE__ */ jsx2("div", { children: props.children })
|
|
1069
|
-
] }) });
|
|
1070
|
-
}
|
|
1071
|
-
function Lesson(props) {
|
|
1072
|
-
const lessonId = useMemo4(() => normalizeComponentId(props.lessonId, "lessonId"), [props.lessonId]);
|
|
1073
|
-
const autoComplete = props.autoCompleteOnUnmount !== false;
|
|
1074
|
-
const { setActiveLesson, config } = useLessonkit();
|
|
1075
|
-
const { completeLesson } = useCompletion();
|
|
1076
|
-
const lessonMountGenerationRef = useRef2(0);
|
|
1077
|
-
const liveCourseIdRef = useRef2(config.courseId);
|
|
1078
|
-
liveCourseIdRef.current = config.courseId;
|
|
1079
|
-
useEffect2(() => {
|
|
1080
|
-
const unregister = registerLessonMount(lessonId);
|
|
1081
|
-
const generation = ++lessonMountGenerationRef.current;
|
|
1082
|
-
const mountedCourseId = config.courseId;
|
|
1083
|
-
let effectSurvivedTick = false;
|
|
1084
|
-
queueMicrotask(() => {
|
|
1085
|
-
queueMicrotask(() => {
|
|
1086
|
-
effectSurvivedTick = true;
|
|
1087
|
-
});
|
|
1088
|
-
});
|
|
1089
|
-
setActiveLesson(lessonId);
|
|
1090
|
-
return () => {
|
|
1091
|
-
unregister();
|
|
1092
|
-
if (getLessonMountCount(lessonId) > 0) {
|
|
1093
|
-
return;
|
|
1094
|
-
}
|
|
1095
|
-
if (!autoComplete) return;
|
|
1096
|
-
queueMicrotask(() => {
|
|
1097
|
-
if (!effectSurvivedTick) return;
|
|
1098
|
-
if (lessonMountGenerationRef.current !== generation) return;
|
|
1099
|
-
if (liveCourseIdRef.current !== mountedCourseId) return;
|
|
1100
|
-
completeLesson(lessonId, { courseId: mountedCourseId });
|
|
1101
|
-
});
|
|
1102
|
-
};
|
|
1103
|
-
}, [lessonId, config.courseId, setActiveLesson, completeLesson, autoComplete]);
|
|
1104
|
-
return /* @__PURE__ */ jsx2(LessonContext.Provider, { value: lessonId, children: /* @__PURE__ */ jsxs("article", { "aria-label": props.title, children: [
|
|
1105
|
-
/* @__PURE__ */ jsx2("h2", { children: props.title }),
|
|
1106
|
-
/* @__PURE__ */ jsx2("div", { children: props.children })
|
|
1107
|
-
] }) });
|
|
1108
|
-
}
|
|
1109
|
-
function Scenario(props) {
|
|
1110
|
-
const blockId = useMemo4(
|
|
1111
|
-
() => props.blockId !== void 0 ? normalizeComponentId(props.blockId, "blockId") : void 0,
|
|
1112
|
-
[props.blockId]
|
|
1113
|
-
);
|
|
1114
|
-
return /* @__PURE__ */ jsx2("section", { "aria-label": "Scenario", "data-lk-block-id": blockId, children: props.children });
|
|
1115
|
-
}
|
|
1116
|
-
function Reflection(props) {
|
|
1117
|
-
const blockId = useMemo4(
|
|
1118
|
-
() => props.blockId !== void 0 ? normalizeComponentId(props.blockId, "blockId") : void 0,
|
|
1119
|
-
[props.blockId]
|
|
1120
|
-
);
|
|
1121
|
-
const promptId = useId();
|
|
1122
|
-
const hintId = useId();
|
|
1123
|
-
const [internalValue, setInternalValue] = useState2("");
|
|
1124
|
-
const isControlled = props.value !== void 0;
|
|
1125
|
-
const value = isControlled ? props.value : internalValue;
|
|
1126
|
-
const handleChange = (event) => {
|
|
1127
|
-
if (!isControlled) setInternalValue(event.target.value);
|
|
1128
|
-
props.onChange?.(event.target.value);
|
|
1129
|
-
};
|
|
1130
|
-
return /* @__PURE__ */ jsxs("section", { "aria-label": "Reflection", "data-lk-block-id": blockId, children: [
|
|
1131
|
-
props.prompt ? /* @__PURE__ */ jsx2("p", { id: promptId, children: props.prompt }) : null,
|
|
1132
|
-
props.hint ? /* @__PURE__ */ jsx2("p", { id: hintId, style: visuallyHiddenStyle, children: props.hint }) : null,
|
|
1133
|
-
props.children,
|
|
1134
|
-
/* @__PURE__ */ jsx2(
|
|
1135
|
-
"textarea",
|
|
1136
|
-
{
|
|
1137
|
-
value,
|
|
1138
|
-
onChange: handleChange,
|
|
1139
|
-
"aria-labelledby": props.prompt ? promptId : void 0,
|
|
1140
|
-
"aria-describedby": props.hint ? hintId : void 0,
|
|
1141
|
-
"aria-label": props.prompt ? void 0 : "Reflection response"
|
|
1142
|
-
}
|
|
1143
|
-
)
|
|
1144
|
-
] });
|
|
1145
|
-
}
|
|
1146
|
-
function KnowledgeCheck(props) {
|
|
1147
|
-
return /* @__PURE__ */ jsx2(
|
|
1148
|
-
Quiz,
|
|
1149
|
-
{
|
|
1150
|
-
checkId: props.checkId,
|
|
1151
|
-
question: props.question,
|
|
1152
|
-
choices: props.choices,
|
|
1153
|
-
answer: props.answer,
|
|
1154
|
-
passingScore: props.passingScore
|
|
1155
|
-
}
|
|
1156
|
-
);
|
|
1129
|
+
// src/components/Quiz.tsx
|
|
1130
|
+
import { forwardRef, useEffect as useEffect3, useId, useMemo as useMemo5, useRef as useRef3, useState as useState3 } from "react";
|
|
1131
|
+
import { visuallyHiddenStyle } from "@lessonkit/accessibility";
|
|
1132
|
+
|
|
1133
|
+
// src/assessment/AssessmentLessonGuard.tsx
|
|
1134
|
+
import { useEffect as useEffect2 } from "react";
|
|
1135
|
+
import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
1136
|
+
var warnedAssessmentOutsideLesson = false;
|
|
1137
|
+
function resetAssessmentWarningsForTests() {
|
|
1138
|
+
warnedAssessmentOutsideLesson = false;
|
|
1157
1139
|
}
|
|
1158
|
-
function
|
|
1140
|
+
function AssessmentLessonGuard(props) {
|
|
1159
1141
|
const enclosingLessonId = useEnclosingLessonId();
|
|
1160
1142
|
const missingLesson = enclosingLessonId === void 0;
|
|
1161
1143
|
useEffect2(() => {
|
|
1162
1144
|
if (!missingLesson || isDevEnvironment4()) return;
|
|
1163
|
-
if (!
|
|
1164
|
-
|
|
1145
|
+
if (!warnedAssessmentOutsideLesson) {
|
|
1146
|
+
warnedAssessmentOutsideLesson = true;
|
|
1165
1147
|
console.error(
|
|
1166
|
-
|
|
1148
|
+
`[lessonkit] <${props.blockLabel}> must be wrapped in <Lesson>; assessment telemetry will not be emitted.`
|
|
1167
1149
|
);
|
|
1168
1150
|
}
|
|
1169
|
-
}, [missingLesson]);
|
|
1151
|
+
}, [missingLesson, props.blockLabel]);
|
|
1170
1152
|
if (missingLesson && isDevEnvironment4()) {
|
|
1171
|
-
throw new Error(
|
|
1153
|
+
throw new Error(`[lessonkit] <${props.blockLabel}> must be wrapped in <Lesson>`);
|
|
1172
1154
|
}
|
|
1173
1155
|
if (missingLesson) {
|
|
1174
|
-
return /* @__PURE__ */ jsx2("section", { role: "alert", "aria-label":
|
|
1156
|
+
return /* @__PURE__ */ jsx2("section", { role: "alert", "aria-label": `${props.blockLabel} configuration error`, "data-lk-check-id": props.checkId, children: /* @__PURE__ */ jsxs("p", { children: [
|
|
1157
|
+
props.blockLabel,
|
|
1158
|
+
" must be placed inside a Lesson."
|
|
1159
|
+
] }) });
|
|
1175
1160
|
}
|
|
1176
|
-
return /* @__PURE__ */ jsx2(
|
|
1161
|
+
return /* @__PURE__ */ jsx2(Fragment, { children: props.children(enclosingLessonId) });
|
|
1177
1162
|
}
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
completedRef.current = false;
|
|
1191
|
-
setQuizPassed(false);
|
|
1192
|
-
setSelected(null);
|
|
1193
|
-
setSelectionCorrect(null);
|
|
1194
|
-
}, [checkId, props.answer, props.question, config.courseId, enclosingLessonId, choicesKey]);
|
|
1195
|
-
const isChoiceCorrect = (choice, custom) => {
|
|
1196
|
-
if (!custom) return choice === props.answer;
|
|
1197
|
-
if (custom.passed !== void 0) return custom.passed;
|
|
1198
|
-
if (custom.maxScore != null && custom.maxScore > 0 && custom.score != null) {
|
|
1199
|
-
return meetsPassingThreshold(custom.score, custom.maxScore, props.passingScore);
|
|
1200
|
-
}
|
|
1201
|
-
return choice === props.answer;
|
|
1163
|
+
|
|
1164
|
+
// src/assessment/internal/buildAssessmentHandle.ts
|
|
1165
|
+
function buildAssessmentHandle(opts) {
|
|
1166
|
+
return {
|
|
1167
|
+
getScore: opts.getScore,
|
|
1168
|
+
getMaxScore: opts.getMaxScore,
|
|
1169
|
+
getAnswerGiven: opts.getAnswerGiven,
|
|
1170
|
+
resetTask: opts.resetTask,
|
|
1171
|
+
showSolutions: opts.showSolutions,
|
|
1172
|
+
getXAPIData: opts.getXAPIData,
|
|
1173
|
+
...opts.getCurrentState ? { getCurrentState: opts.getCurrentState } : {},
|
|
1174
|
+
...opts.resume ? { resume: opts.resume } : {}
|
|
1202
1175
|
};
|
|
1203
|
-
const passed = quizPassed;
|
|
1204
|
-
return /* @__PURE__ */ jsxs("section", { "aria-label": "Quiz", "data-lk-check-id": checkId, children: [
|
|
1205
|
-
/* @__PURE__ */ jsx2("p", { id: questionId, children: props.question }),
|
|
1206
|
-
/* @__PURE__ */ jsxs("fieldset", { "aria-labelledby": questionId, children: [
|
|
1207
|
-
/* @__PURE__ */ jsx2("legend", { style: visuallyHiddenStyle, children: "Quiz choices" }),
|
|
1208
|
-
props.choices.map((c, i) => /* @__PURE__ */ jsxs("label", { style: { display: "block" }, children: [
|
|
1209
|
-
/* @__PURE__ */ jsx2(
|
|
1210
|
-
"input",
|
|
1211
|
-
{
|
|
1212
|
-
type: "radio",
|
|
1213
|
-
name: questionId,
|
|
1214
|
-
value: c,
|
|
1215
|
-
checked: selected === c,
|
|
1216
|
-
disabled: passed,
|
|
1217
|
-
"aria-invalid": selected === c && selectionCorrect === false ? true : void 0,
|
|
1218
|
-
onChange: () => {
|
|
1219
|
-
if (passed) return;
|
|
1220
|
-
setSelected(c);
|
|
1221
|
-
const pluginCtx = buildPluginContext({
|
|
1222
|
-
courseId: config.courseId,
|
|
1223
|
-
sessionId: session.sessionId,
|
|
1224
|
-
attemptId: session.attemptId,
|
|
1225
|
-
user: session.user
|
|
1226
|
-
});
|
|
1227
|
-
const custom = plugins?.scoreAssessment(
|
|
1228
|
-
{
|
|
1229
|
-
checkId,
|
|
1230
|
-
lessonId: enclosingLessonId,
|
|
1231
|
-
response: c
|
|
1232
|
-
},
|
|
1233
|
-
pluginCtx
|
|
1234
|
-
) ?? null;
|
|
1235
|
-
const correct = isChoiceCorrect(c, custom);
|
|
1236
|
-
setSelectionCorrect(correct);
|
|
1237
|
-
quiz.answer({
|
|
1238
|
-
checkId,
|
|
1239
|
-
question: props.question,
|
|
1240
|
-
choice: c,
|
|
1241
|
-
correct
|
|
1242
|
-
});
|
|
1243
|
-
if (correct && !completedRef.current) {
|
|
1244
|
-
completedRef.current = true;
|
|
1245
|
-
setQuizPassed(true);
|
|
1246
|
-
const maxScore = custom?.maxScore ?? 1;
|
|
1247
|
-
quiz.complete({
|
|
1248
|
-
checkId,
|
|
1249
|
-
score: custom?.score ?? maxScore,
|
|
1250
|
-
maxScore,
|
|
1251
|
-
passingScore: props.passingScore ?? maxScore
|
|
1252
|
-
});
|
|
1253
|
-
}
|
|
1254
|
-
}
|
|
1255
|
-
}
|
|
1256
|
-
),
|
|
1257
|
-
c
|
|
1258
|
-
] }, `${questionId}-${i}`))
|
|
1259
|
-
] }),
|
|
1260
|
-
selected && selectionCorrect !== null ? /* @__PURE__ */ jsx2("p", { role: "status", "aria-live": "polite", children: selectionCorrect ? "Correct" : "Try again" }) : null
|
|
1261
|
-
] });
|
|
1262
1176
|
}
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
"Lessons completed: ",
|
|
1279
|
-
displayed,
|
|
1280
|
-
" of ",
|
|
1281
|
-
total
|
|
1282
|
-
] })
|
|
1283
|
-
}
|
|
1284
|
-
) });
|
|
1285
|
-
}
|
|
1286
|
-
return /* @__PURE__ */ jsx2("aside", { "aria-label": "Progress", role: "status", children: /* @__PURE__ */ jsxs("p", { children: [
|
|
1287
|
-
"Lessons completed: ",
|
|
1288
|
-
completed
|
|
1289
|
-
] }) });
|
|
1177
|
+
|
|
1178
|
+
// src/assessment/internal/resumeState.ts
|
|
1179
|
+
function readBooleanField(state, key) {
|
|
1180
|
+
const value = state[key];
|
|
1181
|
+
if (value === true || value === false || value === null) return value;
|
|
1182
|
+
return void 0;
|
|
1183
|
+
}
|
|
1184
|
+
function readStringField(state, key) {
|
|
1185
|
+
const value = state[key];
|
|
1186
|
+
if (typeof value === "string" || value === null) return value;
|
|
1187
|
+
return void 0;
|
|
1188
|
+
}
|
|
1189
|
+
function readBooleanStateField(state, key, apply) {
|
|
1190
|
+
const value = state[key];
|
|
1191
|
+
if (typeof value === "boolean") apply(value);
|
|
1290
1192
|
}
|
|
1291
1193
|
|
|
1292
|
-
// src/
|
|
1293
|
-
import
|
|
1194
|
+
// src/assessment/internal/useAssessmentHandleRegistration.ts
|
|
1195
|
+
import { useImperativeHandle as useImperativeHandle2 } from "react";
|
|
1294
1196
|
|
|
1295
|
-
// src/
|
|
1296
|
-
import {
|
|
1297
|
-
import {
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1197
|
+
// src/compound/CompoundProvider.tsx
|
|
1198
|
+
import React3, { createContext as createContext3, useCallback as useCallback2, useContext as useContext3, useImperativeHandle, useMemo as useMemo4, useRef as useRef2, useState as useState2 } from "react";
|
|
1199
|
+
import { clampCompoundPageIndex, createCompoundResumeState } from "@lessonkit/core";
|
|
1200
|
+
|
|
1201
|
+
// src/compound/aggregateScores.ts
|
|
1202
|
+
function aggregateAssessmentScores(handles) {
|
|
1203
|
+
let score = 0;
|
|
1204
|
+
let maxScore = 0;
|
|
1205
|
+
let allAnswered = true;
|
|
1206
|
+
for (const handle of handles) {
|
|
1207
|
+
score += handle.getScore();
|
|
1208
|
+
maxScore += handle.getMaxScore();
|
|
1209
|
+
if (!handle.getAnswerGiven()) allAnswered = false;
|
|
1210
|
+
}
|
|
1211
|
+
return { score, maxScore, allAnswered };
|
|
1301
1212
|
}
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
if (!warnedAssessmentOutsideLesson) {
|
|
1308
|
-
warnedAssessmentOutsideLesson = true;
|
|
1309
|
-
console.error(
|
|
1310
|
-
`[lessonkit] <${props.blockLabel}> must be wrapped in <Lesson>; assessment telemetry will not be emitted.`
|
|
1311
|
-
);
|
|
1312
|
-
}
|
|
1313
|
-
}, [missingLesson, props.blockLabel]);
|
|
1314
|
-
if (missingLesson && isDevEnvironment4()) {
|
|
1315
|
-
throw new Error(`[lessonkit] <${props.blockLabel}> must be wrapped in <Lesson>`);
|
|
1213
|
+
|
|
1214
|
+
// src/compound/resumeChildHandles.ts
|
|
1215
|
+
function resumeChildHandles(handles, childStates, opts) {
|
|
1216
|
+
if (opts?.waitForHandles && handles.size === 0 && Object.keys(childStates).length > 0) {
|
|
1217
|
+
return false;
|
|
1316
1218
|
}
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
" must be placed inside a Lesson."
|
|
1321
|
-
] }) });
|
|
1219
|
+
for (const [checkId, handle] of handles) {
|
|
1220
|
+
const child = childStates[checkId];
|
|
1221
|
+
if (child && handle.resume) handle.resume(child);
|
|
1322
1222
|
}
|
|
1323
|
-
return
|
|
1223
|
+
return true;
|
|
1324
1224
|
}
|
|
1325
1225
|
|
|
1326
|
-
// src/
|
|
1327
|
-
import
|
|
1328
|
-
|
|
1329
|
-
var
|
|
1330
|
-
function
|
|
1331
|
-
|
|
1226
|
+
// src/compound/CompoundProvider.tsx
|
|
1227
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
1228
|
+
var CompoundRegistryContext = createContext3(null);
|
|
1229
|
+
var CompoundHandlesVersionContext = createContext3(0);
|
|
1230
|
+
function CompoundProvider({
|
|
1231
|
+
children,
|
|
1232
|
+
activePageIndex: _activePageIndex,
|
|
1233
|
+
onActivePageIndexChange: _onActivePageIndexChange
|
|
1234
|
+
}) {
|
|
1235
|
+
const registryRef = useRef2(/* @__PURE__ */ new Map());
|
|
1236
|
+
const [handlesVersion, setHandlesVersion] = useState2(0);
|
|
1332
1237
|
const register = useCallback2((checkId, handle) => {
|
|
1238
|
+
const prev = registryRef.current.get(checkId);
|
|
1333
1239
|
registryRef.current.set(checkId, handle);
|
|
1240
|
+
if (prev !== handle) {
|
|
1241
|
+
setHandlesVersion((v) => v + 1);
|
|
1242
|
+
}
|
|
1334
1243
|
return () => {
|
|
1335
|
-
registryRef.current.
|
|
1244
|
+
if (registryRef.current.get(checkId) === handle) {
|
|
1245
|
+
registryRef.current.delete(checkId);
|
|
1246
|
+
setHandlesVersion((v) => v + 1);
|
|
1247
|
+
}
|
|
1336
1248
|
};
|
|
1337
1249
|
}, []);
|
|
1338
|
-
const
|
|
1250
|
+
const registryValue = useMemo4(
|
|
1339
1251
|
() => ({
|
|
1340
1252
|
register,
|
|
1341
1253
|
getHandles: () => registryRef.current
|
|
1342
1254
|
}),
|
|
1343
1255
|
[register]
|
|
1344
1256
|
);
|
|
1345
|
-
return /* @__PURE__ */
|
|
1257
|
+
return /* @__PURE__ */ jsx3(CompoundRegistryContext.Provider, { value: registryValue, children: /* @__PURE__ */ jsx3(CompoundHandlesVersionContext.Provider, { value: handlesVersion, children }) });
|
|
1346
1258
|
}
|
|
1347
|
-
function
|
|
1348
|
-
|
|
1259
|
+
function useCompoundRegistry() {
|
|
1260
|
+
const registry = useContext3(CompoundRegistryContext);
|
|
1261
|
+
const handlesVersion = useContext3(CompoundHandlesVersionContext);
|
|
1262
|
+
if (!registry) return null;
|
|
1263
|
+
return { ...registry, handlesVersion };
|
|
1264
|
+
}
|
|
1265
|
+
function useCompoundHandlesVersion() {
|
|
1266
|
+
return useContext3(CompoundHandlesVersionContext);
|
|
1349
1267
|
}
|
|
1350
1268
|
function useRegisterAssessmentHandle(checkId, handle) {
|
|
1351
|
-
const
|
|
1352
|
-
|
|
1353
|
-
if (!
|
|
1354
|
-
return
|
|
1355
|
-
}, [
|
|
1269
|
+
const registry = useContext3(CompoundRegistryContext);
|
|
1270
|
+
React3.useEffect(() => {
|
|
1271
|
+
if (!registry || !handle) return;
|
|
1272
|
+
return registry.register(checkId, handle);
|
|
1273
|
+
}, [registry, checkId, handle]);
|
|
1274
|
+
}
|
|
1275
|
+
function useCompoundHandleRef(ref, opts) {
|
|
1276
|
+
const { activePageIndex, setActivePageIndex, getHandles, pageCount } = opts;
|
|
1277
|
+
const setIndexClamped = useCallback2(
|
|
1278
|
+
(index) => {
|
|
1279
|
+
const next = pageCount !== void 0 ? clampCompoundPageIndex(index, pageCount) : Math.max(0, Math.floor(index));
|
|
1280
|
+
setActivePageIndex(next);
|
|
1281
|
+
},
|
|
1282
|
+
[pageCount, setActivePageIndex]
|
|
1283
|
+
);
|
|
1284
|
+
useImperativeHandle(
|
|
1285
|
+
ref,
|
|
1286
|
+
() => ({
|
|
1287
|
+
getScore: () => aggregateAssessmentScores(getHandles().values()).score,
|
|
1288
|
+
getMaxScore: () => aggregateAssessmentScores(getHandles().values()).maxScore,
|
|
1289
|
+
getAnswerGiven: () => aggregateAssessmentScores(getHandles().values()).allAnswered,
|
|
1290
|
+
resetTask: () => {
|
|
1291
|
+
for (const handle of getHandles().values()) handle.resetTask();
|
|
1292
|
+
},
|
|
1293
|
+
showSolutions: () => {
|
|
1294
|
+
if (!opts.enableSolutionsButton) return;
|
|
1295
|
+
for (const handle of getHandles().values()) handle.showSolutions();
|
|
1296
|
+
},
|
|
1297
|
+
getCurrentState: () => {
|
|
1298
|
+
const childStates = {};
|
|
1299
|
+
for (const [checkId, handle] of getHandles()) {
|
|
1300
|
+
if (handle.getCurrentState) {
|
|
1301
|
+
childStates[checkId] = handle.getCurrentState();
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
return createCompoundResumeState({ activePageIndex, childStates });
|
|
1305
|
+
},
|
|
1306
|
+
resume: (state) => {
|
|
1307
|
+
setIndexClamped(state.activePageIndex);
|
|
1308
|
+
resumeChildHandles(getHandles(), state.childStates);
|
|
1309
|
+
}
|
|
1310
|
+
}),
|
|
1311
|
+
[activePageIndex, setIndexClamped, getHandles, opts.enableSolutionsButton]
|
|
1312
|
+
);
|
|
1356
1313
|
}
|
|
1357
1314
|
|
|
1358
|
-
// src/
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1315
|
+
// src/assessment/internal/useAssessmentHandleRegistration.ts
|
|
1316
|
+
function useAssessmentHandleRegistration(checkId, handle, ref) {
|
|
1317
|
+
useImperativeHandle2(ref, () => handle, [handle]);
|
|
1318
|
+
useRegisterAssessmentHandle(checkId, handle);
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
// src/assessment/internal/usePluginScoring.ts
|
|
1322
|
+
import { useCallback as useCallback3 } from "react";
|
|
1323
|
+
|
|
1324
|
+
// src/assessment/scoring.ts
|
|
1325
|
+
function resolvePassingThreshold(passingScore, maxScore) {
|
|
1326
|
+
return passingScore ?? maxScore;
|
|
1327
|
+
}
|
|
1328
|
+
function meetsPassingThreshold(score, maxScore, passingScore) {
|
|
1329
|
+
const threshold = resolvePassingThreshold(passingScore, maxScore);
|
|
1330
|
+
return score >= threshold;
|
|
1331
|
+
}
|
|
1332
|
+
function scoreFromCustom(custom, fallbackCorrect, fallbackMax = 1, passingScore) {
|
|
1333
|
+
const maxScore = custom?.maxScore ?? fallbackMax;
|
|
1334
|
+
if (custom?.passed !== void 0) {
|
|
1335
|
+
const score2 = custom.passed ? custom.score ?? maxScore : custom.score ?? 0;
|
|
1336
|
+
return { score: score2, maxScore, passed: custom.passed };
|
|
1337
|
+
}
|
|
1338
|
+
if (custom?.maxScore != null && custom.maxScore > 0 && custom.score != null) {
|
|
1339
|
+
const passed2 = meetsPassingThreshold(custom.score, custom.maxScore, passingScore);
|
|
1340
|
+
return { score: custom.score, maxScore: custom.maxScore, passed: passed2 };
|
|
1341
|
+
}
|
|
1342
|
+
const score = fallbackCorrect ? maxScore : 0;
|
|
1343
|
+
const passed = meetsPassingThreshold(score, maxScore, passingScore);
|
|
1344
|
+
return { score, maxScore, passed };
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
// src/assessment/internal/usePluginScoring.ts
|
|
1348
|
+
function usePluginScoring(checkId, lessonId) {
|
|
1365
1349
|
const { plugins, config, session } = useLessonkit();
|
|
1350
|
+
const getPluginScore = useCallback3(
|
|
1351
|
+
(response) => {
|
|
1352
|
+
const pluginCtx = buildPluginContext({
|
|
1353
|
+
courseId: config.courseId,
|
|
1354
|
+
sessionId: session.sessionId,
|
|
1355
|
+
attemptId: session.attemptId,
|
|
1356
|
+
user: session.user
|
|
1357
|
+
});
|
|
1358
|
+
return plugins?.scoreAssessment({ checkId, lessonId, response }, pluginCtx) ?? null;
|
|
1359
|
+
},
|
|
1360
|
+
[checkId, config.courseId, lessonId, plugins, session.attemptId, session.sessionId, session.user]
|
|
1361
|
+
);
|
|
1362
|
+
const scoreResponse = useCallback3(
|
|
1363
|
+
(response, defaultCorrect, maxScore = 1, passingScore) => scoreFromCustom(getPluginScore(response), defaultCorrect, maxScore, passingScore),
|
|
1364
|
+
[getPluginScore]
|
|
1365
|
+
);
|
|
1366
|
+
const isChoiceCorrect = useCallback3(
|
|
1367
|
+
(choice, answer, custom, passingScore) => {
|
|
1368
|
+
if (!custom) return choice === answer;
|
|
1369
|
+
if (custom.passed !== void 0) return custom.passed;
|
|
1370
|
+
if (custom.maxScore != null && custom.maxScore > 0 && custom.score != null) {
|
|
1371
|
+
return meetsPassingThreshold(custom.score, custom.maxScore, passingScore);
|
|
1372
|
+
}
|
|
1373
|
+
return choice === answer;
|
|
1374
|
+
},
|
|
1375
|
+
[]
|
|
1376
|
+
);
|
|
1377
|
+
return { getPluginScore, scoreResponse, isChoiceCorrect };
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
// src/components/Quiz.tsx
|
|
1381
|
+
import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1382
|
+
function QuizInner(props, ref) {
|
|
1383
|
+
const { enclosingLessonId } = props;
|
|
1384
|
+
const checkId = useMemo5(() => normalizeComponentId(props.checkId, "checkId"), [props.checkId]);
|
|
1385
|
+
const quiz = useQuizState(enclosingLessonId);
|
|
1386
|
+
const { getPluginScore, isChoiceCorrect } = usePluginScoring(checkId, enclosingLessonId);
|
|
1366
1387
|
const [selected, setSelected] = useState3(null);
|
|
1367
1388
|
const [selectionCorrect, setSelectionCorrect] = useState3(null);
|
|
1368
|
-
const [
|
|
1369
|
-
const
|
|
1370
|
-
const
|
|
1371
|
-
const
|
|
1372
|
-
|
|
1389
|
+
const [quizPassed, setQuizPassed] = useState3(false);
|
|
1390
|
+
const completedRef = useRef3(false);
|
|
1391
|
+
const questionId = useId();
|
|
1392
|
+
const choicesKey = props.choices.join("\0");
|
|
1393
|
+
useEffect3(() => {
|
|
1373
1394
|
completedRef.current = false;
|
|
1374
|
-
|
|
1395
|
+
setQuizPassed(false);
|
|
1375
1396
|
setSelected(null);
|
|
1376
1397
|
setSelectionCorrect(null);
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1398
|
+
}, [checkId, props.answer, props.question, choicesKey]);
|
|
1399
|
+
const passed = quizPassed;
|
|
1400
|
+
const handle = useMemo5(
|
|
1401
|
+
() => buildAssessmentHandle({
|
|
1402
|
+
checkId,
|
|
1403
|
+
getScore: () => {
|
|
1404
|
+
const maxScore = 1;
|
|
1405
|
+
if (quizPassed && selected !== null) return maxScore;
|
|
1406
|
+
if (selected === null) return 0;
|
|
1407
|
+
return selectionCorrect ? maxScore : 0;
|
|
1408
|
+
},
|
|
1409
|
+
getMaxScore: () => 1,
|
|
1388
1410
|
getAnswerGiven: () => selected !== null,
|
|
1389
|
-
resetTask:
|
|
1390
|
-
|
|
1411
|
+
resetTask: () => {
|
|
1412
|
+
completedRef.current = false;
|
|
1413
|
+
setQuizPassed(false);
|
|
1414
|
+
setSelected(null);
|
|
1415
|
+
setSelectionCorrect(null);
|
|
1416
|
+
},
|
|
1417
|
+
showSolutions: () => {
|
|
1418
|
+
},
|
|
1391
1419
|
getXAPIData: () => ({
|
|
1392
1420
|
checkId,
|
|
1393
|
-
interactionType:
|
|
1421
|
+
interactionType: "mcq",
|
|
1394
1422
|
response: selected ?? void 0,
|
|
1395
|
-
correct:
|
|
1396
|
-
score,
|
|
1397
|
-
maxScore
|
|
1398
|
-
})
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
question: props.question,
|
|
1423
|
-
response: value,
|
|
1424
|
-
correct: scored.passed
|
|
1425
|
-
});
|
|
1426
|
-
if (scored.passed && !completedRef.current) {
|
|
1427
|
-
completedRef.current = true;
|
|
1428
|
-
setPassed(true);
|
|
1429
|
-
assessment.complete({
|
|
1430
|
-
checkId,
|
|
1431
|
-
interactionType: INTERACTION,
|
|
1432
|
-
score: scored.score,
|
|
1433
|
-
maxScore: scored.maxScore,
|
|
1434
|
-
passingScore: props.passingScore ?? scored.maxScore
|
|
1435
|
-
});
|
|
1436
|
-
}
|
|
1437
|
-
};
|
|
1438
|
-
const reveal = showSolutions || passed && props.enableSolutionsButton;
|
|
1439
|
-
return /* @__PURE__ */ jsxs3("section", { "aria-label": "True or False", "data-lk-check-id": checkId, children: [
|
|
1440
|
-
/* @__PURE__ */ jsx5("p", { id: questionId, children: props.question }),
|
|
1441
|
-
/* @__PURE__ */ jsxs3("fieldset", { "aria-labelledby": questionId, children: [
|
|
1442
|
-
/* @__PURE__ */ jsx5("legend", { className: "lk-visually-hidden", children: "True or False" }),
|
|
1443
|
-
/* @__PURE__ */ jsxs3("label", { style: { display: "block", marginRight: "1rem" }, children: [
|
|
1444
|
-
/* @__PURE__ */ jsx5(
|
|
1445
|
-
"input",
|
|
1446
|
-
{
|
|
1447
|
-
type: "radio",
|
|
1448
|
-
name: `${questionId}-tf`,
|
|
1449
|
-
checked: selected === true,
|
|
1450
|
-
disabled: passed && !props.enableRetry,
|
|
1451
|
-
onChange: () => submit(true)
|
|
1452
|
-
}
|
|
1453
|
-
),
|
|
1454
|
-
"True"
|
|
1455
|
-
] }),
|
|
1456
|
-
/* @__PURE__ */ jsxs3("label", { style: { display: "block" }, children: [
|
|
1457
|
-
/* @__PURE__ */ jsx5(
|
|
1423
|
+
correct: selectionCorrect ?? void 0,
|
|
1424
|
+
score: quizPassed && selected !== null ? 1 : selected === null ? 0 : selectionCorrect ? 1 : 0,
|
|
1425
|
+
maxScore: 1
|
|
1426
|
+
}),
|
|
1427
|
+
getCurrentState: () => ({ selected, selectionCorrect, quizPassed }),
|
|
1428
|
+
resume: (state) => {
|
|
1429
|
+
const nextSelected = readStringField(state, "selected");
|
|
1430
|
+
if (typeof nextSelected === "string" || nextSelected === null) setSelected(nextSelected);
|
|
1431
|
+
const nextCorrect = readBooleanField(state, "selectionCorrect");
|
|
1432
|
+
if (nextCorrect === true || nextCorrect === false || nextCorrect === null) {
|
|
1433
|
+
setSelectionCorrect(nextCorrect);
|
|
1434
|
+
}
|
|
1435
|
+
readBooleanStateField(state, "quizPassed", (value) => {
|
|
1436
|
+
setQuizPassed(value);
|
|
1437
|
+
completedRef.current = value;
|
|
1438
|
+
});
|
|
1439
|
+
}
|
|
1440
|
+
}),
|
|
1441
|
+
[checkId, quizPassed, selected, selectionCorrect]
|
|
1442
|
+
);
|
|
1443
|
+
useAssessmentHandleRegistration(checkId, handle, ref);
|
|
1444
|
+
return /* @__PURE__ */ jsxs2("section", { "aria-label": "Quiz", "data-lk-check-id": checkId, children: [
|
|
1445
|
+
/* @__PURE__ */ jsx4("p", { id: questionId, children: props.question }),
|
|
1446
|
+
/* @__PURE__ */ jsxs2("fieldset", { "aria-labelledby": questionId, children: [
|
|
1447
|
+
/* @__PURE__ */ jsx4("legend", { style: visuallyHiddenStyle, children: "Quiz choices" }),
|
|
1448
|
+
props.choices.map((c, i) => /* @__PURE__ */ jsxs2("label", { style: { display: "block" }, children: [
|
|
1449
|
+
/* @__PURE__ */ jsx4(
|
|
1458
1450
|
"input",
|
|
1459
1451
|
{
|
|
1460
1452
|
type: "radio",
|
|
1461
|
-
name:
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1453
|
+
name: questionId,
|
|
1454
|
+
value: c,
|
|
1455
|
+
checked: selected === c,
|
|
1456
|
+
disabled: passed,
|
|
1457
|
+
"aria-invalid": selected === c && selectionCorrect === false ? true : void 0,
|
|
1458
|
+
onChange: () => {
|
|
1459
|
+
if (passed) return;
|
|
1460
|
+
setSelected(c);
|
|
1461
|
+
const custom = getPluginScore(c);
|
|
1462
|
+
const correct = isChoiceCorrect(c, props.answer, custom, props.passingScore);
|
|
1463
|
+
setSelectionCorrect(correct);
|
|
1464
|
+
quiz.answer({
|
|
1465
|
+
checkId,
|
|
1466
|
+
question: props.question,
|
|
1467
|
+
choice: c,
|
|
1468
|
+
correct
|
|
1469
|
+
});
|
|
1470
|
+
if (correct && !completedRef.current) {
|
|
1471
|
+
completedRef.current = true;
|
|
1472
|
+
setQuizPassed(true);
|
|
1473
|
+
const maxScore = custom?.maxScore ?? 1;
|
|
1474
|
+
quiz.complete({
|
|
1475
|
+
checkId,
|
|
1476
|
+
score: custom?.score ?? maxScore,
|
|
1477
|
+
maxScore,
|
|
1478
|
+
passingScore: props.passingScore ?? maxScore
|
|
1479
|
+
});
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1465
1482
|
}
|
|
1466
1483
|
),
|
|
1467
|
-
|
|
1468
|
-
] })
|
|
1484
|
+
c
|
|
1485
|
+
] }, `${questionId}-${i}`))
|
|
1469
1486
|
] }),
|
|
1470
|
-
|
|
1471
|
-
"Correct answer: ",
|
|
1472
|
-
/* @__PURE__ */ jsx5("strong", { children: props.answer ? "True" : "False" })
|
|
1473
|
-
] }) : null,
|
|
1474
|
-
selected !== null && selectionCorrect !== null ? /* @__PURE__ */ jsx5("p", { role: "status", "aria-live": "polite", children: selectionCorrect ? "Correct" : "Try again" }) : null,
|
|
1475
|
-
props.enableRetry && passed ? /* @__PURE__ */ jsx5("button", { type: "button", onClick: reset, children: "Try again" }) : null,
|
|
1476
|
-
props.enableSolutionsButton && !reveal ? /* @__PURE__ */ jsx5("button", { type: "button", onClick: () => setShowSolutions(true), children: "Show solution" }) : null
|
|
1487
|
+
selected && selectionCorrect !== null ? /* @__PURE__ */ jsx4("p", { role: "status", "aria-live": "polite", children: selectionCorrect ? "Correct" : "Try again" }) : null
|
|
1477
1488
|
] });
|
|
1478
1489
|
}
|
|
1479
|
-
var
|
|
1480
|
-
var
|
|
1481
|
-
return /* @__PURE__ */
|
|
1490
|
+
var QuizInnerForwarded = forwardRef(QuizInner);
|
|
1491
|
+
var Quiz = forwardRef(function Quiz2(props, ref) {
|
|
1492
|
+
return /* @__PURE__ */ jsx4(AssessmentLessonGuard, { blockLabel: "Quiz", checkId: props.checkId, children: (lessonId) => /* @__PURE__ */ jsx4(QuizInnerForwarded, { ...props, enclosingLessonId: lessonId, ref }) });
|
|
1482
1493
|
});
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
const assessment = useAssessmentState(props.enclosingLessonId);
|
|
1494
|
-
const tokens = useMemo7(() => tokenize(props.text), [props.text]);
|
|
1495
|
-
const correctSet = useMemo7(
|
|
1496
|
-
() => new Set(props.correctWords.map((w) => w.toLowerCase())),
|
|
1497
|
-
[props.correctWords]
|
|
1494
|
+
function KnowledgeCheck(props) {
|
|
1495
|
+
return /* @__PURE__ */ jsx4(
|
|
1496
|
+
Quiz,
|
|
1497
|
+
{
|
|
1498
|
+
checkId: props.checkId,
|
|
1499
|
+
question: props.question,
|
|
1500
|
+
choices: props.choices,
|
|
1501
|
+
answer: props.answer,
|
|
1502
|
+
passingScore: props.passingScore
|
|
1503
|
+
}
|
|
1498
1504
|
);
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1505
|
+
}
|
|
1506
|
+
function resetQuizWarningsForTests() {
|
|
1507
|
+
resetAssessmentWarningsForTests();
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
// src/components.tsx
|
|
1511
|
+
import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
1512
|
+
function Course(props) {
|
|
1513
|
+
const courseId = useMemo6(() => normalizeComponentId(props.courseId, "courseId"), [props.courseId]);
|
|
1514
|
+
const providerConfig = useMemo6(
|
|
1515
|
+
() => ({ ...props.config, courseId }),
|
|
1516
|
+
[props.config, courseId]
|
|
1517
|
+
);
|
|
1518
|
+
return /* @__PURE__ */ jsx5(LessonkitProvider, { config: providerConfig, children: /* @__PURE__ */ jsxs3("section", { "aria-label": props.title, children: [
|
|
1519
|
+
/* @__PURE__ */ jsx5("h1", { children: props.title }),
|
|
1520
|
+
/* @__PURE__ */ jsx5("div", { children: props.children })
|
|
1521
|
+
] }) });
|
|
1522
|
+
}
|
|
1523
|
+
function Lesson(props) {
|
|
1524
|
+
const lessonId = useMemo6(() => normalizeComponentId(props.lessonId, "lessonId"), [props.lessonId]);
|
|
1525
|
+
const autoComplete = props.autoCompleteOnUnmount !== false;
|
|
1526
|
+
const { setActiveLesson, config } = useLessonkit();
|
|
1527
|
+
const { completeLesson } = useCompletion();
|
|
1528
|
+
const lessonMountGenerationRef = useRef4(0);
|
|
1529
|
+
const liveCourseIdRef = useRef4(config.courseId);
|
|
1530
|
+
liveCourseIdRef.current = config.courseId;
|
|
1531
|
+
useEffect4(() => {
|
|
1532
|
+
const unregister = registerLessonMount(lessonId);
|
|
1533
|
+
const generation = ++lessonMountGenerationRef.current;
|
|
1534
|
+
const mountedCourseId = config.courseId;
|
|
1535
|
+
let effectSurvivedTick = false;
|
|
1536
|
+
queueMicrotask(() => {
|
|
1537
|
+
queueMicrotask(() => {
|
|
1538
|
+
effectSurvivedTick = true;
|
|
1539
|
+
});
|
|
1540
|
+
});
|
|
1541
|
+
setActiveLesson(lessonId);
|
|
1542
|
+
return () => {
|
|
1543
|
+
unregister();
|
|
1544
|
+
if (getLessonMountCount(lessonId) > 0) {
|
|
1545
|
+
return;
|
|
1546
|
+
}
|
|
1547
|
+
if (!autoComplete) return;
|
|
1548
|
+
queueMicrotask(() => {
|
|
1549
|
+
if (!effectSurvivedTick) return;
|
|
1550
|
+
if (lessonMountGenerationRef.current !== generation) return;
|
|
1551
|
+
if (liveCourseIdRef.current !== mountedCourseId) return;
|
|
1552
|
+
completeLesson(lessonId, { courseId: mountedCourseId });
|
|
1553
|
+
});
|
|
1554
|
+
};
|
|
1555
|
+
}, [lessonId, config.courseId, setActiveLesson, completeLesson, autoComplete]);
|
|
1556
|
+
return /* @__PURE__ */ jsx5(LessonContext.Provider, { value: lessonId, children: /* @__PURE__ */ jsxs3("article", { "aria-label": props.title, children: [
|
|
1557
|
+
/* @__PURE__ */ jsx5("h2", { children: props.title }),
|
|
1558
|
+
/* @__PURE__ */ jsx5("div", { children: props.children })
|
|
1559
|
+
] }) });
|
|
1560
|
+
}
|
|
1561
|
+
function Scenario(props) {
|
|
1562
|
+
const blockId = useMemo6(
|
|
1563
|
+
() => props.blockId !== void 0 ? normalizeComponentId(props.blockId, "blockId") : void 0,
|
|
1564
|
+
[props.blockId]
|
|
1565
|
+
);
|
|
1566
|
+
return /* @__PURE__ */ jsx5("section", { "aria-label": "Scenario", "data-lk-block-id": blockId, children: props.children });
|
|
1567
|
+
}
|
|
1568
|
+
function Reflection(props) {
|
|
1569
|
+
const blockId = useMemo6(
|
|
1570
|
+
() => props.blockId !== void 0 ? normalizeComponentId(props.blockId, "blockId") : void 0,
|
|
1571
|
+
[props.blockId]
|
|
1572
|
+
);
|
|
1573
|
+
const promptId = useId2();
|
|
1574
|
+
const hintId = useId2();
|
|
1575
|
+
const [internalValue, setInternalValue] = useState4("");
|
|
1576
|
+
const isControlled = props.value !== void 0;
|
|
1577
|
+
const value = isControlled ? props.value : internalValue;
|
|
1578
|
+
const handleChange = (event) => {
|
|
1579
|
+
if (!isControlled) setInternalValue(event.target.value);
|
|
1580
|
+
props.onChange?.(event.target.value);
|
|
1581
|
+
};
|
|
1582
|
+
return /* @__PURE__ */ jsxs3("section", { "aria-label": "Reflection", "data-lk-block-id": blockId, children: [
|
|
1583
|
+
props.prompt ? /* @__PURE__ */ jsx5("p", { id: promptId, children: props.prompt }) : null,
|
|
1584
|
+
props.hint ? /* @__PURE__ */ jsx5("p", { id: hintId, style: visuallyHiddenStyle2, children: props.hint }) : null,
|
|
1585
|
+
props.children,
|
|
1586
|
+
/* @__PURE__ */ jsx5(
|
|
1587
|
+
"textarea",
|
|
1588
|
+
{
|
|
1589
|
+
value,
|
|
1590
|
+
onChange: handleChange,
|
|
1591
|
+
"aria-labelledby": props.prompt ? promptId : void 0,
|
|
1592
|
+
"aria-describedby": props.hint ? hintId : void 0,
|
|
1593
|
+
"aria-label": props.prompt ? void 0 : "Reflection response"
|
|
1594
|
+
}
|
|
1595
|
+
)
|
|
1596
|
+
] });
|
|
1597
|
+
}
|
|
1598
|
+
function ProgressTracker(props) {
|
|
1599
|
+
const { progress } = useLessonkit();
|
|
1600
|
+
const completed = progress.completedLessonIds.size;
|
|
1601
|
+
if (props.totalLessons != null) {
|
|
1602
|
+
const total = props.totalLessons;
|
|
1603
|
+
const displayed = Math.min(completed, total);
|
|
1604
|
+
return /* @__PURE__ */ jsx5("aside", { "aria-label": "Progress", children: /* @__PURE__ */ jsx5(
|
|
1605
|
+
"div",
|
|
1606
|
+
{
|
|
1607
|
+
role: "progressbar",
|
|
1608
|
+
"aria-valuemin": 0,
|
|
1609
|
+
"aria-valuemax": total,
|
|
1610
|
+
"aria-valuenow": displayed,
|
|
1611
|
+
"aria-label": "Lessons completed",
|
|
1612
|
+
children: /* @__PURE__ */ jsxs3("p", { children: [
|
|
1613
|
+
"Lessons completed: ",
|
|
1614
|
+
displayed,
|
|
1615
|
+
" of ",
|
|
1616
|
+
total
|
|
1617
|
+
] })
|
|
1618
|
+
}
|
|
1619
|
+
) });
|
|
1620
|
+
}
|
|
1621
|
+
return /* @__PURE__ */ jsx5("aside", { "aria-label": "Progress", role: "status", children: /* @__PURE__ */ jsxs3("p", { children: [
|
|
1622
|
+
"Lessons completed: ",
|
|
1623
|
+
completed
|
|
1624
|
+
] }) });
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1627
|
+
// src/blocks/TrueFalse.tsx
|
|
1628
|
+
import React7, { forwardRef as forwardRef2, useEffect as useEffect5, useMemo as useMemo7, useRef as useRef5, useState as useState5 } from "react";
|
|
1629
|
+
import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1630
|
+
var INTERACTION = "trueFalse";
|
|
1631
|
+
function TrueFalseInner(props, ref) {
|
|
1632
|
+
const { enclosingLessonId } = props;
|
|
1633
|
+
const checkId = useMemo7(() => normalizeComponentId(props.checkId, "checkId"), [props.checkId]);
|
|
1634
|
+
const assessment = useAssessmentState(enclosingLessonId);
|
|
1635
|
+
const { config } = useLessonkit();
|
|
1636
|
+
const { scoreResponse } = usePluginScoring(checkId, enclosingLessonId);
|
|
1637
|
+
const [selected, setSelected] = useState5(null);
|
|
1638
|
+
const [selectionCorrect, setSelectionCorrect] = useState5(null);
|
|
1639
|
+
const [showSolutions, setShowSolutions] = useState5(false);
|
|
1640
|
+
const [passed, setPassed] = useState5(false);
|
|
1641
|
+
const completedRef = useRef5(false);
|
|
1642
|
+
const questionId = React7.useId();
|
|
1503
1643
|
const reset = () => {
|
|
1504
1644
|
completedRef.current = false;
|
|
1505
1645
|
setPassed(false);
|
|
1506
|
-
|
|
1646
|
+
setSelected(null);
|
|
1647
|
+
setSelectionCorrect(null);
|
|
1507
1648
|
setShowSolutions(false);
|
|
1508
1649
|
};
|
|
1509
1650
|
useEffect5(() => {
|
|
1510
1651
|
reset();
|
|
1652
|
+
}, [checkId, props.answer, props.question, config.courseId, enclosingLessonId]);
|
|
1653
|
+
const handle = useMemo7(
|
|
1654
|
+
() => buildAssessmentHandle({
|
|
1655
|
+
checkId,
|
|
1656
|
+
getScore: () => {
|
|
1657
|
+
const maxScore = 1;
|
|
1658
|
+
return passed ? maxScore : selected === null ? 0 : selected === props.answer ? maxScore : 0;
|
|
1659
|
+
},
|
|
1660
|
+
getMaxScore: () => 1,
|
|
1661
|
+
getAnswerGiven: () => selected !== null,
|
|
1662
|
+
resetTask: reset,
|
|
1663
|
+
showSolutions: () => setShowSolutions(true),
|
|
1664
|
+
getXAPIData: () => ({
|
|
1665
|
+
checkId,
|
|
1666
|
+
interactionType: INTERACTION,
|
|
1667
|
+
response: selected ?? void 0,
|
|
1668
|
+
correct: selected === props.answer,
|
|
1669
|
+
score: passed ? 1 : selected === null ? 0 : selected === props.answer ? 1 : 0,
|
|
1670
|
+
maxScore: 1
|
|
1671
|
+
}),
|
|
1672
|
+
getCurrentState: () => ({ selected, selectionCorrect, passed, showSolutions }),
|
|
1673
|
+
resume: (state) => {
|
|
1674
|
+
const nextSelected = readBooleanField(state, "selected");
|
|
1675
|
+
if (nextSelected === true || nextSelected === false || nextSelected === null) {
|
|
1676
|
+
setSelected(nextSelected);
|
|
1677
|
+
}
|
|
1678
|
+
const nextCorrect = readBooleanField(state, "selectionCorrect");
|
|
1679
|
+
if (nextCorrect === true || nextCorrect === false || nextCorrect === null) {
|
|
1680
|
+
setSelectionCorrect(nextCorrect);
|
|
1681
|
+
}
|
|
1682
|
+
readBooleanStateField(state, "passed", (value) => {
|
|
1683
|
+
setPassed(value);
|
|
1684
|
+
completedRef.current = value;
|
|
1685
|
+
});
|
|
1686
|
+
readBooleanStateField(state, "showSolutions", setShowSolutions);
|
|
1687
|
+
}
|
|
1688
|
+
}),
|
|
1689
|
+
[checkId, passed, props.answer, selected, selectionCorrect, showSolutions]
|
|
1690
|
+
);
|
|
1691
|
+
useAssessmentHandleRegistration(checkId, handle, ref);
|
|
1692
|
+
const submit = (value) => {
|
|
1693
|
+
if (passed && !props.enableRetry) return;
|
|
1694
|
+
setSelected(value);
|
|
1695
|
+
const correct = value === props.answer;
|
|
1696
|
+
const scored = scoreResponse(value, correct, 1, props.passingScore);
|
|
1697
|
+
setSelectionCorrect(scored.passed);
|
|
1698
|
+
assessment.answer({
|
|
1699
|
+
checkId,
|
|
1700
|
+
interactionType: INTERACTION,
|
|
1701
|
+
question: props.question,
|
|
1702
|
+
response: value,
|
|
1703
|
+
correct: scored.passed
|
|
1704
|
+
});
|
|
1705
|
+
if (scored.passed && !completedRef.current) {
|
|
1706
|
+
completedRef.current = true;
|
|
1707
|
+
setPassed(true);
|
|
1708
|
+
assessment.complete({
|
|
1709
|
+
checkId,
|
|
1710
|
+
interactionType: INTERACTION,
|
|
1711
|
+
score: scored.score,
|
|
1712
|
+
maxScore: scored.maxScore,
|
|
1713
|
+
passingScore: props.passingScore ?? scored.maxScore
|
|
1714
|
+
});
|
|
1715
|
+
}
|
|
1716
|
+
};
|
|
1717
|
+
const reveal = showSolutions || passed && props.enableSolutionsButton;
|
|
1718
|
+
return /* @__PURE__ */ jsxs4("section", { "aria-label": "True or False", "data-lk-check-id": checkId, children: [
|
|
1719
|
+
/* @__PURE__ */ jsx6("p", { id: questionId, children: props.question }),
|
|
1720
|
+
/* @__PURE__ */ jsxs4("fieldset", { "aria-labelledby": questionId, children: [
|
|
1721
|
+
/* @__PURE__ */ jsx6("legend", { className: "lk-visually-hidden", children: "True or False" }),
|
|
1722
|
+
/* @__PURE__ */ jsxs4("label", { style: { display: "block", marginRight: "1rem" }, children: [
|
|
1723
|
+
/* @__PURE__ */ jsx6(
|
|
1724
|
+
"input",
|
|
1725
|
+
{
|
|
1726
|
+
type: "radio",
|
|
1727
|
+
name: `${questionId}-tf`,
|
|
1728
|
+
checked: selected === true,
|
|
1729
|
+
disabled: passed && !props.enableRetry,
|
|
1730
|
+
onChange: () => submit(true)
|
|
1731
|
+
}
|
|
1732
|
+
),
|
|
1733
|
+
"True"
|
|
1734
|
+
] }),
|
|
1735
|
+
/* @__PURE__ */ jsxs4("label", { style: { display: "block" }, children: [
|
|
1736
|
+
/* @__PURE__ */ jsx6(
|
|
1737
|
+
"input",
|
|
1738
|
+
{
|
|
1739
|
+
type: "radio",
|
|
1740
|
+
name: `${questionId}-tf`,
|
|
1741
|
+
checked: selected === false,
|
|
1742
|
+
disabled: passed && !props.enableRetry,
|
|
1743
|
+
onChange: () => submit(false)
|
|
1744
|
+
}
|
|
1745
|
+
),
|
|
1746
|
+
"False"
|
|
1747
|
+
] })
|
|
1748
|
+
] }),
|
|
1749
|
+
reveal ? /* @__PURE__ */ jsxs4("p", { children: [
|
|
1750
|
+
"Correct answer: ",
|
|
1751
|
+
/* @__PURE__ */ jsx6("strong", { children: props.answer ? "True" : "False" })
|
|
1752
|
+
] }) : null,
|
|
1753
|
+
selected !== null && selectionCorrect !== null ? /* @__PURE__ */ jsx6("p", { role: "status", "aria-live": "polite", children: selectionCorrect ? "Correct" : "Try again" }) : null,
|
|
1754
|
+
props.enableRetry && passed ? /* @__PURE__ */ jsx6("button", { type: "button", onClick: reset, children: "Try again" }) : null,
|
|
1755
|
+
props.enableSolutionsButton && !reveal ? /* @__PURE__ */ jsx6("button", { type: "button", onClick: () => setShowSolutions(true), children: "Show solution" }) : null
|
|
1756
|
+
] });
|
|
1757
|
+
}
|
|
1758
|
+
var TrueFalseInnerForwarded = forwardRef2(TrueFalseInner);
|
|
1759
|
+
var TrueFalse = forwardRef2(function TrueFalse2(props, ref) {
|
|
1760
|
+
return /* @__PURE__ */ jsx6(AssessmentLessonGuard, { blockLabel: "TrueFalse", checkId: props.checkId, children: (lessonId) => /* @__PURE__ */ jsx6(TrueFalseInnerForwarded, { ...props, enclosingLessonId: lessonId, ref }) });
|
|
1761
|
+
});
|
|
1762
|
+
|
|
1763
|
+
// src/blocks/MarkTheWords.tsx
|
|
1764
|
+
import React8, { forwardRef as forwardRef3, useEffect as useEffect6, useMemo as useMemo8, useRef as useRef6, useState as useState6 } from "react";
|
|
1765
|
+
import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1766
|
+
var INTERACTION2 = "markTheWords";
|
|
1767
|
+
function tokenize(text) {
|
|
1768
|
+
return text.split(/(\s+)/).filter((t) => t.length > 0);
|
|
1769
|
+
}
|
|
1770
|
+
function MarkTheWordsInner(props, ref) {
|
|
1771
|
+
const checkId = useMemo8(() => normalizeComponentId(props.checkId, "checkId"), [props.checkId]);
|
|
1772
|
+
const assessment = useAssessmentState(props.enclosingLessonId);
|
|
1773
|
+
const tokens = useMemo8(() => tokenize(props.text), [props.text]);
|
|
1774
|
+
const correctSet = useMemo8(
|
|
1775
|
+
() => new Set(props.correctWords.map((w) => w.toLowerCase())),
|
|
1776
|
+
[props.correctWords]
|
|
1777
|
+
);
|
|
1778
|
+
const [marked, setMarked] = useState6(() => /* @__PURE__ */ new Set());
|
|
1779
|
+
const [passed, setPassed] = useState6(false);
|
|
1780
|
+
const [showSolutions, setShowSolutions] = useState6(false);
|
|
1781
|
+
const completedRef = useRef6(false);
|
|
1782
|
+
const reset = () => {
|
|
1783
|
+
completedRef.current = false;
|
|
1784
|
+
setPassed(false);
|
|
1785
|
+
setMarked(/* @__PURE__ */ new Set());
|
|
1786
|
+
setShowSolutions(false);
|
|
1787
|
+
};
|
|
1788
|
+
useEffect6(() => {
|
|
1789
|
+
reset();
|
|
1511
1790
|
}, [checkId, props.text, props.correctWords.join("\0")]);
|
|
1512
|
-
const selectableIndices =
|
|
1791
|
+
const selectableIndices = useMemo8(() => {
|
|
1513
1792
|
const indices = [];
|
|
1514
1793
|
tokens.forEach((t, i) => {
|
|
1515
1794
|
if (!/^\s+$/.test(t) && correctSet.has(t.toLowerCase())) indices.push(i);
|
|
@@ -1521,11 +1800,11 @@ function MarkTheWordsInner(props, ref) {
|
|
|
1521
1800
|
const maxScore = selectableIndices.length;
|
|
1522
1801
|
const score = allMarked ? maxScore : marked.size;
|
|
1523
1802
|
const passedThreshold = meetsPassingThreshold(score, maxScore || 1, props.passingScore);
|
|
1524
|
-
const handle =
|
|
1525
|
-
|
|
1526
|
-
|
|
1803
|
+
const handle = useMemo8(
|
|
1804
|
+
() => buildAssessmentHandle({
|
|
1805
|
+
checkId,
|
|
1527
1806
|
getScore: () => score,
|
|
1528
|
-
getMaxScore: () =>
|
|
1807
|
+
getMaxScore: () => maxScore || 1,
|
|
1529
1808
|
getAnswerGiven: () => marked.size > 0,
|
|
1530
1809
|
resetTask: reset,
|
|
1531
1810
|
showSolutions: () => setShowSolutions(true),
|
|
@@ -1535,12 +1814,22 @@ function MarkTheWordsInner(props, ref) {
|
|
|
1535
1814
|
response: [...marked].map((i) => tokens[i]),
|
|
1536
1815
|
correct: passedThreshold,
|
|
1537
1816
|
score,
|
|
1538
|
-
maxScore:
|
|
1539
|
-
})
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1817
|
+
maxScore: maxScore || 1
|
|
1818
|
+
}),
|
|
1819
|
+
getCurrentState: () => ({ marked: [...marked], passed, showSolutions }),
|
|
1820
|
+
resume: (state) => {
|
|
1821
|
+
const raw = state.marked;
|
|
1822
|
+
if (Array.isArray(raw)) setMarked(new Set(raw.filter((i) => typeof i === "number")));
|
|
1823
|
+
readBooleanStateField(state, "passed", (value) => {
|
|
1824
|
+
setPassed(value);
|
|
1825
|
+
completedRef.current = value;
|
|
1826
|
+
});
|
|
1827
|
+
readBooleanStateField(state, "showSolutions", setShowSolutions);
|
|
1828
|
+
}
|
|
1829
|
+
}),
|
|
1830
|
+
[checkId, marked, maxScore, passed, passedThreshold, score, showSolutions, tokens]
|
|
1831
|
+
);
|
|
1832
|
+
useAssessmentHandleRegistration(checkId, handle, ref);
|
|
1544
1833
|
const toggle = (index) => {
|
|
1545
1834
|
if (passed && !props.enableRetry) return;
|
|
1546
1835
|
setMarked((prev) => {
|
|
@@ -1550,7 +1839,7 @@ function MarkTheWordsInner(props, ref) {
|
|
|
1550
1839
|
return next;
|
|
1551
1840
|
});
|
|
1552
1841
|
};
|
|
1553
|
-
|
|
1842
|
+
useEffect6(() => {
|
|
1554
1843
|
if (!hasTargets) {
|
|
1555
1844
|
if (isDevEnvironment4()) {
|
|
1556
1845
|
console.warn(
|
|
@@ -1590,20 +1879,20 @@ function MarkTheWordsInner(props, ref) {
|
|
|
1590
1879
|
score,
|
|
1591
1880
|
tokens
|
|
1592
1881
|
]);
|
|
1593
|
-
return /* @__PURE__ */
|
|
1594
|
-
!hasTargets ? /* @__PURE__ */
|
|
1882
|
+
return /* @__PURE__ */ jsxs5("section", { "aria-label": "Mark the Words", "data-lk-check-id": checkId, children: [
|
|
1883
|
+
!hasTargets ? /* @__PURE__ */ jsxs5("p", { role: "alert", children: [
|
|
1595
1884
|
"No words in this sentence match ",
|
|
1596
|
-
/* @__PURE__ */
|
|
1885
|
+
/* @__PURE__ */ jsx7("code", { children: "correctWords" }),
|
|
1597
1886
|
". Check spelling and capitalization in the source text."
|
|
1598
1887
|
] }) : null,
|
|
1599
|
-
/* @__PURE__ */
|
|
1600
|
-
/* @__PURE__ */
|
|
1888
|
+
/* @__PURE__ */ jsx7("p", { id: `${checkId}-hint`, children: "Select the correct words in the sentence." }),
|
|
1889
|
+
/* @__PURE__ */ jsx7("p", { "aria-describedby": `${checkId}-hint`, children: tokens.map((token, i) => {
|
|
1601
1890
|
const isWord = !/^\s+$/.test(token);
|
|
1602
1891
|
const isTarget = isWord && correctSet.has(token.toLowerCase());
|
|
1603
|
-
if (!isTarget) return /* @__PURE__ */
|
|
1892
|
+
if (!isTarget) return /* @__PURE__ */ jsx7(React8.Fragment, { children: token }, i);
|
|
1604
1893
|
const selected = marked.has(i);
|
|
1605
1894
|
const solution = showSolutions || passed && props.enableSolutionsButton;
|
|
1606
|
-
return /* @__PURE__ */
|
|
1895
|
+
return /* @__PURE__ */ jsx7(
|
|
1607
1896
|
"button",
|
|
1608
1897
|
{
|
|
1609
1898
|
type: "button",
|
|
@@ -1621,49 +1910,59 @@ function MarkTheWordsInner(props, ref) {
|
|
|
1621
1910
|
i
|
|
1622
1911
|
);
|
|
1623
1912
|
}) }),
|
|
1624
|
-
allMarked ? /* @__PURE__ */
|
|
1625
|
-
props.enableRetry && passed ? /* @__PURE__ */
|
|
1626
|
-
props.enableSolutionsButton && !showSolutions ? /* @__PURE__ */
|
|
1913
|
+
allMarked ? /* @__PURE__ */ jsx7("p", { role: "status", "aria-live": "polite", children: "Correct" }) : null,
|
|
1914
|
+
props.enableRetry && passed ? /* @__PURE__ */ jsx7("button", { type: "button", onClick: reset, children: "Try again" }) : null,
|
|
1915
|
+
props.enableSolutionsButton && !showSolutions ? /* @__PURE__ */ jsx7("button", { type: "button", onClick: () => setShowSolutions(true), children: "Show solution" }) : null
|
|
1627
1916
|
] });
|
|
1628
1917
|
}
|
|
1629
|
-
var MarkTheWordsInnerForwarded =
|
|
1630
|
-
var MarkTheWords =
|
|
1631
|
-
return /* @__PURE__ */
|
|
1918
|
+
var MarkTheWordsInnerForwarded = forwardRef3(MarkTheWordsInner);
|
|
1919
|
+
var MarkTheWords = forwardRef3(function MarkTheWords2(props, ref) {
|
|
1920
|
+
return /* @__PURE__ */ jsx7(AssessmentLessonGuard, { blockLabel: "MarkTheWords", checkId: props.checkId, children: (lessonId) => /* @__PURE__ */ jsx7(MarkTheWordsInnerForwarded, { ...props, enclosingLessonId: lessonId, ref }) });
|
|
1632
1921
|
});
|
|
1633
1922
|
|
|
1634
1923
|
// src/blocks/FillInTheBlanks.tsx
|
|
1635
|
-
import
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
function
|
|
1924
|
+
import React9, { forwardRef as forwardRef4, useEffect as useEffect7, useMemo as useMemo9, useRef as useRef7, useState as useState7 } from "react";
|
|
1925
|
+
|
|
1926
|
+
// src/assessment/internal/parseStarDelimitedTemplate.ts
|
|
1927
|
+
function parseStarDelimitedTemplate(template, idPrefix) {
|
|
1639
1928
|
const parts = [];
|
|
1640
|
-
const
|
|
1929
|
+
const values = [];
|
|
1641
1930
|
const re = /\*([^*]+)\*/g;
|
|
1642
1931
|
let last = 0;
|
|
1643
1932
|
let match;
|
|
1644
1933
|
let n = 0;
|
|
1645
1934
|
while ((match = re.exec(template)) !== null) {
|
|
1646
1935
|
parts.push(template.slice(last, match.index));
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
parts.push(id);
|
|
1936
|
+
values.push(match[1].trim());
|
|
1937
|
+
parts.push(`${idPrefix}-${n++}`);
|
|
1650
1938
|
last = match.index + match[0].length;
|
|
1651
1939
|
}
|
|
1652
1940
|
parts.push(template.slice(last));
|
|
1653
|
-
return { parts,
|
|
1941
|
+
return { parts, values };
|
|
1942
|
+
}
|
|
1943
|
+
|
|
1944
|
+
// src/blocks/FillInTheBlanks.tsx
|
|
1945
|
+
import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1946
|
+
var INTERACTION3 = "fillInBlanks";
|
|
1947
|
+
function parseTemplate(template) {
|
|
1948
|
+
const { parts, values } = parseStarDelimitedTemplate(template, "blank");
|
|
1949
|
+
return {
|
|
1950
|
+
parts,
|
|
1951
|
+
blanks: values.map((answer, i) => ({ id: `blank-${i}`, answer }))
|
|
1952
|
+
};
|
|
1654
1953
|
}
|
|
1655
1954
|
function FillInTheBlanksInner(props, ref) {
|
|
1656
|
-
const checkId =
|
|
1955
|
+
const checkId = useMemo9(() => normalizeComponentId(props.checkId, "checkId"), [props.checkId]);
|
|
1657
1956
|
const assessment = useAssessmentState(props.enclosingLessonId);
|
|
1658
|
-
const parsed =
|
|
1957
|
+
const parsed = useMemo9(() => parseTemplate(props.template), [props.template]);
|
|
1659
1958
|
const blanks = props.blanks ?? parsed.blanks;
|
|
1660
|
-
const [values, setValues] =
|
|
1959
|
+
const [values, setValues] = useState7(
|
|
1661
1960
|
() => Object.fromEntries(blanks.map((b) => [b.id, ""]))
|
|
1662
1961
|
);
|
|
1663
|
-
const [passed, setPassed] =
|
|
1664
|
-
const [showSolutions, setShowSolutions] =
|
|
1665
|
-
const completedRef =
|
|
1666
|
-
const answeredRef =
|
|
1962
|
+
const [passed, setPassed] = useState7(false);
|
|
1963
|
+
const [showSolutions, setShowSolutions] = useState7(false);
|
|
1964
|
+
const completedRef = useRef7(false);
|
|
1965
|
+
const answeredRef = useRef7(false);
|
|
1667
1966
|
const reset = () => {
|
|
1668
1967
|
completedRef.current = false;
|
|
1669
1968
|
answeredRef.current = false;
|
|
@@ -1671,7 +1970,7 @@ function FillInTheBlanksInner(props, ref) {
|
|
|
1671
1970
|
setValues(Object.fromEntries(blanks.map((b) => [b.id, ""])));
|
|
1672
1971
|
setShowSolutions(false);
|
|
1673
1972
|
};
|
|
1674
|
-
|
|
1973
|
+
useEffect7(() => {
|
|
1675
1974
|
reset();
|
|
1676
1975
|
}, [checkId, props.template, blanks.map((b) => b.answer).join("\0")]);
|
|
1677
1976
|
const hasBlanks = blanks.length > 0;
|
|
@@ -1682,11 +1981,11 @@ function FillInTheBlanksInner(props, ref) {
|
|
|
1682
1981
|
});
|
|
1683
1982
|
const maxScore = blanks.length;
|
|
1684
1983
|
const passedThreshold = meetsPassingThreshold(score, maxScore || 1, props.passingScore);
|
|
1685
|
-
const handle =
|
|
1686
|
-
|
|
1687
|
-
|
|
1984
|
+
const handle = useMemo9(
|
|
1985
|
+
() => buildAssessmentHandle({
|
|
1986
|
+
checkId,
|
|
1688
1987
|
getScore: () => score,
|
|
1689
|
-
getMaxScore: () =>
|
|
1988
|
+
getMaxScore: () => maxScore || 1,
|
|
1690
1989
|
getAnswerGiven: () => allFilled,
|
|
1691
1990
|
resetTask: reset,
|
|
1692
1991
|
showSolutions: () => setShowSolutions(true),
|
|
@@ -1696,12 +1995,23 @@ function FillInTheBlanksInner(props, ref) {
|
|
|
1696
1995
|
response: values,
|
|
1697
1996
|
correct: passedThreshold,
|
|
1698
1997
|
score,
|
|
1699
|
-
maxScore:
|
|
1700
|
-
})
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1998
|
+
maxScore: maxScore || 1
|
|
1999
|
+
}),
|
|
2000
|
+
getCurrentState: () => ({ values, passed, showSolutions }),
|
|
2001
|
+
resume: (state) => {
|
|
2002
|
+
const raw = state.values;
|
|
2003
|
+
if (raw && typeof raw === "object") setValues({ ...raw });
|
|
2004
|
+
readBooleanStateField(state, "passed", (value) => {
|
|
2005
|
+
setPassed(value);
|
|
2006
|
+
completedRef.current = value;
|
|
2007
|
+
answeredRef.current = value;
|
|
2008
|
+
});
|
|
2009
|
+
readBooleanStateField(state, "showSolutions", setShowSolutions);
|
|
2010
|
+
}
|
|
2011
|
+
}),
|
|
2012
|
+
[allFilled, checkId, maxScore, passed, passedThreshold, score, showSolutions, values]
|
|
2013
|
+
);
|
|
2014
|
+
useAssessmentHandleRegistration(checkId, handle, ref);
|
|
1705
2015
|
const check = () => {
|
|
1706
2016
|
if (!hasBlanks) {
|
|
1707
2017
|
if (isDevEnvironment4()) {
|
|
@@ -1732,20 +2042,20 @@ function FillInTheBlanksInner(props, ref) {
|
|
|
1732
2042
|
});
|
|
1733
2043
|
}
|
|
1734
2044
|
};
|
|
1735
|
-
|
|
2045
|
+
useEffect7(() => {
|
|
1736
2046
|
if (!allFilled) answeredRef.current = false;
|
|
1737
2047
|
}, [allFilled]);
|
|
1738
|
-
|
|
2048
|
+
useEffect7(() => {
|
|
1739
2049
|
if (props.autoCheck && allFilled) check();
|
|
1740
2050
|
}, [allFilled, props.autoCheck, values, passedThreshold]);
|
|
1741
2051
|
const reveal = showSolutions || passed && props.enableSolutionsButton;
|
|
1742
|
-
return /* @__PURE__ */
|
|
1743
|
-
/* @__PURE__ */
|
|
2052
|
+
return /* @__PURE__ */ jsxs6("section", { "aria-label": "Fill in the Blanks", "data-lk-check-id": checkId, children: [
|
|
2053
|
+
/* @__PURE__ */ jsx8("p", { children: parsed.parts.map((part, i) => {
|
|
1744
2054
|
const blank = blanks.find((b) => b.id === part);
|
|
1745
|
-
if (!blank) return /* @__PURE__ */
|
|
1746
|
-
return /* @__PURE__ */
|
|
1747
|
-
/* @__PURE__ */
|
|
1748
|
-
/* @__PURE__ */
|
|
2055
|
+
if (!blank) return /* @__PURE__ */ jsx8(React9.Fragment, { children: part }, i);
|
|
2056
|
+
return /* @__PURE__ */ jsxs6("label", { style: { margin: "0 0.25em" }, children: [
|
|
2057
|
+
/* @__PURE__ */ jsx8("span", { className: "lk-visually-hidden", children: blank.answer }),
|
|
2058
|
+
/* @__PURE__ */ jsx8(
|
|
1749
2059
|
"input",
|
|
1750
2060
|
{
|
|
1751
2061
|
type: "text",
|
|
@@ -1761,52 +2071,40 @@ function FillInTheBlanksInner(props, ref) {
|
|
|
1761
2071
|
)
|
|
1762
2072
|
] }, blank.id);
|
|
1763
2073
|
}) }),
|
|
1764
|
-
!props.autoCheck ? /* @__PURE__ */
|
|
1765
|
-
!hasBlanks ? /* @__PURE__ */
|
|
1766
|
-
allFilled ? /* @__PURE__ */
|
|
1767
|
-
props.enableRetry && passed ? /* @__PURE__ */
|
|
1768
|
-
props.enableSolutionsButton && !reveal ? /* @__PURE__ */
|
|
2074
|
+
!props.autoCheck ? /* @__PURE__ */ jsx8("button", { type: "button", "data-testid": "check-blanks", disabled: !allFilled || passed, onClick: check, children: "Check" }) : null,
|
|
2075
|
+
!hasBlanks ? /* @__PURE__ */ jsx8("p", { role: "alert", children: "This activity has no blanks. Add text wrapped in asterisks, e.g. The *answer* here." }) : null,
|
|
2076
|
+
allFilled ? /* @__PURE__ */ jsx8("p", { role: "status", "aria-live": "polite", children: passed || passedThreshold ? "Correct" : "Try again" }) : null,
|
|
2077
|
+
props.enableRetry && passed ? /* @__PURE__ */ jsx8("button", { type: "button", onClick: reset, children: "Try again" }) : null,
|
|
2078
|
+
props.enableSolutionsButton && !reveal ? /* @__PURE__ */ jsx8("button", { type: "button", onClick: () => setShowSolutions(true), children: "Show solution" }) : null
|
|
1769
2079
|
] });
|
|
1770
2080
|
}
|
|
1771
|
-
var FillInTheBlanksInnerForwarded =
|
|
1772
|
-
var FillInTheBlanks =
|
|
2081
|
+
var FillInTheBlanksInnerForwarded = forwardRef4(FillInTheBlanksInner);
|
|
2082
|
+
var FillInTheBlanks = forwardRef4(
|
|
1773
2083
|
function FillInTheBlanks2(props, ref) {
|
|
1774
|
-
return /* @__PURE__ */
|
|
2084
|
+
return /* @__PURE__ */ jsx8(AssessmentLessonGuard, { blockLabel: "FillInTheBlanks", checkId: props.checkId, children: (lessonId) => /* @__PURE__ */ jsx8(FillInTheBlanksInnerForwarded, { ...props, enclosingLessonId: lessonId, ref }) });
|
|
1775
2085
|
}
|
|
1776
2086
|
);
|
|
1777
2087
|
|
|
1778
2088
|
// src/blocks/DragTheWords.tsx
|
|
1779
|
-
import
|
|
1780
|
-
import { jsx as
|
|
2089
|
+
import React10, { forwardRef as forwardRef5, useEffect as useEffect8, useMemo as useMemo10, useRef as useRef8, useState as useState8 } from "react";
|
|
2090
|
+
import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1781
2091
|
var INTERACTION4 = "dragTheWords";
|
|
1782
2092
|
function parseZones(template) {
|
|
1783
|
-
const parts =
|
|
1784
|
-
|
|
1785
|
-
const re = /\*([^*]+)\*/g;
|
|
1786
|
-
let last = 0;
|
|
1787
|
-
let match;
|
|
1788
|
-
let n = 0;
|
|
1789
|
-
while ((match = re.exec(template)) !== null) {
|
|
1790
|
-
parts.push(template.slice(last, match.index));
|
|
1791
|
-
answers.push(match[1].trim());
|
|
1792
|
-
parts.push(`zone-${n++}`);
|
|
1793
|
-
last = match.index + match[0].length;
|
|
1794
|
-
}
|
|
1795
|
-
parts.push(template.slice(last));
|
|
1796
|
-
return { parts, answers };
|
|
2093
|
+
const { parts, values } = parseStarDelimitedTemplate(template, "zone");
|
|
2094
|
+
return { parts, answers: values };
|
|
1797
2095
|
}
|
|
1798
2096
|
function DragTheWordsInner(props, ref) {
|
|
1799
|
-
const checkId =
|
|
2097
|
+
const checkId = useMemo10(() => normalizeComponentId(props.checkId, "checkId"), [props.checkId]);
|
|
1800
2098
|
const assessment = useAssessmentState(props.enclosingLessonId);
|
|
1801
|
-
const { parts, answers } =
|
|
1802
|
-
const [zones, setZones] =
|
|
2099
|
+
const { parts, answers } = useMemo10(() => parseZones(props.template), [props.template]);
|
|
2100
|
+
const [zones, setZones] = useState8(
|
|
1803
2101
|
() => Object.fromEntries(answers.map((_, i) => [`zone-${i}`, ""]))
|
|
1804
2102
|
);
|
|
1805
|
-
const [pool, setPool] =
|
|
1806
|
-
const [keyboardWord, setKeyboardWord] =
|
|
1807
|
-
const [passed, setPassed] =
|
|
1808
|
-
const completedRef =
|
|
1809
|
-
const answeredRef =
|
|
2103
|
+
const [pool, setPool] = useState8(() => [...props.words]);
|
|
2104
|
+
const [keyboardWord, setKeyboardWord] = useState8(null);
|
|
2105
|
+
const [passed, setPassed] = useState8(false);
|
|
2106
|
+
const completedRef = useRef8(false);
|
|
2107
|
+
const answeredRef = useRef8(false);
|
|
1810
2108
|
const reset = () => {
|
|
1811
2109
|
completedRef.current = false;
|
|
1812
2110
|
answeredRef.current = false;
|
|
@@ -1815,7 +2113,7 @@ function DragTheWordsInner(props, ref) {
|
|
|
1815
2113
|
setPool([...props.words]);
|
|
1816
2114
|
setKeyboardWord(null);
|
|
1817
2115
|
};
|
|
1818
|
-
|
|
2116
|
+
useEffect8(() => {
|
|
1819
2117
|
reset();
|
|
1820
2118
|
}, [checkId, props.template, props.words.join("\0")]);
|
|
1821
2119
|
const hasZones = answers.length > 0;
|
|
@@ -1826,11 +2124,11 @@ function DragTheWordsInner(props, ref) {
|
|
|
1826
2124
|
});
|
|
1827
2125
|
const maxScore = answers.length;
|
|
1828
2126
|
const passedThreshold = meetsPassingThreshold(score, maxScore || 1, props.passingScore);
|
|
1829
|
-
const handle =
|
|
1830
|
-
|
|
1831
|
-
|
|
2127
|
+
const handle = useMemo10(
|
|
2128
|
+
() => buildAssessmentHandle({
|
|
2129
|
+
checkId,
|
|
1832
2130
|
getScore: () => score,
|
|
1833
|
-
getMaxScore: () =>
|
|
2131
|
+
getMaxScore: () => maxScore || 1,
|
|
1834
2132
|
getAnswerGiven: () => allFilled,
|
|
1835
2133
|
resetTask: reset,
|
|
1836
2134
|
showSolutions: () => {
|
|
@@ -1841,12 +2139,25 @@ function DragTheWordsInner(props, ref) {
|
|
|
1841
2139
|
response: zones,
|
|
1842
2140
|
correct: passedThreshold,
|
|
1843
2141
|
score,
|
|
1844
|
-
maxScore:
|
|
1845
|
-
})
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
2142
|
+
maxScore: maxScore || 1
|
|
2143
|
+
}),
|
|
2144
|
+
getCurrentState: () => ({ zones, pool, passed, keyboardWord }),
|
|
2145
|
+
resume: (state) => {
|
|
2146
|
+
const rawZones = state.zones;
|
|
2147
|
+
if (rawZones && typeof rawZones === "object") setZones({ ...rawZones });
|
|
2148
|
+
if (Array.isArray(state.pool)) setPool([...state.pool]);
|
|
2149
|
+
readBooleanStateField(state, "passed", (value) => {
|
|
2150
|
+
setPassed(value);
|
|
2151
|
+
completedRef.current = value;
|
|
2152
|
+
answeredRef.current = value;
|
|
2153
|
+
});
|
|
2154
|
+
const kw = state.keyboardWord;
|
|
2155
|
+
if (kw === null || typeof kw === "string") setKeyboardWord(kw ?? null);
|
|
2156
|
+
}
|
|
2157
|
+
}),
|
|
2158
|
+
[allFilled, checkId, keyboardWord, maxScore, passed, passedThreshold, pool, score, zones]
|
|
2159
|
+
);
|
|
2160
|
+
useAssessmentHandleRegistration(checkId, handle, ref);
|
|
1850
2161
|
const placeInZone = (zoneId, word) => {
|
|
1851
2162
|
if (passed && !props.enableRetry) return;
|
|
1852
2163
|
const prev = zones[zoneId];
|
|
@@ -1896,15 +2207,15 @@ function DragTheWordsInner(props, ref) {
|
|
|
1896
2207
|
});
|
|
1897
2208
|
}
|
|
1898
2209
|
};
|
|
1899
|
-
|
|
2210
|
+
useEffect8(() => {
|
|
1900
2211
|
if (!allFilled) answeredRef.current = false;
|
|
1901
2212
|
}, [allFilled]);
|
|
1902
|
-
|
|
2213
|
+
useEffect8(() => {
|
|
1903
2214
|
if (props.autoCheck && allFilled) check();
|
|
1904
2215
|
}, [allFilled, props.autoCheck, zones, passedThreshold]);
|
|
1905
|
-
return /* @__PURE__ */
|
|
1906
|
-
/* @__PURE__ */
|
|
1907
|
-
/* @__PURE__ */
|
|
2216
|
+
return /* @__PURE__ */ jsxs7("section", { "aria-label": "Drag the Words", "data-lk-check-id": checkId, children: [
|
|
2217
|
+
/* @__PURE__ */ jsx9("p", { children: "Drag words into the blanks (or select a word, then activate a blank)." }),
|
|
2218
|
+
/* @__PURE__ */ jsx9("div", { role: "list", "aria-label": "Word bank", "data-testid": "word-bank", children: pool.map((word) => /* @__PURE__ */ jsx9(
|
|
1908
2219
|
"button",
|
|
1909
2220
|
{
|
|
1910
2221
|
type: "button",
|
|
@@ -1918,9 +2229,9 @@ function DragTheWordsInner(props, ref) {
|
|
|
1918
2229
|
},
|
|
1919
2230
|
word
|
|
1920
2231
|
)) }),
|
|
1921
|
-
/* @__PURE__ */
|
|
1922
|
-
if (!part.startsWith("zone-")) return /* @__PURE__ */
|
|
1923
|
-
return /* @__PURE__ */
|
|
2232
|
+
/* @__PURE__ */ jsx9("p", { children: parts.map((part, i) => {
|
|
2233
|
+
if (!part.startsWith("zone-")) return /* @__PURE__ */ jsx9(React10.Fragment, { children: part }, i);
|
|
2234
|
+
return /* @__PURE__ */ jsx9(
|
|
1924
2235
|
"span",
|
|
1925
2236
|
{
|
|
1926
2237
|
role: "button",
|
|
@@ -1944,203 +2255,1169 @@ function DragTheWordsInner(props, ref) {
|
|
|
1944
2255
|
part
|
|
1945
2256
|
);
|
|
1946
2257
|
}) }),
|
|
1947
|
-
/* @__PURE__ */
|
|
1948
|
-
!hasZones ? /* @__PURE__ */
|
|
1949
|
-
allFilled ? /* @__PURE__ */
|
|
2258
|
+
/* @__PURE__ */ jsx9("button", { type: "button", "data-testid": "check-drag-words", disabled: !allFilled || passed, onClick: check, children: "Check" }),
|
|
2259
|
+
!hasZones ? /* @__PURE__ */ jsx9("p", { role: "alert", children: "This activity has no drop zones. Wrap answers in asterisks in the template." }) : null,
|
|
2260
|
+
allFilled ? /* @__PURE__ */ jsx9("p", { role: "status", "aria-live": "polite", children: passed || passedThreshold ? "Correct" : "Try again" }) : null
|
|
2261
|
+
] });
|
|
2262
|
+
}
|
|
2263
|
+
var DragTheWordsInnerForwarded = forwardRef5(DragTheWordsInner);
|
|
2264
|
+
var DragTheWords = forwardRef5(function DragTheWords2(props, ref) {
|
|
2265
|
+
return /* @__PURE__ */ jsx9(AssessmentLessonGuard, { blockLabel: "DragTheWords", checkId: props.checkId, children: (lessonId) => /* @__PURE__ */ jsx9(DragTheWordsInnerForwarded, { ...props, enclosingLessonId: lessonId, ref }) });
|
|
2266
|
+
});
|
|
2267
|
+
|
|
2268
|
+
// src/blocks/DragAndDrop.tsx
|
|
2269
|
+
import { forwardRef as forwardRef6, useEffect as useEffect9, useMemo as useMemo11, useRef as useRef9, useState as useState9 } from "react";
|
|
2270
|
+
import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
2271
|
+
var INTERACTION5 = "dragAndDrop";
|
|
2272
|
+
function DragAndDropInner(props, ref) {
|
|
2273
|
+
const checkId = useMemo11(() => normalizeComponentId(props.checkId, "checkId"), [props.checkId]);
|
|
2274
|
+
const assessment = useAssessmentState(props.enclosingLessonId);
|
|
2275
|
+
const [assignments, setAssignments] = useState9(
|
|
2276
|
+
() => Object.fromEntries(props.targets.map((t) => [t.id, ""]))
|
|
2277
|
+
);
|
|
2278
|
+
const [pool, setPool] = useState9(() => props.items.map((i) => i.id));
|
|
2279
|
+
const [keyboardItem, setKeyboardItem] = useState9(null);
|
|
2280
|
+
const [passed, setPassed] = useState9(false);
|
|
2281
|
+
const completedRef = useRef9(false);
|
|
2282
|
+
const reset = () => {
|
|
2283
|
+
completedRef.current = false;
|
|
2284
|
+
setPassed(false);
|
|
2285
|
+
setAssignments(Object.fromEntries(props.targets.map((t) => [t.id, ""])));
|
|
2286
|
+
setPool(props.items.map((i) => i.id));
|
|
2287
|
+
setKeyboardItem(null);
|
|
2288
|
+
};
|
|
2289
|
+
useEffect9(() => {
|
|
2290
|
+
reset();
|
|
2291
|
+
}, [checkId, props.items.map((i) => i.id).join(","), props.targets.map((t) => t.id).join(",")]);
|
|
2292
|
+
const allFilled = props.targets.every((t) => (assignments[t.id] ?? "").length > 0);
|
|
2293
|
+
const allCorrect = props.targets.every((t) => assignments[t.id] === t.accepts);
|
|
2294
|
+
const handle = useMemo11(() => {
|
|
2295
|
+
const maxScore = props.targets.length || 1;
|
|
2296
|
+
let score = 0;
|
|
2297
|
+
props.targets.forEach((t) => {
|
|
2298
|
+
if (assignments[t.id] === t.accepts) score += 1;
|
|
2299
|
+
});
|
|
2300
|
+
return buildAssessmentHandle({
|
|
2301
|
+
checkId,
|
|
2302
|
+
getScore: () => score,
|
|
2303
|
+
getMaxScore: () => maxScore,
|
|
2304
|
+
getAnswerGiven: () => allFilled,
|
|
2305
|
+
resetTask: reset,
|
|
2306
|
+
showSolutions: () => {
|
|
2307
|
+
},
|
|
2308
|
+
getXAPIData: () => ({
|
|
2309
|
+
checkId,
|
|
2310
|
+
interactionType: INTERACTION5,
|
|
2311
|
+
response: assignments,
|
|
2312
|
+
correct: allCorrect,
|
|
2313
|
+
score,
|
|
2314
|
+
maxScore
|
|
2315
|
+
}),
|
|
2316
|
+
getCurrentState: () => ({ assignments, pool, passed, keyboardItem }),
|
|
2317
|
+
resume: (state) => {
|
|
2318
|
+
const rawAssignments = state.assignments;
|
|
2319
|
+
if (rawAssignments && typeof rawAssignments === "object") {
|
|
2320
|
+
setAssignments({ ...rawAssignments });
|
|
2321
|
+
}
|
|
2322
|
+
if (Array.isArray(state.pool)) setPool([...state.pool]);
|
|
2323
|
+
readBooleanStateField(state, "passed", (value) => {
|
|
2324
|
+
setPassed(value);
|
|
2325
|
+
completedRef.current = value;
|
|
2326
|
+
});
|
|
2327
|
+
const item = state.keyboardItem;
|
|
2328
|
+
if (item === null || typeof item === "string") setKeyboardItem(item ?? null);
|
|
2329
|
+
}
|
|
2330
|
+
});
|
|
2331
|
+
}, [allCorrect, allFilled, assignments, checkId, keyboardItem, passed, pool, props.targets]);
|
|
2332
|
+
useAssessmentHandleRegistration(checkId, handle, ref);
|
|
2333
|
+
const place = (targetId, itemId) => {
|
|
2334
|
+
if (passed && !props.enableRetry) return;
|
|
2335
|
+
const prev = assignments[targetId];
|
|
2336
|
+
setAssignments((a) => ({ ...a, [targetId]: itemId }));
|
|
2337
|
+
setPool((p) => {
|
|
2338
|
+
const next = p.filter((id) => id !== itemId);
|
|
2339
|
+
if (prev) next.push(prev);
|
|
2340
|
+
return next;
|
|
2341
|
+
});
|
|
2342
|
+
setKeyboardItem(null);
|
|
2343
|
+
};
|
|
2344
|
+
const check = () => {
|
|
2345
|
+
if (!allFilled) return;
|
|
2346
|
+
assessment.answer({
|
|
2347
|
+
checkId,
|
|
2348
|
+
interactionType: INTERACTION5,
|
|
2349
|
+
response: assignments,
|
|
2350
|
+
correct: allCorrect
|
|
2351
|
+
});
|
|
2352
|
+
if (allCorrect && !completedRef.current) {
|
|
2353
|
+
completedRef.current = true;
|
|
2354
|
+
setPassed(true);
|
|
2355
|
+
assessment.complete({
|
|
2356
|
+
checkId,
|
|
2357
|
+
interactionType: INTERACTION5,
|
|
2358
|
+
score: props.targets.length,
|
|
2359
|
+
maxScore: props.targets.length,
|
|
2360
|
+
passingScore: props.passingScore ?? props.targets.length
|
|
2361
|
+
});
|
|
2362
|
+
}
|
|
2363
|
+
};
|
|
2364
|
+
return /* @__PURE__ */ jsxs8("section", { "aria-label": "Drag and Drop", "data-lk-check-id": checkId, children: [
|
|
2365
|
+
/* @__PURE__ */ jsx10("p", { children: "Match each item to the correct target (drag or use keyboard: select item, then activate target)." }),
|
|
2366
|
+
/* @__PURE__ */ jsx10("div", { role: "list", "aria-label": "Draggable items", children: pool.map((id) => {
|
|
2367
|
+
const item = props.items.find((i) => i.id === id);
|
|
2368
|
+
return /* @__PURE__ */ jsx10(
|
|
2369
|
+
"button",
|
|
2370
|
+
{
|
|
2371
|
+
type: "button",
|
|
2372
|
+
draggable: true,
|
|
2373
|
+
"data-testid": `drag-item-${id}`,
|
|
2374
|
+
"aria-pressed": keyboardItem === id,
|
|
2375
|
+
onDragStart: (e) => e.dataTransfer.setData("text/plain", id),
|
|
2376
|
+
onClick: () => setKeyboardItem(keyboardItem === id ? null : id),
|
|
2377
|
+
style: { margin: "0.25rem" },
|
|
2378
|
+
children: item.label
|
|
2379
|
+
},
|
|
2380
|
+
id
|
|
2381
|
+
);
|
|
2382
|
+
}) }),
|
|
2383
|
+
/* @__PURE__ */ jsx10("ul", { children: props.targets.map((target) => {
|
|
2384
|
+
const assigned = assignments[target.id];
|
|
2385
|
+
const label = assigned ? props.items.find((i) => i.id === assigned)?.label ?? assigned : "Drop here";
|
|
2386
|
+
return /* @__PURE__ */ jsxs8("li", { children: [
|
|
2387
|
+
/* @__PURE__ */ jsx10("strong", { children: target.label }),
|
|
2388
|
+
" ",
|
|
2389
|
+
/* @__PURE__ */ jsx10(
|
|
2390
|
+
"span",
|
|
2391
|
+
{
|
|
2392
|
+
role: "button",
|
|
2393
|
+
tabIndex: 0,
|
|
2394
|
+
"data-testid": `drop-${target.id}`,
|
|
2395
|
+
onDragOver: (e) => e.preventDefault(),
|
|
2396
|
+
onDrop: (e) => {
|
|
2397
|
+
e.preventDefault();
|
|
2398
|
+
const id = e.dataTransfer.getData("text/plain");
|
|
2399
|
+
if (id) place(target.id, id);
|
|
2400
|
+
},
|
|
2401
|
+
onClick: () => keyboardItem && place(target.id, keyboardItem),
|
|
2402
|
+
onKeyDown: (e) => {
|
|
2403
|
+
if (e.key === "Enter" && keyboardItem) place(target.id, keyboardItem);
|
|
2404
|
+
},
|
|
2405
|
+
style: {
|
|
2406
|
+
display: "inline-block",
|
|
2407
|
+
minWidth: "8em",
|
|
2408
|
+
border: "1px dashed currentColor",
|
|
2409
|
+
padding: "0.25em"
|
|
2410
|
+
},
|
|
2411
|
+
children: label
|
|
2412
|
+
}
|
|
2413
|
+
)
|
|
2414
|
+
] }, target.id);
|
|
2415
|
+
}) }),
|
|
2416
|
+
/* @__PURE__ */ jsx10("button", { type: "button", "data-testid": "check-drag-drop", disabled: !allFilled || passed, onClick: check, children: "Check" }),
|
|
2417
|
+
allFilled ? /* @__PURE__ */ jsx10("p", { role: "status", "aria-live": "polite", children: passed || allCorrect ? "Correct" : "Try again" }) : null
|
|
2418
|
+
] });
|
|
2419
|
+
}
|
|
2420
|
+
var DragAndDropInnerForwarded = forwardRef6(DragAndDropInner);
|
|
2421
|
+
var DragAndDrop = forwardRef6(function DragAndDrop2(props, ref) {
|
|
2422
|
+
return /* @__PURE__ */ jsx10(AssessmentLessonGuard, { blockLabel: "DragAndDrop", checkId: props.checkId, children: (lessonId) => /* @__PURE__ */ jsx10(DragAndDropInnerForwarded, { ...props, enclosingLessonId: lessonId, ref }) });
|
|
2423
|
+
});
|
|
2424
|
+
|
|
2425
|
+
// src/blocks/AssessmentSequence.tsx
|
|
2426
|
+
import React14, { forwardRef as forwardRef7, useCallback as useCallback7, useEffect as useEffect12, useMemo as useMemo13, useState as useState10 } from "react";
|
|
2427
|
+
|
|
2428
|
+
// src/compound/useCompoundShell.ts
|
|
2429
|
+
import { useMemo as useMemo12 } from "react";
|
|
2430
|
+
import { clampCompoundPageIndex as clampCompoundPageIndex3 } from "@lessonkit/core";
|
|
2431
|
+
|
|
2432
|
+
// src/compound/useCompoundNavigation.ts
|
|
2433
|
+
import { useCallback as useCallback4 } from "react";
|
|
2434
|
+
function useCompoundNavigation(pageCount, index, setIndex) {
|
|
2435
|
+
const goNext = useCallback4(() => {
|
|
2436
|
+
if (pageCount < 1) return;
|
|
2437
|
+
setIndex((i) => Math.min(i + 1, pageCount - 1));
|
|
2438
|
+
}, [pageCount, setIndex]);
|
|
2439
|
+
const goPrev = useCallback4(() => {
|
|
2440
|
+
setIndex((i) => Math.max(i - 1, 0));
|
|
2441
|
+
}, [setIndex]);
|
|
2442
|
+
const clampedIndex = pageCount < 1 ? 0 : Math.min(index, pageCount - 1);
|
|
2443
|
+
return {
|
|
2444
|
+
index: clampedIndex,
|
|
2445
|
+
setIndex,
|
|
2446
|
+
goNext,
|
|
2447
|
+
goPrev,
|
|
2448
|
+
progress: { current: pageCount < 1 ? 0 : clampedIndex + 1, total: pageCount }
|
|
2449
|
+
};
|
|
2450
|
+
}
|
|
2451
|
+
|
|
2452
|
+
// src/compound/useCompoundPersistence.ts
|
|
2453
|
+
import { useCallback as useCallback6, useEffect as useEffect11, useRef as useRef11 } from "react";
|
|
2454
|
+
import {
|
|
2455
|
+
clampCompoundPageIndex as clampCompoundPageIndex2,
|
|
2456
|
+
createCompoundResumeState as createCompoundResumeState2,
|
|
2457
|
+
createSessionStoragePort as createSessionStoragePort3,
|
|
2458
|
+
loadCompoundState as loadCompoundState2
|
|
2459
|
+
} from "@lessonkit/core";
|
|
2460
|
+
|
|
2461
|
+
// src/compound/useCompoundResume.ts
|
|
2462
|
+
import { useCallback as useCallback5, useEffect as useEffect10, useRef as useRef10 } from "react";
|
|
2463
|
+
import { loadCompoundState, saveCompoundState } from "@lessonkit/core";
|
|
2464
|
+
import { createSessionStoragePort as createSessionStoragePort2 } from "@lessonkit/core";
|
|
2465
|
+
function useCompoundResume(opts) {
|
|
2466
|
+
const storageRef = useRef10(opts.storage ?? createSessionStoragePort2());
|
|
2467
|
+
const resumedRef = useRef10(false);
|
|
2468
|
+
useEffect10(() => {
|
|
2469
|
+
if (!opts.enabled || !opts.courseId || resumedRef.current) return;
|
|
2470
|
+
const saved = loadCompoundState(storageRef.current, opts.courseId, opts.compoundId);
|
|
2471
|
+
if (saved) {
|
|
2472
|
+
resumedRef.current = true;
|
|
2473
|
+
opts.onResume?.(saved);
|
|
2474
|
+
}
|
|
2475
|
+
}, [opts.enabled, opts.courseId, opts.compoundId, opts.onResume]);
|
|
2476
|
+
return useCallback5(
|
|
2477
|
+
(state) => {
|
|
2478
|
+
if (!opts.enabled || !opts.courseId) return;
|
|
2479
|
+
saveCompoundState(storageRef.current, opts.courseId, opts.compoundId, state);
|
|
2480
|
+
},
|
|
2481
|
+
[opts.enabled, opts.courseId, opts.compoundId]
|
|
2482
|
+
);
|
|
2483
|
+
}
|
|
2484
|
+
|
|
2485
|
+
// src/compound/useCompoundPersistence.ts
|
|
2486
|
+
function readCompoundInitialIndex(courseId, compoundId, pageCount, enabled, storage = createSessionStoragePort3()) {
|
|
2487
|
+
if (!enabled || !courseId || pageCount < 1) return 0;
|
|
2488
|
+
const saved = loadCompoundState2(storage, courseId, compoundId);
|
|
2489
|
+
if (!saved) return 0;
|
|
2490
|
+
return clampCompoundPageIndex2(saved.activePageIndex, pageCount);
|
|
2491
|
+
}
|
|
2492
|
+
function useCompoundPersistence(opts) {
|
|
2493
|
+
const storage = opts.storage ?? createSessionStoragePort3();
|
|
2494
|
+
const ctx = useCompoundRegistry();
|
|
2495
|
+
const handlesVersion = useCompoundHandlesVersion();
|
|
2496
|
+
const pendingChildResumeRef = useRef11(null);
|
|
2497
|
+
const loadedChildStatesRef = useRef11({});
|
|
2498
|
+
const skipSaveUntilHydratedRef = useRef11(false);
|
|
2499
|
+
const buildState = useCallback6(() => {
|
|
2500
|
+
const childStates = {
|
|
2501
|
+
...loadedChildStatesRef.current
|
|
2502
|
+
};
|
|
2503
|
+
if (ctx) {
|
|
2504
|
+
for (const [checkId, handle] of ctx.getHandles()) {
|
|
2505
|
+
if (handle.getCurrentState) {
|
|
2506
|
+
childStates[checkId] = handle.getCurrentState();
|
|
2507
|
+
delete loadedChildStatesRef.current[checkId];
|
|
2508
|
+
}
|
|
2509
|
+
}
|
|
2510
|
+
}
|
|
2511
|
+
return createCompoundResumeState2({
|
|
2512
|
+
activePageIndex: clampCompoundPageIndex2(opts.index, opts.pageCount),
|
|
2513
|
+
childStates
|
|
2514
|
+
});
|
|
2515
|
+
}, [ctx, opts.index, opts.pageCount]);
|
|
2516
|
+
const applyPendingChildResume = useCallback6(() => {
|
|
2517
|
+
const pending = pendingChildResumeRef.current;
|
|
2518
|
+
if (!pending || !ctx) return;
|
|
2519
|
+
const applied = resumeChildHandles(ctx.getHandles(), pending.childStates, { waitForHandles: true });
|
|
2520
|
+
if (!applied) return;
|
|
2521
|
+
pendingChildResumeRef.current = null;
|
|
2522
|
+
skipSaveUntilHydratedRef.current = false;
|
|
2523
|
+
}, [ctx]);
|
|
2524
|
+
const saveResume = useCompoundResume({
|
|
2525
|
+
courseId: opts.courseId,
|
|
2526
|
+
compoundId: opts.compoundId,
|
|
2527
|
+
enabled: opts.enabled,
|
|
2528
|
+
storage,
|
|
2529
|
+
onResume: (state) => {
|
|
2530
|
+
const clamped = clampCompoundPageIndex2(state.activePageIndex, opts.pageCount);
|
|
2531
|
+
loadedChildStatesRef.current = { ...state.childStates };
|
|
2532
|
+
skipSaveUntilHydratedRef.current = Object.keys(state.childStates).length > 0;
|
|
2533
|
+
opts.setIndex(clamped);
|
|
2534
|
+
pendingChildResumeRef.current = { ...state, activePageIndex: clamped };
|
|
2535
|
+
queueMicrotask(() => applyPendingChildResume());
|
|
2536
|
+
}
|
|
2537
|
+
});
|
|
2538
|
+
useEffect11(() => {
|
|
2539
|
+
if (!opts.enabled || !opts.courseId) return;
|
|
2540
|
+
if (skipSaveUntilHydratedRef.current) return;
|
|
2541
|
+
saveResume(buildState());
|
|
2542
|
+
}, [
|
|
2543
|
+
opts.enabled,
|
|
2544
|
+
opts.courseId,
|
|
2545
|
+
opts.index,
|
|
2546
|
+
opts.pageCount,
|
|
2547
|
+
handlesVersion,
|
|
2548
|
+
saveResume,
|
|
2549
|
+
buildState
|
|
2550
|
+
]);
|
|
2551
|
+
useEffect11(() => {
|
|
2552
|
+
applyPendingChildResume();
|
|
2553
|
+
}, [opts.index, handlesVersion, applyPendingChildResume]);
|
|
2554
|
+
}
|
|
2555
|
+
|
|
2556
|
+
// src/compound/useCompoundShell.ts
|
|
2557
|
+
function useCompoundShell(opts) {
|
|
2558
|
+
const ctx = useCompoundRegistry();
|
|
2559
|
+
useCompoundPersistence({
|
|
2560
|
+
courseId: opts.courseId,
|
|
2561
|
+
compoundId: opts.compoundId,
|
|
2562
|
+
pageCount: opts.pageCount,
|
|
2563
|
+
index: opts.index,
|
|
2564
|
+
setIndex: opts.setIndex,
|
|
2565
|
+
enabled: opts.persistEnabled,
|
|
2566
|
+
storage: opts.storage
|
|
2567
|
+
});
|
|
2568
|
+
const { goNext, goPrev, progress } = useCompoundNavigation(opts.pageCount, opts.index, opts.setIndex);
|
|
2569
|
+
const visibleIndex = clampCompoundPageIndex3(opts.index, opts.pageCount);
|
|
2570
|
+
useCompoundHandleRef(opts.ref, {
|
|
2571
|
+
activePageIndex: visibleIndex,
|
|
2572
|
+
setActivePageIndex: opts.setIndex,
|
|
2573
|
+
getHandles: () => ctx?.getHandles() ?? /* @__PURE__ */ new Map(),
|
|
2574
|
+
pageCount: opts.pageCount,
|
|
2575
|
+
enableSolutionsButton: opts.enableSolutionsButton
|
|
2576
|
+
});
|
|
2577
|
+
return { visibleIndex, goNext, goPrev, progress, ctx };
|
|
2578
|
+
}
|
|
2579
|
+
function useCompoundInitialIndex(opts) {
|
|
2580
|
+
return useMemo12(
|
|
2581
|
+
() => readCompoundInitialIndex(
|
|
2582
|
+
opts.courseId,
|
|
2583
|
+
opts.compoundId,
|
|
2584
|
+
opts.pageCount,
|
|
2585
|
+
opts.persistEnabled,
|
|
2586
|
+
opts.storage
|
|
2587
|
+
),
|
|
2588
|
+
[opts.courseId, opts.compoundId, opts.pageCount, opts.persistEnabled, opts.storage]
|
|
2589
|
+
);
|
|
2590
|
+
}
|
|
2591
|
+
|
|
2592
|
+
// src/compound/validateChildren.ts
|
|
2593
|
+
import React13 from "react";
|
|
2594
|
+
import {
|
|
2595
|
+
ACCORDION_FORBIDDEN_CHILD_TYPES,
|
|
2596
|
+
COMPOUND_MAX_NESTING_DEPTH,
|
|
2597
|
+
isChildTypeAllowed
|
|
2598
|
+
} from "@lessonkit/core";
|
|
2599
|
+
|
|
2600
|
+
// src/compound/blockType.ts
|
|
2601
|
+
var LESSONKIT_BLOCK_TYPE = /* @__PURE__ */ Symbol.for("lessonkit.blockType");
|
|
2602
|
+
function setLessonkitBlockType(component, blockType) {
|
|
2603
|
+
component[LESSONKIT_BLOCK_TYPE] = blockType;
|
|
2604
|
+
if (!component.displayName) {
|
|
2605
|
+
component.displayName = blockType;
|
|
2606
|
+
}
|
|
2607
|
+
return component;
|
|
2608
|
+
}
|
|
2609
|
+
function getLessonkitBlockType(component) {
|
|
2610
|
+
if (!component || typeof component !== "object" && typeof component !== "function") {
|
|
2611
|
+
return void 0;
|
|
2612
|
+
}
|
|
2613
|
+
const typed = component;
|
|
2614
|
+
return typed[LESSONKIT_BLOCK_TYPE] ?? typed.displayName;
|
|
2615
|
+
}
|
|
2616
|
+
|
|
2617
|
+
// src/compound/validateChildren.ts
|
|
2618
|
+
var warnedPairs = /* @__PURE__ */ new Set();
|
|
2619
|
+
var COMPOUND_CONTAINER_TYPES = /* @__PURE__ */ new Set([
|
|
2620
|
+
"Page",
|
|
2621
|
+
"InteractiveBook",
|
|
2622
|
+
"AssessmentSequence"
|
|
2623
|
+
]);
|
|
2624
|
+
function warnOrThrow(msg, strict) {
|
|
2625
|
+
if (strict) throw new Error(msg);
|
|
2626
|
+
if (!warnedPairs.has(msg)) {
|
|
2627
|
+
warnedPairs.add(msg);
|
|
2628
|
+
console.warn(msg);
|
|
2629
|
+
}
|
|
2630
|
+
}
|
|
2631
|
+
function validateNode(parent, node, depth, strict) {
|
|
2632
|
+
React13.Children.forEach(node, (child) => {
|
|
2633
|
+
if (!React13.isValidElement(child)) return;
|
|
2634
|
+
const blockType = getLessonkitBlockType(child.type);
|
|
2635
|
+
if (!blockType) {
|
|
2636
|
+
if (child.props && typeof child.props === "object" && "children" in child.props) {
|
|
2637
|
+
validateNode(parent, child.props.children, depth, strict);
|
|
2638
|
+
}
|
|
2639
|
+
return;
|
|
2640
|
+
}
|
|
2641
|
+
if (!isChildTypeAllowed(parent, blockType)) {
|
|
2642
|
+
const key = `${parent}:${blockType}`;
|
|
2643
|
+
if (!warnedPairs.has(key)) {
|
|
2644
|
+
warnedPairs.add(key);
|
|
2645
|
+
const msg = `[lessonkit] Block "${blockType}" is not in the allowlist for "${parent}"`;
|
|
2646
|
+
if (strict) throw new Error(msg);
|
|
2647
|
+
console.warn(msg);
|
|
2648
|
+
}
|
|
2649
|
+
}
|
|
2650
|
+
if (COMPOUND_CONTAINER_TYPES.has(blockType)) {
|
|
2651
|
+
const maxDepth = COMPOUND_MAX_NESTING_DEPTH[parent];
|
|
2652
|
+
if (depth >= maxDepth) {
|
|
2653
|
+
warnOrThrow(
|
|
2654
|
+
`[lessonkit] Block "${blockType}" exceeds max nesting depth (${maxDepth}) for "${parent}"`,
|
|
2655
|
+
strict
|
|
2656
|
+
);
|
|
2657
|
+
}
|
|
2658
|
+
const nestedParent = blockType;
|
|
2659
|
+
validateNode(nestedParent, child.props.children, depth + 1, strict);
|
|
2660
|
+
} else if (blockType === "Accordion") {
|
|
2661
|
+
const sections = child.props.sections;
|
|
2662
|
+
if (sections) validateAccordionSections(sections, strict);
|
|
2663
|
+
} else if (child.props && typeof child.props === "object" && "children" in child.props) {
|
|
2664
|
+
validateSubtreeForForbidden(
|
|
2665
|
+
child.props.children,
|
|
2666
|
+
ACCORDION_FORBIDDEN_CHILD_TYPES,
|
|
2667
|
+
strict
|
|
2668
|
+
);
|
|
2669
|
+
}
|
|
2670
|
+
});
|
|
2671
|
+
}
|
|
2672
|
+
function validateSubtreeForForbidden(node, forbidden, strict) {
|
|
2673
|
+
React13.Children.forEach(node, (child) => {
|
|
2674
|
+
if (!React13.isValidElement(child)) return;
|
|
2675
|
+
const blockType = getLessonkitBlockType(child.type);
|
|
2676
|
+
if (blockType && forbidden.includes(blockType)) {
|
|
2677
|
+
warnOrThrow(`[lessonkit] Block "${blockType}" must not nest inside Accordion`, strict);
|
|
2678
|
+
}
|
|
2679
|
+
if (blockType === "Accordion") {
|
|
2680
|
+
const sections = child.props.sections;
|
|
2681
|
+
if (sections) validateAccordionSections(sections, strict);
|
|
2682
|
+
return;
|
|
2683
|
+
}
|
|
2684
|
+
if (child.props && typeof child.props === "object" && "children" in child.props) {
|
|
2685
|
+
validateSubtreeForForbidden(
|
|
2686
|
+
child.props.children,
|
|
2687
|
+
forbidden,
|
|
2688
|
+
strict
|
|
2689
|
+
);
|
|
2690
|
+
}
|
|
2691
|
+
});
|
|
2692
|
+
}
|
|
2693
|
+
function validateAccordionSections(sections, strict) {
|
|
2694
|
+
if (!isDevEnvironment4() && !strict) return;
|
|
2695
|
+
for (const section of sections) {
|
|
2696
|
+
validateSubtreeForForbidden(section.content, ACCORDION_FORBIDDEN_CHILD_TYPES, strict);
|
|
2697
|
+
}
|
|
2698
|
+
}
|
|
2699
|
+
function validateCompoundChildren(parent, children, strict) {
|
|
2700
|
+
if (!isDevEnvironment4() && !strict) return;
|
|
2701
|
+
validateNode(parent, children, 0, strict);
|
|
2702
|
+
}
|
|
2703
|
+
|
|
2704
|
+
// src/compound/warnPersistence.ts
|
|
2705
|
+
var DEFAULT_ASSESSMENT_SEQUENCE_COMPOUND_ID = "assessment-sequence";
|
|
2706
|
+
function warnSharedCompoundStorageKey(opts) {
|
|
2707
|
+
if (!opts.persistEnabled || opts.hasExplicitBlockId || !isDevEnvironment4()) return;
|
|
2708
|
+
console.warn(
|
|
2709
|
+
`[lessonkit] <${opts.componentName}> without blockId shares one sessionStorage key when persistCompoundState is enabled; set a unique blockId per instance.`
|
|
2710
|
+
);
|
|
2711
|
+
}
|
|
2712
|
+
|
|
2713
|
+
// src/blocks/AssessmentSequence.tsx
|
|
2714
|
+
import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
2715
|
+
var AssessmentSequenceInner = forwardRef7(
|
|
2716
|
+
function AssessmentSequenceInner2(props, ref) {
|
|
2717
|
+
const { compoundId, childArray, index, setIndex, persistEnabled } = props;
|
|
2718
|
+
const sequential = props.sequential !== false;
|
|
2719
|
+
const { config } = useLessonkit();
|
|
2720
|
+
const { visibleIndex, goNext, goPrev, progress } = useCompoundShell({
|
|
2721
|
+
courseId: config.courseId,
|
|
2722
|
+
compoundId,
|
|
2723
|
+
pageCount: childArray.length,
|
|
2724
|
+
index,
|
|
2725
|
+
setIndex,
|
|
2726
|
+
persistEnabled,
|
|
2727
|
+
ref,
|
|
2728
|
+
enableSolutionsButton: props.enableSolutionsButton
|
|
2729
|
+
});
|
|
2730
|
+
validateCompoundChildren("AssessmentSequence", props.children);
|
|
2731
|
+
if (!sequential) {
|
|
2732
|
+
return /* @__PURE__ */ jsx11("section", { "aria-label": "Assessment sequence", "data-testid": "assessment-sequence", children: props.children });
|
|
2733
|
+
}
|
|
2734
|
+
return /* @__PURE__ */ jsxs9("section", { "aria-label": "Assessment sequence", "data-testid": "assessment-sequence", children: [
|
|
2735
|
+
/* @__PURE__ */ jsxs9("p", { children: [
|
|
2736
|
+
"Question ",
|
|
2737
|
+
progress.current,
|
|
2738
|
+
" of ",
|
|
2739
|
+
progress.total
|
|
2740
|
+
] }),
|
|
2741
|
+
/* @__PURE__ */ jsx11("div", { "data-testid": "assessment-sequence-step", children: childArray.map((child, i) => /* @__PURE__ */ jsx11("div", { hidden: i !== visibleIndex, children: child }, child.key ?? i)) }),
|
|
2742
|
+
/* @__PURE__ */ jsxs9("nav", { "aria-label": "Sequence navigation", children: [
|
|
2743
|
+
/* @__PURE__ */ jsx11(
|
|
2744
|
+
"button",
|
|
2745
|
+
{
|
|
2746
|
+
type: "button",
|
|
2747
|
+
"data-testid": "sequence-prev",
|
|
2748
|
+
disabled: visibleIndex === 0 || childArray.length === 0,
|
|
2749
|
+
onClick: goPrev,
|
|
2750
|
+
children: "Previous"
|
|
2751
|
+
}
|
|
2752
|
+
),
|
|
2753
|
+
/* @__PURE__ */ jsx11(
|
|
2754
|
+
"button",
|
|
2755
|
+
{
|
|
2756
|
+
type: "button",
|
|
2757
|
+
"data-testid": "sequence-next",
|
|
2758
|
+
disabled: visibleIndex >= childArray.length - 1 || childArray.length === 0,
|
|
2759
|
+
onClick: goNext,
|
|
2760
|
+
children: "Next"
|
|
2761
|
+
}
|
|
2762
|
+
)
|
|
2763
|
+
] })
|
|
2764
|
+
] });
|
|
2765
|
+
}
|
|
2766
|
+
);
|
|
2767
|
+
var AssessmentSequence = forwardRef7(
|
|
2768
|
+
function AssessmentSequence2(props, ref) {
|
|
2769
|
+
const compoundId = useMemo13(
|
|
2770
|
+
() => props.blockId ? normalizeComponentId(props.blockId, "blockId") : DEFAULT_ASSESSMENT_SEQUENCE_COMPOUND_ID,
|
|
2771
|
+
[props.blockId]
|
|
2772
|
+
);
|
|
2773
|
+
const childArray = React14.Children.toArray(props.children).filter(
|
|
2774
|
+
React14.isValidElement
|
|
2775
|
+
);
|
|
2776
|
+
const { config } = useLessonkit();
|
|
2777
|
+
const persistEnabled = config.session?.persistCompoundState !== false;
|
|
2778
|
+
useEffect12(() => {
|
|
2779
|
+
warnSharedCompoundStorageKey({
|
|
2780
|
+
persistEnabled,
|
|
2781
|
+
hasExplicitBlockId: Boolean(props.blockId),
|
|
2782
|
+
componentName: "AssessmentSequence"
|
|
2783
|
+
});
|
|
2784
|
+
}, [persistEnabled, props.blockId]);
|
|
2785
|
+
const initialIndex = useCompoundInitialIndex({
|
|
2786
|
+
courseId: config.courseId,
|
|
2787
|
+
compoundId,
|
|
2788
|
+
pageCount: childArray.length,
|
|
2789
|
+
persistEnabled
|
|
2790
|
+
});
|
|
2791
|
+
const [index, setIndex] = useState10(initialIndex);
|
|
2792
|
+
const setIndexStable = useCallback7((i) => setIndex(i), []);
|
|
2793
|
+
return /* @__PURE__ */ jsx11(CompoundProvider, { activePageIndex: index, onActivePageIndexChange: setIndexStable, children: /* @__PURE__ */ jsx11(
|
|
2794
|
+
AssessmentSequenceInner,
|
|
2795
|
+
{
|
|
2796
|
+
...props,
|
|
2797
|
+
ref,
|
|
2798
|
+
compoundId,
|
|
2799
|
+
childArray,
|
|
2800
|
+
index,
|
|
2801
|
+
setIndex,
|
|
2802
|
+
persistEnabled
|
|
2803
|
+
}
|
|
2804
|
+
) });
|
|
2805
|
+
}
|
|
2806
|
+
);
|
|
2807
|
+
setLessonkitBlockType(AssessmentSequence, "AssessmentSequence");
|
|
2808
|
+
|
|
2809
|
+
// src/blocks/Text.tsx
|
|
2810
|
+
import "react";
|
|
2811
|
+
import { jsx as jsx12 } from "react/jsx-runtime";
|
|
2812
|
+
function Text(props) {
|
|
2813
|
+
return /* @__PURE__ */ jsx12("p", { "data-lk-block-id": props.blockId, "data-testid": props.blockId ? `text-${props.blockId}` : "text", children: props.children });
|
|
2814
|
+
}
|
|
2815
|
+
setLessonkitBlockType(Text, "Text");
|
|
2816
|
+
|
|
2817
|
+
// src/blocks/Heading.tsx
|
|
2818
|
+
import { jsx as jsx13 } from "react/jsx-runtime";
|
|
2819
|
+
function Heading(props) {
|
|
2820
|
+
const Tag = `h${props.level}`;
|
|
2821
|
+
return /* @__PURE__ */ jsx13(Tag, { "data-lk-block-id": props.blockId, "data-testid": props.blockId ? `heading-${props.blockId}` : "heading", children: props.children });
|
|
2822
|
+
}
|
|
2823
|
+
setLessonkitBlockType(Heading, "Heading");
|
|
2824
|
+
|
|
2825
|
+
// src/blocks/Image.tsx
|
|
2826
|
+
import { jsx as jsx14 } from "react/jsx-runtime";
|
|
2827
|
+
function Image(props) {
|
|
2828
|
+
return /* @__PURE__ */ jsx14(
|
|
2829
|
+
"img",
|
|
2830
|
+
{
|
|
2831
|
+
src: props.src,
|
|
2832
|
+
alt: props.alt,
|
|
2833
|
+
"data-lk-block-id": props.blockId,
|
|
2834
|
+
"data-testid": props.blockId ? `image-${props.blockId}` : "image",
|
|
2835
|
+
style: { maxWidth: "100%", height: "auto" }
|
|
2836
|
+
}
|
|
2837
|
+
);
|
|
2838
|
+
}
|
|
2839
|
+
setLessonkitBlockType(Image, "Image");
|
|
2840
|
+
|
|
2841
|
+
// src/blocks/Page.tsx
|
|
2842
|
+
import { useEffect as useEffect13 } from "react";
|
|
2843
|
+
import { jsx as jsx15, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
2844
|
+
function Page(props) {
|
|
2845
|
+
validateCompoundChildren("Page", props.children);
|
|
2846
|
+
const { track } = useLessonkit();
|
|
2847
|
+
const lessonId = useEnclosingLessonId();
|
|
2848
|
+
useEffect13(() => {
|
|
2849
|
+
if (props.hidden || !lessonId) return;
|
|
2850
|
+
track(
|
|
2851
|
+
"compound_page_viewed",
|
|
2852
|
+
{
|
|
2853
|
+
blockId: props.blockId,
|
|
2854
|
+
pageIndex: props.pageIndex ?? 0,
|
|
2855
|
+
parentType: props.parentType
|
|
2856
|
+
},
|
|
2857
|
+
{ lessonId }
|
|
2858
|
+
);
|
|
2859
|
+
}, [props.hidden, props.pageIndex, props.parentType, props.blockId, lessonId, track]);
|
|
2860
|
+
return /* @__PURE__ */ jsxs10(
|
|
2861
|
+
"section",
|
|
2862
|
+
{
|
|
2863
|
+
"aria-label": props.title ?? "Page",
|
|
2864
|
+
"data-lk-block-id": props.blockId,
|
|
2865
|
+
"data-testid": `page-${props.blockId}`,
|
|
2866
|
+
hidden: props.hidden ? true : void 0,
|
|
2867
|
+
children: [
|
|
2868
|
+
props.title ? /* @__PURE__ */ jsx15("h3", { children: props.title }) : null,
|
|
2869
|
+
/* @__PURE__ */ jsx15("div", { children: props.children })
|
|
2870
|
+
]
|
|
2871
|
+
}
|
|
2872
|
+
);
|
|
2873
|
+
}
|
|
2874
|
+
setLessonkitBlockType(Page, "Page");
|
|
2875
|
+
|
|
2876
|
+
// src/blocks/InteractiveBook.tsx
|
|
2877
|
+
import React17, { forwardRef as forwardRef8, useCallback as useCallback8, useEffect as useEffect14, useMemo as useMemo14, useState as useState11 } from "react";
|
|
2878
|
+
import { jsx as jsx16, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
2879
|
+
var InteractiveBookInner = forwardRef8(
|
|
2880
|
+
function InteractiveBookInner2(props, ref) {
|
|
2881
|
+
const { blockId, pages, index, setIndex, persistEnabled } = props;
|
|
2882
|
+
validateCompoundChildren("InteractiveBook", pages);
|
|
2883
|
+
const { config, track } = useLessonkit();
|
|
2884
|
+
const lessonId = useEnclosingLessonId();
|
|
2885
|
+
const { visibleIndex, goNext, goPrev, progress, ctx } = useCompoundShell({
|
|
2886
|
+
courseId: config.courseId,
|
|
2887
|
+
compoundId: blockId,
|
|
2888
|
+
pageCount: pages.length,
|
|
2889
|
+
index,
|
|
2890
|
+
setIndex,
|
|
2891
|
+
persistEnabled,
|
|
2892
|
+
ref
|
|
2893
|
+
});
|
|
2894
|
+
const pageTitles = useMemo14(
|
|
2895
|
+
() => pages.map((page) => page.props.title),
|
|
2896
|
+
[pages]
|
|
2897
|
+
);
|
|
2898
|
+
useEffect14(() => {
|
|
2899
|
+
if (!lessonId || pages.length === 0) return;
|
|
2900
|
+
track(
|
|
2901
|
+
"book_page_viewed",
|
|
2902
|
+
{
|
|
2903
|
+
blockId,
|
|
2904
|
+
pageIndex: visibleIndex,
|
|
2905
|
+
pageTitle: pageTitles[visibleIndex]
|
|
2906
|
+
},
|
|
2907
|
+
{ lessonId }
|
|
2908
|
+
);
|
|
2909
|
+
}, [visibleIndex, blockId, lessonId, pages.length, pageTitles, track]);
|
|
2910
|
+
return /* @__PURE__ */ jsxs11("section", { "aria-label": props.title, "data-testid": "interactive-book", "data-lk-block-id": blockId, children: [
|
|
2911
|
+
/* @__PURE__ */ jsx16("h3", { children: props.title }),
|
|
2912
|
+
/* @__PURE__ */ jsxs11("p", { children: [
|
|
2913
|
+
"Page ",
|
|
2914
|
+
progress.current,
|
|
2915
|
+
" of ",
|
|
2916
|
+
progress.total
|
|
2917
|
+
] }),
|
|
2918
|
+
props.showBookScore && ctx ? /* @__PURE__ */ jsxs11("p", { "data-testid": "book-score", children: [
|
|
2919
|
+
"Score: ",
|
|
2920
|
+
Array.from(ctx.getHandles().values()).reduce((s, h) => s + h.getScore(), 0),
|
|
2921
|
+
" /",
|
|
2922
|
+
" ",
|
|
2923
|
+
Array.from(ctx.getHandles().values()).reduce((s, h) => s + h.getMaxScore(), 0)
|
|
2924
|
+
] }) : null,
|
|
2925
|
+
/* @__PURE__ */ jsx16("div", { "data-testid": "interactive-book-page", children: pages.map(
|
|
2926
|
+
(page, i) => React17.cloneElement(page, {
|
|
2927
|
+
key: page.key ?? page.props.blockId,
|
|
2928
|
+
hidden: i !== visibleIndex,
|
|
2929
|
+
pageIndex: i,
|
|
2930
|
+
parentType: "InteractiveBook"
|
|
2931
|
+
})
|
|
2932
|
+
) }),
|
|
2933
|
+
/* @__PURE__ */ jsxs11("nav", { "aria-label": "Book navigation", children: [
|
|
2934
|
+
/* @__PURE__ */ jsx16(
|
|
2935
|
+
"button",
|
|
2936
|
+
{
|
|
2937
|
+
type: "button",
|
|
2938
|
+
"data-testid": "book-prev",
|
|
2939
|
+
disabled: visibleIndex === 0 || pages.length === 0,
|
|
2940
|
+
onClick: goPrev,
|
|
2941
|
+
children: "Previous"
|
|
2942
|
+
}
|
|
2943
|
+
),
|
|
2944
|
+
/* @__PURE__ */ jsx16(
|
|
2945
|
+
"button",
|
|
2946
|
+
{
|
|
2947
|
+
type: "button",
|
|
2948
|
+
"data-testid": "book-next",
|
|
2949
|
+
disabled: visibleIndex >= pages.length - 1 || pages.length === 0,
|
|
2950
|
+
onClick: goNext,
|
|
2951
|
+
children: "Next"
|
|
2952
|
+
}
|
|
2953
|
+
)
|
|
2954
|
+
] })
|
|
2955
|
+
] });
|
|
2956
|
+
}
|
|
2957
|
+
);
|
|
2958
|
+
var InteractiveBook = forwardRef8(function InteractiveBook2(props, ref) {
|
|
2959
|
+
const blockId = useMemo14(
|
|
2960
|
+
() => normalizeComponentId(props.blockId, "blockId"),
|
|
2961
|
+
[props.blockId]
|
|
2962
|
+
);
|
|
2963
|
+
const pages = React17.Children.toArray(props.children).filter(
|
|
2964
|
+
React17.isValidElement
|
|
2965
|
+
);
|
|
2966
|
+
const { config } = useLessonkit();
|
|
2967
|
+
const persistEnabled = config.session?.persistCompoundState !== false;
|
|
2968
|
+
const initialIndex = useCompoundInitialIndex({
|
|
2969
|
+
courseId: config.courseId,
|
|
2970
|
+
compoundId: blockId,
|
|
2971
|
+
pageCount: pages.length,
|
|
2972
|
+
persistEnabled
|
|
2973
|
+
});
|
|
2974
|
+
const [index, setIndex] = useState11(initialIndex);
|
|
2975
|
+
const setIndexStable = useCallback8((i) => setIndex(i), []);
|
|
2976
|
+
return /* @__PURE__ */ jsx16(CompoundProvider, { activePageIndex: index, onActivePageIndexChange: setIndexStable, children: /* @__PURE__ */ jsx16(
|
|
2977
|
+
InteractiveBookInner,
|
|
2978
|
+
{
|
|
2979
|
+
...props,
|
|
2980
|
+
ref,
|
|
2981
|
+
blockId,
|
|
2982
|
+
pages,
|
|
2983
|
+
index,
|
|
2984
|
+
setIndex,
|
|
2985
|
+
persistEnabled
|
|
2986
|
+
}
|
|
2987
|
+
) });
|
|
2988
|
+
});
|
|
2989
|
+
setLessonkitBlockType(InteractiveBook, "InteractiveBook");
|
|
2990
|
+
|
|
2991
|
+
// src/blocks/Accordion.tsx
|
|
2992
|
+
import { useId as useId3, useState as useState12 } from "react";
|
|
2993
|
+
import { jsx as jsx17, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
2994
|
+
function Accordion(props) {
|
|
2995
|
+
if (isDevEnvironment4()) {
|
|
2996
|
+
validateAccordionSections(props.sections);
|
|
2997
|
+
}
|
|
2998
|
+
const [open, setOpen] = useState12(/* @__PURE__ */ new Set());
|
|
2999
|
+
const { track } = useLessonkit();
|
|
3000
|
+
const lessonId = useEnclosingLessonId();
|
|
3001
|
+
const baseId = useId3();
|
|
3002
|
+
const toggle = (sectionId) => {
|
|
3003
|
+
setOpen((prev) => {
|
|
3004
|
+
const next = new Set(prev);
|
|
3005
|
+
const expanded = !next.has(sectionId);
|
|
3006
|
+
if (expanded) next.add(sectionId);
|
|
3007
|
+
else next.delete(sectionId);
|
|
3008
|
+
track(
|
|
3009
|
+
"accordion_section_toggled",
|
|
3010
|
+
{ blockId: props.blockId, sectionId, expanded },
|
|
3011
|
+
lessonId ? { lessonId } : void 0
|
|
3012
|
+
);
|
|
3013
|
+
return next;
|
|
3014
|
+
});
|
|
3015
|
+
};
|
|
3016
|
+
return /* @__PURE__ */ jsx17("section", { "aria-label": "Accordion", "data-lk-block-id": props.blockId, "data-testid": "accordion", children: props.sections.map((section) => {
|
|
3017
|
+
const expanded = open.has(section.id);
|
|
3018
|
+
const panelId = `${baseId}-${section.id}`;
|
|
3019
|
+
const triggerId = `${baseId}-trigger-${section.id}`;
|
|
3020
|
+
return /* @__PURE__ */ jsxs12("div", { "data-testid": `accordion-section-${section.id}`, children: [
|
|
3021
|
+
/* @__PURE__ */ jsx17("h4", { children: /* @__PURE__ */ jsx17(
|
|
3022
|
+
"button",
|
|
3023
|
+
{
|
|
3024
|
+
id: triggerId,
|
|
3025
|
+
type: "button",
|
|
3026
|
+
"aria-expanded": expanded,
|
|
3027
|
+
"aria-controls": panelId,
|
|
3028
|
+
"data-testid": `accordion-trigger-${section.id}`,
|
|
3029
|
+
onClick: () => toggle(section.id),
|
|
3030
|
+
children: section.title
|
|
3031
|
+
}
|
|
3032
|
+
) }),
|
|
3033
|
+
expanded ? /* @__PURE__ */ jsx17("div", { id: panelId, role: "region", "aria-labelledby": triggerId, children: section.content }) : null
|
|
3034
|
+
] }, section.id);
|
|
3035
|
+
}) });
|
|
3036
|
+
}
|
|
3037
|
+
setLessonkitBlockType(Accordion, "Accordion");
|
|
3038
|
+
|
|
3039
|
+
// src/blocks/DialogCards.tsx
|
|
3040
|
+
import { useState as useState13 } from "react";
|
|
3041
|
+
import { jsx as jsx18, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
3042
|
+
function DialogCards(props) {
|
|
3043
|
+
const [index, setIndex] = useState13(0);
|
|
3044
|
+
const [flipped, setFlipped] = useState13(false);
|
|
3045
|
+
const card = props.cards[index];
|
|
3046
|
+
if (!card) return null;
|
|
3047
|
+
return /* @__PURE__ */ jsxs13("section", { "aria-label": "Dialog cards", "data-lk-block-id": props.blockId, "data-testid": "dialog-cards", children: [
|
|
3048
|
+
/* @__PURE__ */ jsxs13("p", { children: [
|
|
3049
|
+
"Card ",
|
|
3050
|
+
index + 1,
|
|
3051
|
+
" of ",
|
|
3052
|
+
props.cards.length
|
|
3053
|
+
] }),
|
|
3054
|
+
/* @__PURE__ */ jsx18(
|
|
3055
|
+
"button",
|
|
3056
|
+
{
|
|
3057
|
+
type: "button",
|
|
3058
|
+
"data-testid": "dialog-card-flip",
|
|
3059
|
+
"aria-pressed": flipped,
|
|
3060
|
+
onClick: () => setFlipped((f) => !f),
|
|
3061
|
+
style: { minHeight: "6rem", width: "100%" },
|
|
3062
|
+
children: flipped ? card.back : card.front
|
|
3063
|
+
}
|
|
3064
|
+
),
|
|
3065
|
+
/* @__PURE__ */ jsxs13("nav", { "aria-label": "Card navigation", children: [
|
|
3066
|
+
/* @__PURE__ */ jsx18(
|
|
3067
|
+
"button",
|
|
3068
|
+
{
|
|
3069
|
+
type: "button",
|
|
3070
|
+
"data-testid": "dialog-prev",
|
|
3071
|
+
disabled: index === 0,
|
|
3072
|
+
onClick: () => {
|
|
3073
|
+
setIndex((i) => i - 1);
|
|
3074
|
+
setFlipped(false);
|
|
3075
|
+
},
|
|
3076
|
+
children: "Previous"
|
|
3077
|
+
}
|
|
3078
|
+
),
|
|
3079
|
+
/* @__PURE__ */ jsx18(
|
|
3080
|
+
"button",
|
|
3081
|
+
{
|
|
3082
|
+
type: "button",
|
|
3083
|
+
"data-testid": "dialog-next",
|
|
3084
|
+
disabled: index >= props.cards.length - 1,
|
|
3085
|
+
onClick: () => {
|
|
3086
|
+
setIndex((i) => i + 1);
|
|
3087
|
+
setFlipped(false);
|
|
3088
|
+
},
|
|
3089
|
+
children: "Next"
|
|
3090
|
+
}
|
|
3091
|
+
)
|
|
3092
|
+
] })
|
|
3093
|
+
] });
|
|
3094
|
+
}
|
|
3095
|
+
setLessonkitBlockType(DialogCards, "DialogCards");
|
|
3096
|
+
|
|
3097
|
+
// src/blocks/Flashcards.tsx
|
|
3098
|
+
import { useState as useState14 } from "react";
|
|
3099
|
+
import { jsx as jsx19, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
3100
|
+
function Flashcards(props) {
|
|
3101
|
+
const [index, setIndex] = useState14(0);
|
|
3102
|
+
const [face, setFace] = useState14("front");
|
|
3103
|
+
const { track } = useLessonkit();
|
|
3104
|
+
const lessonId = useEnclosingLessonId();
|
|
3105
|
+
const card = props.cards[index];
|
|
3106
|
+
if (!card) return null;
|
|
3107
|
+
const flip = () => {
|
|
3108
|
+
const next = face === "front" ? "back" : "front";
|
|
3109
|
+
setFace(next);
|
|
3110
|
+
track(
|
|
3111
|
+
"flashcard_flipped",
|
|
3112
|
+
{ blockId: props.blockId, cardIndex: index, face: next },
|
|
3113
|
+
lessonId ? { lessonId } : void 0
|
|
3114
|
+
);
|
|
3115
|
+
};
|
|
3116
|
+
return /* @__PURE__ */ jsxs14("section", { "aria-label": "Flashcards", "data-lk-block-id": props.blockId, "data-testid": "flashcards", children: [
|
|
3117
|
+
/* @__PURE__ */ jsx19("button", { type: "button", "data-testid": "flashcard-flip", onClick: flip, style: { minHeight: "6rem", width: "100%" }, children: face === "front" ? card.front : card.back }),
|
|
3118
|
+
props.selfScore ? /* @__PURE__ */ jsx19("p", { "data-testid": "flashcard-self-score", children: "Self-score mode enabled" }) : null,
|
|
3119
|
+
/* @__PURE__ */ jsx19(
|
|
3120
|
+
"button",
|
|
3121
|
+
{
|
|
3122
|
+
type: "button",
|
|
3123
|
+
"data-testid": "flashcard-next",
|
|
3124
|
+
disabled: index >= props.cards.length - 1,
|
|
3125
|
+
onClick: () => {
|
|
3126
|
+
setIndex((i) => i + 1);
|
|
3127
|
+
setFace("front");
|
|
3128
|
+
},
|
|
3129
|
+
children: "Next card"
|
|
3130
|
+
}
|
|
3131
|
+
)
|
|
3132
|
+
] });
|
|
3133
|
+
}
|
|
3134
|
+
setLessonkitBlockType(Flashcards, "Flashcards");
|
|
3135
|
+
|
|
3136
|
+
// src/blocks/ImageHotspots.tsx
|
|
3137
|
+
import { useState as useState15 } from "react";
|
|
3138
|
+
import { jsx as jsx20, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
3139
|
+
function ImageHotspots(props) {
|
|
3140
|
+
const [active, setActive] = useState15(null);
|
|
3141
|
+
const { track } = useLessonkit();
|
|
3142
|
+
const lessonId = useEnclosingLessonId();
|
|
3143
|
+
const open = (hotspotId) => {
|
|
3144
|
+
setActive(hotspotId);
|
|
3145
|
+
track(
|
|
3146
|
+
"hotspot_opened",
|
|
3147
|
+
{ blockId: props.blockId, hotspotId },
|
|
3148
|
+
lessonId ? { lessonId } : void 0
|
|
3149
|
+
);
|
|
3150
|
+
};
|
|
3151
|
+
return /* @__PURE__ */ jsxs15("section", { "aria-label": "Image hotspots", "data-lk-block-id": props.blockId, "data-testid": "image-hotspots", children: [
|
|
3152
|
+
/* @__PURE__ */ jsxs15("div", { style: { position: "relative", display: "inline-block" }, children: [
|
|
3153
|
+
/* @__PURE__ */ jsx20("img", { src: props.src, alt: props.alt, style: { maxWidth: "100%" } }),
|
|
3154
|
+
props.hotspots.map((h) => /* @__PURE__ */ jsx20(
|
|
3155
|
+
"button",
|
|
3156
|
+
{
|
|
3157
|
+
type: "button",
|
|
3158
|
+
"aria-expanded": active === h.id,
|
|
3159
|
+
"aria-label": h.label,
|
|
3160
|
+
"data-testid": `hotspot-${h.id}`,
|
|
3161
|
+
style: {
|
|
3162
|
+
position: "absolute",
|
|
3163
|
+
left: `${h.x}%`,
|
|
3164
|
+
top: `${h.y}%`,
|
|
3165
|
+
transform: "translate(-50%, -50%)"
|
|
3166
|
+
},
|
|
3167
|
+
onClick: () => open(h.id),
|
|
3168
|
+
children: "+"
|
|
3169
|
+
},
|
|
3170
|
+
h.id
|
|
3171
|
+
))
|
|
3172
|
+
] }),
|
|
3173
|
+
active ? /* @__PURE__ */ jsxs15("div", { role: "dialog", "aria-label": "Hotspot details", "data-testid": "hotspot-popover", children: [
|
|
3174
|
+
props.hotspots.find((h) => h.id === active)?.content,
|
|
3175
|
+
/* @__PURE__ */ jsx20("button", { type: "button", onClick: () => setActive(null), children: "Close" })
|
|
3176
|
+
] }) : null
|
|
3177
|
+
] });
|
|
3178
|
+
}
|
|
3179
|
+
setLessonkitBlockType(ImageHotspots, "ImageHotspots");
|
|
3180
|
+
|
|
3181
|
+
// src/blocks/ImageSlider.tsx
|
|
3182
|
+
import { useState as useState16 } from "react";
|
|
3183
|
+
import { jsx as jsx21, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
3184
|
+
function ImageSlider(props) {
|
|
3185
|
+
const [index, setIndex] = useState16(0);
|
|
3186
|
+
const { track } = useLessonkit();
|
|
3187
|
+
const lessonId = useEnclosingLessonId();
|
|
3188
|
+
const slide = props.slides[index];
|
|
3189
|
+
if (!slide) return null;
|
|
3190
|
+
const goTo = (next) => {
|
|
3191
|
+
setIndex(next);
|
|
3192
|
+
track(
|
|
3193
|
+
"image_slider_changed",
|
|
3194
|
+
{ blockId: props.blockId, slideIndex: next },
|
|
3195
|
+
lessonId ? { lessonId } : void 0
|
|
3196
|
+
);
|
|
3197
|
+
};
|
|
3198
|
+
return /* @__PURE__ */ jsxs16("section", { "aria-label": "Image slider", "data-lk-block-id": props.blockId, "data-testid": "image-slider", children: [
|
|
3199
|
+
/* @__PURE__ */ jsx21("img", { src: slide.src, alt: slide.alt, style: { maxWidth: "100%" } }),
|
|
3200
|
+
slide.caption ? /* @__PURE__ */ jsx21("p", { children: slide.caption }) : null,
|
|
3201
|
+
/* @__PURE__ */ jsxs16("nav", { "aria-label": "Slide navigation", children: [
|
|
3202
|
+
/* @__PURE__ */ jsx21(
|
|
3203
|
+
"button",
|
|
3204
|
+
{
|
|
3205
|
+
type: "button",
|
|
3206
|
+
"data-testid": "slider-prev",
|
|
3207
|
+
disabled: index === 0,
|
|
3208
|
+
onClick: () => goTo(index - 1),
|
|
3209
|
+
children: "Previous"
|
|
3210
|
+
}
|
|
3211
|
+
),
|
|
3212
|
+
/* @__PURE__ */ jsxs16("span", { children: [
|
|
3213
|
+
index + 1,
|
|
3214
|
+
" / ",
|
|
3215
|
+
props.slides.length
|
|
3216
|
+
] }),
|
|
3217
|
+
/* @__PURE__ */ jsx21(
|
|
3218
|
+
"button",
|
|
3219
|
+
{
|
|
3220
|
+
type: "button",
|
|
3221
|
+
"data-testid": "slider-next",
|
|
3222
|
+
disabled: index >= props.slides.length - 1,
|
|
3223
|
+
onClick: () => goTo(index + 1),
|
|
3224
|
+
children: "Next"
|
|
3225
|
+
}
|
|
3226
|
+
)
|
|
3227
|
+
] })
|
|
1950
3228
|
] });
|
|
1951
3229
|
}
|
|
1952
|
-
|
|
1953
|
-
var DragTheWords = forwardRef4(function DragTheWords2(props, ref) {
|
|
1954
|
-
return /* @__PURE__ */ jsx8(AssessmentLessonGuard, { blockLabel: "DragTheWords", checkId: props.checkId, children: (lessonId) => /* @__PURE__ */ jsx8(DragTheWordsInnerForwarded, { ...props, enclosingLessonId: lessonId, ref }) });
|
|
1955
|
-
});
|
|
3230
|
+
setLessonkitBlockType(ImageSlider, "ImageSlider");
|
|
1956
3231
|
|
|
1957
|
-
// src/blocks/
|
|
1958
|
-
import { forwardRef as
|
|
1959
|
-
import { jsx as
|
|
1960
|
-
var
|
|
1961
|
-
function
|
|
1962
|
-
const checkId =
|
|
3232
|
+
// src/blocks/FindHotspot.tsx
|
|
3233
|
+
import { forwardRef as forwardRef9, useMemo as useMemo15, useState as useState17 } from "react";
|
|
3234
|
+
import { jsx as jsx22, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
3235
|
+
var INTERACTION6 = "findHotspot";
|
|
3236
|
+
function FindHotspotInner(props, ref) {
|
|
3237
|
+
const checkId = useMemo15(() => normalizeComponentId(props.checkId, "checkId"), [props.checkId]);
|
|
3238
|
+
const [selected, setSelected] = useState17(null);
|
|
3239
|
+
const [checked, setChecked] = useState17(false);
|
|
1963
3240
|
const assessment = useAssessmentState(props.enclosingLessonId);
|
|
1964
|
-
const
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
setAssignments(Object.fromEntries(props.targets.map((t) => [t.id, ""])));
|
|
1975
|
-
setPool(props.items.map((i) => i.id));
|
|
1976
|
-
setKeyboardItem(null);
|
|
1977
|
-
};
|
|
1978
|
-
useEffect8(() => {
|
|
1979
|
-
reset();
|
|
1980
|
-
}, [checkId, props.items.map((i) => i.id).join(","), props.targets.map((t) => t.id).join(",")]);
|
|
1981
|
-
const allFilled = props.targets.every((t) => (assignments[t.id] ?? "").length > 0);
|
|
1982
|
-
const allCorrect = props.targets.every((t) => assignments[t.id] === t.accepts);
|
|
1983
|
-
const handle = useMemo10(() => {
|
|
1984
|
-
const maxScore = props.targets.length || 1;
|
|
1985
|
-
let score = 0;
|
|
1986
|
-
props.targets.forEach((t) => {
|
|
1987
|
-
if (assignments[t.id] === t.accepts) score += 1;
|
|
1988
|
-
});
|
|
1989
|
-
return {
|
|
1990
|
-
getScore: () => score,
|
|
1991
|
-
getMaxScore: () => maxScore,
|
|
1992
|
-
getAnswerGiven: () => allFilled,
|
|
1993
|
-
resetTask: reset,
|
|
1994
|
-
showSolutions: () => {
|
|
3241
|
+
const correct = selected === props.correctTargetId;
|
|
3242
|
+
const handle = useMemo15(
|
|
3243
|
+
() => buildAssessmentHandle({
|
|
3244
|
+
checkId,
|
|
3245
|
+
getScore: () => checked && correct ? 1 : 0,
|
|
3246
|
+
getMaxScore: () => 1,
|
|
3247
|
+
getAnswerGiven: () => selected !== null,
|
|
3248
|
+
resetTask: () => {
|
|
3249
|
+
setSelected(null);
|
|
3250
|
+
setChecked(false);
|
|
1995
3251
|
},
|
|
3252
|
+
showSolutions: () => setSelected(props.correctTargetId),
|
|
1996
3253
|
getXAPIData: () => ({
|
|
1997
3254
|
checkId,
|
|
1998
|
-
interactionType:
|
|
1999
|
-
response:
|
|
2000
|
-
correct:
|
|
2001
|
-
score,
|
|
2002
|
-
maxScore
|
|
2003
|
-
})
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
setKeyboardItem(null);
|
|
2018
|
-
};
|
|
2019
|
-
const check = () => {
|
|
2020
|
-
if (!allFilled) return;
|
|
3255
|
+
interactionType: INTERACTION6,
|
|
3256
|
+
response: selected ?? void 0,
|
|
3257
|
+
correct: checked ? correct : void 0,
|
|
3258
|
+
score: checked && correct ? 1 : 0,
|
|
3259
|
+
maxScore: 1
|
|
3260
|
+
}),
|
|
3261
|
+
getCurrentState: () => ({ selected, checked }),
|
|
3262
|
+
resume: (state) => {
|
|
3263
|
+
const nextSelected = readStringField(state, "selected");
|
|
3264
|
+
if (typeof nextSelected === "string") setSelected(nextSelected);
|
|
3265
|
+
readBooleanStateField(state, "checked", setChecked);
|
|
3266
|
+
}
|
|
3267
|
+
}),
|
|
3268
|
+
[checkId, selected, checked, correct, props.correctTargetId]
|
|
3269
|
+
);
|
|
3270
|
+
useAssessmentHandleRegistration(checkId, handle, ref);
|
|
3271
|
+
const submit = () => {
|
|
3272
|
+
if (!selected) return;
|
|
3273
|
+
setChecked(true);
|
|
2021
3274
|
assessment.answer({
|
|
2022
3275
|
checkId,
|
|
2023
|
-
interactionType:
|
|
2024
|
-
response:
|
|
2025
|
-
correct
|
|
3276
|
+
interactionType: INTERACTION6,
|
|
3277
|
+
response: selected,
|
|
3278
|
+
correct
|
|
2026
3279
|
});
|
|
2027
|
-
if (
|
|
2028
|
-
completedRef.current = true;
|
|
2029
|
-
setPassed(true);
|
|
3280
|
+
if (correct) {
|
|
2030
3281
|
assessment.complete({
|
|
2031
3282
|
checkId,
|
|
2032
|
-
interactionType:
|
|
2033
|
-
score:
|
|
2034
|
-
maxScore:
|
|
2035
|
-
passingScore: props.passingScore
|
|
3283
|
+
interactionType: INTERACTION6,
|
|
3284
|
+
score: 1,
|
|
3285
|
+
maxScore: 1,
|
|
3286
|
+
passingScore: props.passingScore
|
|
2036
3287
|
});
|
|
2037
3288
|
}
|
|
2038
3289
|
};
|
|
2039
|
-
return /* @__PURE__ */
|
|
2040
|
-
/* @__PURE__ */
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
return /* @__PURE__ */ jsx9(
|
|
3290
|
+
return /* @__PURE__ */ jsxs17("section", { "aria-label": "Find the hotspot", "data-lk-check-id": checkId, "data-testid": "find-hotspot", children: [
|
|
3291
|
+
/* @__PURE__ */ jsxs17("div", { style: { position: "relative", display: "inline-block" }, children: [
|
|
3292
|
+
/* @__PURE__ */ jsx22("img", { src: props.src, alt: props.alt, style: { maxWidth: "100%" } }),
|
|
3293
|
+
props.targets.map((t) => /* @__PURE__ */ jsx22(
|
|
2044
3294
|
"button",
|
|
2045
3295
|
{
|
|
2046
3296
|
type: "button",
|
|
2047
|
-
|
|
2048
|
-
"
|
|
2049
|
-
"
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
3297
|
+
"aria-label": t.label,
|
|
3298
|
+
"aria-pressed": selected === t.id,
|
|
3299
|
+
"data-testid": `target-${t.id}`,
|
|
3300
|
+
style: {
|
|
3301
|
+
position: "absolute",
|
|
3302
|
+
left: `${t.x}%`,
|
|
3303
|
+
top: `${t.y}%`,
|
|
3304
|
+
transform: "translate(-50%, -50%)"
|
|
3305
|
+
},
|
|
3306
|
+
onClick: () => setSelected(t.id),
|
|
3307
|
+
children: t.label
|
|
2054
3308
|
},
|
|
2055
|
-
id
|
|
2056
|
-
)
|
|
2057
|
-
|
|
2058
|
-
/* @__PURE__ */
|
|
2059
|
-
|
|
2060
|
-
const label = assigned ? props.items.find((i) => i.id === assigned)?.label ?? assigned : "Drop here";
|
|
2061
|
-
return /* @__PURE__ */ jsxs7("li", { children: [
|
|
2062
|
-
/* @__PURE__ */ jsx9("strong", { children: target.label }),
|
|
2063
|
-
" ",
|
|
2064
|
-
/* @__PURE__ */ jsx9(
|
|
2065
|
-
"span",
|
|
2066
|
-
{
|
|
2067
|
-
role: "button",
|
|
2068
|
-
tabIndex: 0,
|
|
2069
|
-
"data-testid": `drop-${target.id}`,
|
|
2070
|
-
onDragOver: (e) => e.preventDefault(),
|
|
2071
|
-
onDrop: (e) => {
|
|
2072
|
-
e.preventDefault();
|
|
2073
|
-
const id = e.dataTransfer.getData("text/plain");
|
|
2074
|
-
if (id) place(target.id, id);
|
|
2075
|
-
},
|
|
2076
|
-
onClick: () => keyboardItem && place(target.id, keyboardItem),
|
|
2077
|
-
onKeyDown: (e) => {
|
|
2078
|
-
if (e.key === "Enter" && keyboardItem) place(target.id, keyboardItem);
|
|
2079
|
-
},
|
|
2080
|
-
style: {
|
|
2081
|
-
display: "inline-block",
|
|
2082
|
-
minWidth: "8em",
|
|
2083
|
-
border: "1px dashed currentColor",
|
|
2084
|
-
padding: "0.25em"
|
|
2085
|
-
},
|
|
2086
|
-
children: label
|
|
2087
|
-
}
|
|
2088
|
-
)
|
|
2089
|
-
] }, target.id);
|
|
2090
|
-
}) }),
|
|
2091
|
-
/* @__PURE__ */ jsx9("button", { type: "button", "data-testid": "check-drag-drop", disabled: !allFilled || passed, onClick: check, children: "Check" }),
|
|
2092
|
-
allFilled ? /* @__PURE__ */ jsx9("p", { role: "status", "aria-live": "polite", children: passed || allCorrect ? "Correct" : "Try again" }) : null
|
|
3309
|
+
t.id
|
|
3310
|
+
))
|
|
3311
|
+
] }),
|
|
3312
|
+
/* @__PURE__ */ jsx22("button", { type: "button", "data-testid": "check-hotspot", disabled: !selected, onClick: submit, children: "Check" }),
|
|
3313
|
+
checked ? /* @__PURE__ */ jsx22("p", { role: "status", children: correct ? "Correct" : "Try again" }) : null
|
|
2093
3314
|
] });
|
|
2094
3315
|
}
|
|
2095
|
-
var
|
|
2096
|
-
var
|
|
2097
|
-
return /* @__PURE__ */
|
|
3316
|
+
var FindHotspotInnerForwarded = forwardRef9(FindHotspotInner);
|
|
3317
|
+
var FindHotspot = forwardRef9(function FindHotspot2(props, ref) {
|
|
3318
|
+
return /* @__PURE__ */ jsx22(AssessmentLessonGuard, { blockLabel: "FindHotspot", checkId: props.checkId, children: (enclosingLessonId) => /* @__PURE__ */ jsx22(FindHotspotInnerForwarded, { ...props, enclosingLessonId, ref }) });
|
|
2098
3319
|
});
|
|
3320
|
+
setLessonkitBlockType(FindHotspot, "FindHotspot");
|
|
2099
3321
|
|
|
2100
|
-
// src/blocks/
|
|
2101
|
-
import
|
|
2102
|
-
import { jsx as
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
const
|
|
2106
|
-
const [
|
|
2107
|
-
const
|
|
2108
|
-
const
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
3322
|
+
// src/blocks/FindMultipleHotspots.tsx
|
|
3323
|
+
import { forwardRef as forwardRef10, useMemo as useMemo16, useState as useState18 } from "react";
|
|
3324
|
+
import { jsx as jsx23, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
3325
|
+
var INTERACTION7 = "findMultipleHotspots";
|
|
3326
|
+
function FindMultipleHotspotsInner(props, ref) {
|
|
3327
|
+
const checkId = useMemo16(() => normalizeComponentId(props.checkId, "checkId"), [props.checkId]);
|
|
3328
|
+
const [selected, setSelected] = useState18(/* @__PURE__ */ new Set());
|
|
3329
|
+
const [checked, setChecked] = useState18(false);
|
|
3330
|
+
const assessment = useAssessmentState(props.enclosingLessonId);
|
|
3331
|
+
const toggle = (id) => {
|
|
3332
|
+
setSelected((prev) => {
|
|
3333
|
+
const next = new Set(prev);
|
|
3334
|
+
if (next.has(id)) next.delete(id);
|
|
3335
|
+
else next.add(id);
|
|
3336
|
+
return next;
|
|
3337
|
+
});
|
|
3338
|
+
};
|
|
3339
|
+
const correct = selected.size === props.correctTargetIds.length && props.correctTargetIds.every((id) => selected.has(id));
|
|
3340
|
+
const handle = useMemo16(
|
|
3341
|
+
() => buildAssessmentHandle({
|
|
3342
|
+
checkId,
|
|
3343
|
+
getScore: () => checked && correct ? 1 : 0,
|
|
3344
|
+
getMaxScore: () => 1,
|
|
3345
|
+
getAnswerGiven: () => selected.size > 0,
|
|
3346
|
+
resetTask: () => {
|
|
3347
|
+
setSelected(/* @__PURE__ */ new Set());
|
|
3348
|
+
setChecked(false);
|
|
3349
|
+
},
|
|
3350
|
+
showSolutions: () => setSelected(new Set(props.correctTargetIds)),
|
|
3351
|
+
getXAPIData: () => ({
|
|
3352
|
+
checkId,
|
|
3353
|
+
interactionType: INTERACTION7,
|
|
3354
|
+
response: [...selected],
|
|
3355
|
+
correct: checked ? correct : void 0,
|
|
3356
|
+
score: checked && correct ? 1 : 0,
|
|
3357
|
+
maxScore: 1
|
|
3358
|
+
}),
|
|
3359
|
+
getCurrentState: () => ({ selected: [...selected], checked }),
|
|
3360
|
+
resume: (state) => {
|
|
3361
|
+
const raw = state.selected;
|
|
3362
|
+
if (Array.isArray(raw)) setSelected(new Set(raw.filter((id) => typeof id === "string")));
|
|
3363
|
+
readBooleanStateField(state, "checked", setChecked);
|
|
3364
|
+
}
|
|
3365
|
+
}),
|
|
3366
|
+
[checkId, selected, checked, correct, props.correctTargetIds]
|
|
2117
3367
|
);
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
3368
|
+
useAssessmentHandleRegistration(checkId, handle, ref);
|
|
3369
|
+
const submit = () => {
|
|
3370
|
+
if (selected.size === 0) return;
|
|
3371
|
+
setChecked(true);
|
|
3372
|
+
assessment.answer({
|
|
3373
|
+
checkId,
|
|
3374
|
+
interactionType: INTERACTION7,
|
|
3375
|
+
response: [...selected],
|
|
3376
|
+
correct
|
|
3377
|
+
});
|
|
3378
|
+
if (correct) {
|
|
3379
|
+
assessment.complete({
|
|
3380
|
+
checkId,
|
|
3381
|
+
interactionType: INTERACTION7,
|
|
3382
|
+
score: 1,
|
|
3383
|
+
maxScore: 1,
|
|
3384
|
+
passingScore: props.passingScore
|
|
3385
|
+
});
|
|
3386
|
+
}
|
|
3387
|
+
};
|
|
3388
|
+
return /* @__PURE__ */ jsxs18("section", { "aria-label": "Find multiple hotspots", "data-lk-check-id": checkId, "data-testid": "find-multiple-hotspots", children: [
|
|
3389
|
+
/* @__PURE__ */ jsxs18("div", { style: { position: "relative", display: "inline-block" }, children: [
|
|
3390
|
+
/* @__PURE__ */ jsx23("img", { src: props.src, alt: props.alt, style: { maxWidth: "100%" } }),
|
|
3391
|
+
props.targets.map((t) => /* @__PURE__ */ jsx23(
|
|
2132
3392
|
"button",
|
|
2133
3393
|
{
|
|
2134
3394
|
type: "button",
|
|
2135
|
-
"
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
3395
|
+
"aria-label": t.label,
|
|
3396
|
+
"aria-pressed": selected.has(t.id),
|
|
3397
|
+
"data-testid": `target-${t.id}`,
|
|
3398
|
+
style: {
|
|
3399
|
+
position: "absolute",
|
|
3400
|
+
left: `${t.x}%`,
|
|
3401
|
+
top: `${t.y}%`,
|
|
3402
|
+
transform: "translate(-50%, -50%)"
|
|
3403
|
+
},
|
|
3404
|
+
onClick: () => toggle(t.id),
|
|
3405
|
+
children: t.label
|
|
3406
|
+
},
|
|
3407
|
+
t.id
|
|
3408
|
+
))
|
|
3409
|
+
] }),
|
|
3410
|
+
/* @__PURE__ */ jsx23("button", { type: "button", "data-testid": "check-hotspots", disabled: selected.size === 0, onClick: submit, children: "Check" }),
|
|
3411
|
+
checked ? /* @__PURE__ */ jsx23("p", { role: "status", children: correct ? "Correct" : "Try again" }) : null
|
|
3412
|
+
] });
|
|
2143
3413
|
}
|
|
3414
|
+
var FindMultipleHotspotsInnerForwarded = forwardRef10(FindMultipleHotspotsInner);
|
|
3415
|
+
var FindMultipleHotspots = forwardRef10(
|
|
3416
|
+
function FindMultipleHotspots2(props, ref) {
|
|
3417
|
+
return /* @__PURE__ */ jsx23(AssessmentLessonGuard, { blockLabel: "FindMultipleHotspots", checkId: props.checkId, children: (enclosingLessonId) => /* @__PURE__ */ jsx23(FindMultipleHotspotsInnerForwarded, { ...props, enclosingLessonId, ref }) });
|
|
3418
|
+
}
|
|
3419
|
+
);
|
|
3420
|
+
setLessonkitBlockType(FindMultipleHotspots, "FindMultipleHotspots");
|
|
2144
3421
|
|
|
2145
3422
|
// src/index.tsx
|
|
2146
3423
|
import {
|
|
@@ -2154,14 +3431,14 @@ import {
|
|
|
2154
3431
|
} from "@lessonkit/core";
|
|
2155
3432
|
|
|
2156
3433
|
// src/theme/ThemeProvider.tsx
|
|
2157
|
-
import
|
|
3434
|
+
import React25, {
|
|
2158
3435
|
createContext as createContext4,
|
|
2159
|
-
useCallback as
|
|
3436
|
+
useCallback as useCallback9,
|
|
2160
3437
|
useContext as useContext4,
|
|
2161
3438
|
useLayoutEffect as useLayoutEffect2,
|
|
2162
|
-
useMemo as
|
|
2163
|
-
useRef as
|
|
2164
|
-
useState as
|
|
3439
|
+
useMemo as useMemo17,
|
|
3440
|
+
useRef as useRef12,
|
|
3441
|
+
useState as useState19
|
|
2165
3442
|
} from "react";
|
|
2166
3443
|
import {
|
|
2167
3444
|
brandThemeOverrides,
|
|
@@ -2188,11 +3465,11 @@ function applyCssVariables(target, vars, previousKeys) {
|
|
|
2188
3465
|
}
|
|
2189
3466
|
|
|
2190
3467
|
// src/theme/ThemeProvider.tsx
|
|
2191
|
-
import { jsx as
|
|
3468
|
+
import { jsx as jsx24 } from "react/jsx-runtime";
|
|
2192
3469
|
var ThemeContext = createContext4(null);
|
|
2193
3470
|
var useIsoLayoutEffect2 = (
|
|
2194
3471
|
/* v8 ignore next -- SSR uses useEffect when window is unavailable */
|
|
2195
|
-
typeof window !== "undefined" ? useLayoutEffect2 :
|
|
3472
|
+
typeof window !== "undefined" ? useLayoutEffect2 : React25.useEffect
|
|
2196
3473
|
);
|
|
2197
3474
|
function getSystemMode() {
|
|
2198
3475
|
if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
|
|
@@ -2211,7 +3488,7 @@ function ThemeProvider(props) {
|
|
|
2211
3488
|
const preset = props.preset ?? "default";
|
|
2212
3489
|
const mode = props.mode ?? "light";
|
|
2213
3490
|
const targetKind = props.target ?? "document";
|
|
2214
|
-
const [resolvedMode, setResolvedMode] =
|
|
3491
|
+
const [resolvedMode, setResolvedMode] = useState19(
|
|
2215
3492
|
() => mode === "system" ? getSystemMode() : mode
|
|
2216
3493
|
);
|
|
2217
3494
|
useIsoLayoutEffect2(() => {
|
|
@@ -2227,20 +3504,20 @@ function ThemeProvider(props) {
|
|
|
2227
3504
|
return () => mq.removeEventListener("change", onChange);
|
|
2228
3505
|
}, [mode]);
|
|
2229
3506
|
const dataTheme = mode === "system" ? resolvedMode : mode === "dark" ? "dark" : "light";
|
|
2230
|
-
const effectiveTheme =
|
|
3507
|
+
const effectiveTheme = useMemo17(() => {
|
|
2231
3508
|
const modeBase = resolveModeBase(mode, dataTheme);
|
|
2232
3509
|
const base = preset === "default" ? modeBase : preset === "brand" ? mergeThemes(modeBase, brandThemeOverrides) : mergeThemes(modeBase, getPresetTheme(preset));
|
|
2233
3510
|
return mergeThemes(base, props.theme ?? {});
|
|
2234
3511
|
}, [preset, mode, dataTheme, props.theme]);
|
|
2235
|
-
const hostRef =
|
|
2236
|
-
const appliedKeysRef =
|
|
3512
|
+
const hostRef = useRef12(null);
|
|
3513
|
+
const appliedKeysRef = useRef12(/* @__PURE__ */ new Set());
|
|
2237
3514
|
useIsoLayoutEffect2(() => {
|
|
2238
3515
|
if (targetKind === "document" && typeof document !== "undefined") {
|
|
2239
3516
|
document.documentElement.setAttribute("data-lk-theme", dataTheme);
|
|
2240
3517
|
return () => document.documentElement.removeAttribute("data-lk-theme");
|
|
2241
3518
|
}
|
|
2242
3519
|
}, [targetKind, dataTheme]);
|
|
2243
|
-
const inject =
|
|
3520
|
+
const inject = useCallback9(() => {
|
|
2244
3521
|
const vars = themeToCssVariables(effectiveTheme);
|
|
2245
3522
|
const el = targetKind === "document" && typeof document !== "undefined" ? document.documentElement : hostRef.current;
|
|
2246
3523
|
if (!el) return;
|
|
@@ -2257,7 +3534,7 @@ function ThemeProvider(props) {
|
|
|
2257
3534
|
appliedKeysRef.current = /* @__PURE__ */ new Set();
|
|
2258
3535
|
};
|
|
2259
3536
|
}, [inject, targetKind]);
|
|
2260
|
-
const value =
|
|
3537
|
+
const value = useMemo17(
|
|
2261
3538
|
() => ({
|
|
2262
3539
|
theme: effectiveTheme,
|
|
2263
3540
|
preset,
|
|
@@ -2267,9 +3544,9 @@ function ThemeProvider(props) {
|
|
|
2267
3544
|
[effectiveTheme, preset, mode, dataTheme]
|
|
2268
3545
|
);
|
|
2269
3546
|
if (targetKind === "document") {
|
|
2270
|
-
return /* @__PURE__ */
|
|
3547
|
+
return /* @__PURE__ */ jsx24(ThemeContext.Provider, { value, children: /* @__PURE__ */ jsx24("div", { "data-lk-theme": dataTheme, style: { display: "contents" }, children: props.children }) });
|
|
2271
3548
|
}
|
|
2272
|
-
return /* @__PURE__ */
|
|
3549
|
+
return /* @__PURE__ */ jsx24(ThemeContext.Provider, { value, children: /* @__PURE__ */ jsx24("div", { ref: hostRef, "data-lk-theme": dataTheme, children: props.children }) });
|
|
2273
3550
|
}
|
|
2274
3551
|
function useTheme() {
|
|
2275
3552
|
const ctx = useContext4(ThemeContext);
|
|
@@ -2279,9 +3556,263 @@ function useTheme() {
|
|
|
2279
3556
|
return ctx;
|
|
2280
3557
|
}
|
|
2281
3558
|
|
|
3559
|
+
// src/catalogV3Entries.ts
|
|
3560
|
+
import {
|
|
3561
|
+
ASSESSMENT_SEQUENCE_ALLOWED_CHILD_TYPES,
|
|
3562
|
+
INTERACTIVE_BOOK_ALLOWED_CHILD_TYPES,
|
|
3563
|
+
PAGE_ALLOWED_CHILD_TYPES,
|
|
3564
|
+
COMPOUND_MAX_NESTING_DEPTH as COMPOUND_MAX_NESTING_DEPTH2
|
|
3565
|
+
} from "@lessonkit/core";
|
|
3566
|
+
var COMPOUND_PARENTS = ["Lesson", "Page", "InteractiveBook", "AssessmentSequence"];
|
|
3567
|
+
function extendParents(entry) {
|
|
3568
|
+
if (!entry.parentConstraints?.length) return entry;
|
|
3569
|
+
const merged = /* @__PURE__ */ new Set([...entry.parentConstraints, ...COMPOUND_PARENTS]);
|
|
3570
|
+
return { ...entry, parentConstraints: [...merged] };
|
|
3571
|
+
}
|
|
3572
|
+
var assessmentBehaviourProps = [
|
|
3573
|
+
{ name: "enableRetry", type: "boolean", required: false, description: "Allow retry after completion." },
|
|
3574
|
+
{ name: "enableSolutionsButton", type: "boolean", required: false, description: "Show solution control." },
|
|
3575
|
+
{ name: "autoCheck", type: "boolean", required: false, description: "Check answers automatically when possible." },
|
|
3576
|
+
{ name: "passingScore", type: "number", required: false, description: "Minimum score to pass." }
|
|
3577
|
+
];
|
|
3578
|
+
var v3CompoundAndContentEntries = [
|
|
3579
|
+
{
|
|
3580
|
+
type: "Text",
|
|
3581
|
+
category: "content",
|
|
3582
|
+
description: "Paragraph text content.",
|
|
3583
|
+
props: [
|
|
3584
|
+
{ name: "blockId", type: "BlockId", required: false, description: "Stable block id." },
|
|
3585
|
+
{ name: "children", type: "ReactNode", required: true, description: "Text body." }
|
|
3586
|
+
],
|
|
3587
|
+
requiredIds: [],
|
|
3588
|
+
parentConstraints: [...COMPOUND_PARENTS],
|
|
3589
|
+
a11y: { element: "p", ariaLabel: "Text", keyboard: "N/A", notes: "Semantic paragraph." },
|
|
3590
|
+
theming: { surface: "global-inherit", stylingNotes: "Inherits theme." },
|
|
3591
|
+
telemetry: { emits: [] }
|
|
3592
|
+
},
|
|
3593
|
+
{
|
|
3594
|
+
type: "Heading",
|
|
3595
|
+
category: "content",
|
|
3596
|
+
description: "Heading levels 1\u20133.",
|
|
3597
|
+
props: [
|
|
3598
|
+
{ name: "blockId", type: "BlockId", required: false, description: "Stable block id." },
|
|
3599
|
+
{ name: "level", type: "1 | 2 | 3", required: true, description: "Heading level." },
|
|
3600
|
+
{ name: "children", type: "ReactNode", required: true, description: "Heading text." }
|
|
3601
|
+
],
|
|
3602
|
+
requiredIds: [],
|
|
3603
|
+
parentConstraints: [...COMPOUND_PARENTS],
|
|
3604
|
+
a11y: { element: "h1-h3", ariaLabel: "Heading", keyboard: "N/A", notes: "Use one level per outline." },
|
|
3605
|
+
theming: { surface: "global-inherit", stylingNotes: "Inherits theme." },
|
|
3606
|
+
telemetry: { emits: [] }
|
|
3607
|
+
},
|
|
3608
|
+
{
|
|
3609
|
+
type: "Image",
|
|
3610
|
+
category: "content",
|
|
3611
|
+
description: "Image with required alt text.",
|
|
3612
|
+
props: [
|
|
3613
|
+
{ name: "blockId", type: "BlockId", required: false, description: "Stable block id." },
|
|
3614
|
+
{ name: "src", type: "string", required: true, description: "Image URL." },
|
|
3615
|
+
{ name: "alt", type: "string", required: true, description: "Alt text." }
|
|
3616
|
+
],
|
|
3617
|
+
requiredIds: [],
|
|
3618
|
+
parentConstraints: [...COMPOUND_PARENTS],
|
|
3619
|
+
a11y: { element: "img", ariaLabel: "Image", keyboard: "N/A", notes: "Requires alt." },
|
|
3620
|
+
theming: { surface: "global-inherit", stylingNotes: "Responsive max-width." },
|
|
3621
|
+
telemetry: { emits: [] }
|
|
3622
|
+
},
|
|
3623
|
+
{
|
|
3624
|
+
type: "Page",
|
|
3625
|
+
category: "container",
|
|
3626
|
+
compoundContract: true,
|
|
3627
|
+
h5pMachineName: "H5P.Column",
|
|
3628
|
+
h5pAlias: "Column",
|
|
3629
|
+
description: "Column layout container (H5P Column / Page).",
|
|
3630
|
+
allowedChildTypes: [...PAGE_ALLOWED_CHILD_TYPES],
|
|
3631
|
+
maxNestingDepth: COMPOUND_MAX_NESTING_DEPTH2.Page,
|
|
3632
|
+
props: [
|
|
3633
|
+
{ name: "blockId", type: "BlockId", required: true, description: "Stable block id." },
|
|
3634
|
+
{ name: "title", type: "string", required: false, description: "Page title." },
|
|
3635
|
+
{ name: "children", type: "ReactNode", required: true, description: "Page content." }
|
|
3636
|
+
],
|
|
3637
|
+
requiredIds: [],
|
|
3638
|
+
optionalIds: ["blockId"],
|
|
3639
|
+
parentConstraints: ["Lesson", "InteractiveBook"],
|
|
3640
|
+
a11y: { element: "section", ariaLabel: "Page", keyboard: "N/A", notes: "H5P Column equivalent." },
|
|
3641
|
+
theming: { surface: "global-inherit", stylingNotes: "Container." },
|
|
3642
|
+
telemetry: { emits: ["compound_page_viewed"], requiresActiveLesson: true }
|
|
3643
|
+
},
|
|
3644
|
+
{
|
|
3645
|
+
type: "InteractiveBook",
|
|
3646
|
+
category: "container",
|
|
3647
|
+
compoundContract: true,
|
|
3648
|
+
h5pMachineName: "H5P.InteractiveBook",
|
|
3649
|
+
h5pAlias: "Interactive Book",
|
|
3650
|
+
description: "Multi-page book with chapter navigation.",
|
|
3651
|
+
allowedChildTypes: [...INTERACTIVE_BOOK_ALLOWED_CHILD_TYPES],
|
|
3652
|
+
maxNestingDepth: COMPOUND_MAX_NESTING_DEPTH2.InteractiveBook,
|
|
3653
|
+
props: [
|
|
3654
|
+
{ name: "blockId", type: "BlockId", required: true, description: "Stable block id." },
|
|
3655
|
+
{ name: "title", type: "string", required: true, description: "Book title." },
|
|
3656
|
+
{ name: "showBookScore", type: "boolean", required: false, description: "Show aggregate score." },
|
|
3657
|
+
{ name: "children", type: "Page[]", required: true, description: "Page chapters." }
|
|
3658
|
+
],
|
|
3659
|
+
requiredIds: ["blockId"],
|
|
3660
|
+
parentConstraints: ["Lesson"],
|
|
3661
|
+
a11y: {
|
|
3662
|
+
element: "section",
|
|
3663
|
+
ariaLabel: "Interactive book",
|
|
3664
|
+
keyboard: "Previous/Next chapter navigation.",
|
|
3665
|
+
notes: "H5P Interactive Book equivalent."
|
|
3666
|
+
},
|
|
3667
|
+
theming: { surface: "global-inherit", stylingNotes: "Book chrome." },
|
|
3668
|
+
telemetry: { emits: ["book_page_viewed"], requiresActiveLesson: true }
|
|
3669
|
+
},
|
|
3670
|
+
{
|
|
3671
|
+
type: "Accordion",
|
|
3672
|
+
category: "content",
|
|
3673
|
+
h5pMachineName: "H5P.Accordion",
|
|
3674
|
+
h5pAlias: "Accordion",
|
|
3675
|
+
description: "Expandable sections.",
|
|
3676
|
+
props: [
|
|
3677
|
+
{ name: "blockId", type: "BlockId", required: true, description: "Stable block id." },
|
|
3678
|
+
{ name: "sections", type: "AccordionSection[]", required: true, description: "Sections." }
|
|
3679
|
+
],
|
|
3680
|
+
requiredIds: ["blockId"],
|
|
3681
|
+
parentConstraints: [...COMPOUND_PARENTS],
|
|
3682
|
+
a11y: { element: "section", ariaLabel: "Accordion", keyboard: "Button toggles sections.", notes: "No nested accordions." },
|
|
3683
|
+
theming: { surface: "global-inherit", stylingNotes: "Disclosure pattern." },
|
|
3684
|
+
telemetry: { emits: ["accordion_section_toggled"] }
|
|
3685
|
+
},
|
|
3686
|
+
{
|
|
3687
|
+
type: "DialogCards",
|
|
3688
|
+
category: "content",
|
|
3689
|
+
h5pMachineName: "H5P.Dialogcards",
|
|
3690
|
+
h5pAlias: "Dialog Cards",
|
|
3691
|
+
description: "Flip cards with front/back text.",
|
|
3692
|
+
props: [
|
|
3693
|
+
{ name: "blockId", type: "BlockId", required: true, description: "Stable block id." },
|
|
3694
|
+
{ name: "cards", type: "DialogCard[]", required: true, description: "Cards." }
|
|
3695
|
+
],
|
|
3696
|
+
requiredIds: ["blockId"],
|
|
3697
|
+
parentConstraints: [...COMPOUND_PARENTS],
|
|
3698
|
+
a11y: { element: "section", ariaLabel: "Dialog cards", keyboard: "Flip and navigate cards.", notes: "Reduced motion safe." },
|
|
3699
|
+
theming: { surface: "global-inherit", stylingNotes: "Card flip." },
|
|
3700
|
+
telemetry: { emits: [] }
|
|
3701
|
+
},
|
|
3702
|
+
{
|
|
3703
|
+
type: "Flashcards",
|
|
3704
|
+
category: "content",
|
|
3705
|
+
h5pMachineName: "H5P.Flashcards",
|
|
3706
|
+
h5pAlias: "Flashcards",
|
|
3707
|
+
description: "Study flashcards with optional self-score.",
|
|
3708
|
+
props: [
|
|
3709
|
+
{ name: "blockId", type: "BlockId", required: true, description: "Stable block id." },
|
|
3710
|
+
{ name: "cards", type: "Flashcard[]", required: true, description: "Cards." },
|
|
3711
|
+
{ name: "selfScore", type: "boolean", required: false, description: "Self-score mode." }
|
|
3712
|
+
],
|
|
3713
|
+
requiredIds: ["blockId"],
|
|
3714
|
+
parentConstraints: [...COMPOUND_PARENTS],
|
|
3715
|
+
a11y: { element: "section", ariaLabel: "Flashcards", keyboard: "Flip and next.", notes: "Not LMS-scored by default." },
|
|
3716
|
+
theming: { surface: "global-inherit", stylingNotes: "Study mode." },
|
|
3717
|
+
telemetry: { emits: ["flashcard_flipped"] }
|
|
3718
|
+
},
|
|
3719
|
+
{
|
|
3720
|
+
type: "ImageHotspots",
|
|
3721
|
+
category: "content",
|
|
3722
|
+
h5pMachineName: "H5P.ImageHotspots",
|
|
3723
|
+
h5pAlias: "Image Hotspots",
|
|
3724
|
+
description: "Image with clickable hotspot popovers.",
|
|
3725
|
+
props: [
|
|
3726
|
+
{ name: "blockId", type: "BlockId", required: true, description: "Stable block id." },
|
|
3727
|
+
{ name: "src", type: "string", required: true, description: "Image URL." },
|
|
3728
|
+
{ name: "alt", type: "string", required: true, description: "Alt text." },
|
|
3729
|
+
{ name: "hotspots", type: "HotspotSpec[]", required: true, description: "Hotspots." }
|
|
3730
|
+
],
|
|
3731
|
+
requiredIds: ["blockId"],
|
|
3732
|
+
parentConstraints: [...COMPOUND_PARENTS],
|
|
3733
|
+
a11y: { element: "section", ariaLabel: "Image hotspots", keyboard: "Buttons on image.", notes: "Popover dialog." },
|
|
3734
|
+
theming: { surface: "global-inherit", stylingNotes: "Positioned hotspots." },
|
|
3735
|
+
telemetry: { emits: ["hotspot_opened"] }
|
|
3736
|
+
},
|
|
3737
|
+
{
|
|
3738
|
+
type: "ImageSlider",
|
|
3739
|
+
category: "content",
|
|
3740
|
+
h5pMachineName: "H5P.ImageSlider",
|
|
3741
|
+
h5pAlias: "Image Slider",
|
|
3742
|
+
description: "Carousel of images.",
|
|
3743
|
+
props: [
|
|
3744
|
+
{ name: "blockId", type: "BlockId", required: true, description: "Stable block id." },
|
|
3745
|
+
{ name: "slides", type: "ImageSlide[]", required: true, description: "Slides." }
|
|
3746
|
+
],
|
|
3747
|
+
requiredIds: ["blockId"],
|
|
3748
|
+
parentConstraints: [...COMPOUND_PARENTS],
|
|
3749
|
+
a11y: { element: "section", ariaLabel: "Image slider", keyboard: "Previous/next slide.", notes: "Carousel." },
|
|
3750
|
+
theming: { surface: "global-inherit", stylingNotes: "Slider." },
|
|
3751
|
+
telemetry: { emits: ["image_slider_changed"] }
|
|
3752
|
+
},
|
|
3753
|
+
{
|
|
3754
|
+
type: "FindHotspot",
|
|
3755
|
+
category: "assessment",
|
|
3756
|
+
assessmentContract: true,
|
|
3757
|
+
h5pMachineName: "H5P.ImageHotspotQuestion",
|
|
3758
|
+
h5pAlias: "Find the Hotspot",
|
|
3759
|
+
description: "Select the correct region on an image.",
|
|
3760
|
+
props: [
|
|
3761
|
+
{ name: "checkId", type: "CheckId", required: true, description: "Stable check id." },
|
|
3762
|
+
{ name: "src", type: "string", required: true, description: "Image URL." },
|
|
3763
|
+
{ name: "alt", type: "string", required: true, description: "Alt text." },
|
|
3764
|
+
{ name: "targets", type: "HotspotTarget[]", required: true, description: "Targets." },
|
|
3765
|
+
{ name: "correctTargetId", type: "string", required: true, description: "Correct target id." },
|
|
3766
|
+
...assessmentBehaviourProps
|
|
3767
|
+
],
|
|
3768
|
+
requiredIds: ["checkId"],
|
|
3769
|
+
parentConstraints: [...COMPOUND_PARENTS],
|
|
3770
|
+
a11y: { element: "section", ariaLabel: "Find the hotspot", keyboard: "Select target buttons.", notes: "Scored." },
|
|
3771
|
+
theming: { surface: "global-inherit", dataAttributes: ["data-lk-check-id"], stylingNotes: "Uses data-lk-check-id." },
|
|
3772
|
+
telemetry: { emits: ["assessment_answered", "assessment_completed"], requiresActiveLesson: true }
|
|
3773
|
+
},
|
|
3774
|
+
{
|
|
3775
|
+
type: "FindMultipleHotspots",
|
|
3776
|
+
category: "assessment",
|
|
3777
|
+
assessmentContract: true,
|
|
3778
|
+
h5pMachineName: "H5P.ImageMultipleHotspotQuestion",
|
|
3779
|
+
h5pAlias: "Find Multiple Hotspots",
|
|
3780
|
+
description: "Select all correct regions on an image.",
|
|
3781
|
+
props: [
|
|
3782
|
+
{ name: "checkId", type: "CheckId", required: true, description: "Stable check id." },
|
|
3783
|
+
{ name: "src", type: "string", required: true, description: "Image URL." },
|
|
3784
|
+
{ name: "alt", type: "string", required: true, description: "Alt text." },
|
|
3785
|
+
{ name: "targets", type: "HotspotTarget[]", required: true, description: "Targets." },
|
|
3786
|
+
{ name: "correctTargetIds", type: "string[]", required: true, description: "Correct target ids." },
|
|
3787
|
+
...assessmentBehaviourProps
|
|
3788
|
+
],
|
|
3789
|
+
requiredIds: ["checkId"],
|
|
3790
|
+
parentConstraints: [...COMPOUND_PARENTS],
|
|
3791
|
+
a11y: { element: "section", ariaLabel: "Find multiple hotspots", keyboard: "Toggle targets.", notes: "Scored." },
|
|
3792
|
+
theming: { surface: "global-inherit", dataAttributes: ["data-lk-check-id"], stylingNotes: "Uses data-lk-check-id." },
|
|
3793
|
+
telemetry: { emits: ["assessment_answered", "assessment_completed"], requiresActiveLesson: true }
|
|
3794
|
+
}
|
|
3795
|
+
];
|
|
3796
|
+
function buildV3CatalogFromV2(v2) {
|
|
3797
|
+
const patched = v2.map((entry) => {
|
|
3798
|
+
const base = extendParents(entry);
|
|
3799
|
+
if (entry.type === "AssessmentSequence") {
|
|
3800
|
+
return {
|
|
3801
|
+
...base,
|
|
3802
|
+
compoundContract: true,
|
|
3803
|
+
allowedChildTypes: [...ASSESSMENT_SEQUENCE_ALLOWED_CHILD_TYPES],
|
|
3804
|
+
maxNestingDepth: COMPOUND_MAX_NESTING_DEPTH2.AssessmentSequence
|
|
3805
|
+
};
|
|
3806
|
+
}
|
|
3807
|
+
return base;
|
|
3808
|
+
});
|
|
3809
|
+
return [...patched, ...v3CompoundAndContentEntries];
|
|
3810
|
+
}
|
|
3811
|
+
|
|
2282
3812
|
// src/blockCatalog.ts
|
|
2283
3813
|
var blockCatalogVersion = 1;
|
|
2284
3814
|
var blockCatalogV2Version = 2;
|
|
3815
|
+
var blockCatalogV3Version = 3;
|
|
2285
3816
|
var BLOCK_CATALOG = [
|
|
2286
3817
|
{
|
|
2287
3818
|
type: "Course",
|
|
@@ -2468,7 +3999,7 @@ var BLOCK_CATALOG = [
|
|
|
2468
3999
|
}
|
|
2469
4000
|
}
|
|
2470
4001
|
];
|
|
2471
|
-
var
|
|
4002
|
+
var assessmentBehaviourProps2 = [
|
|
2472
4003
|
{ name: "enableRetry", type: "boolean", required: false, description: "Allow retry after completion." },
|
|
2473
4004
|
{ name: "enableSolutionsButton", type: "boolean", required: false, description: "Show solution control." },
|
|
2474
4005
|
{ name: "autoCheck", type: "boolean", required: false, description: "Check answers automatically when possible." },
|
|
@@ -2486,7 +4017,7 @@ var v2AssessmentEntries = [
|
|
|
2486
4017
|
{ name: "checkId", type: "CheckId", required: true, description: "Stable check id." },
|
|
2487
4018
|
{ name: "question", type: "string", required: true, description: "Question text." },
|
|
2488
4019
|
{ name: "answer", type: "boolean", required: true, description: "Correct answer." },
|
|
2489
|
-
...
|
|
4020
|
+
...assessmentBehaviourProps2
|
|
2490
4021
|
],
|
|
2491
4022
|
requiredIds: ["checkId"],
|
|
2492
4023
|
parentConstraints: ["Lesson", "AssessmentSequence"],
|
|
@@ -2511,7 +4042,7 @@ var v2AssessmentEntries = [
|
|
|
2511
4042
|
{ name: "checkId", type: "CheckId", required: true, description: "Stable check id." },
|
|
2512
4043
|
{ name: "template", type: "string", required: true, description: "Text with *blank* markers." },
|
|
2513
4044
|
{ name: "blanks", type: "FillInBlankSpec[]", required: false, description: "Explicit blank specs." },
|
|
2514
|
-
...
|
|
4045
|
+
...assessmentBehaviourProps2
|
|
2515
4046
|
],
|
|
2516
4047
|
requiredIds: ["checkId"],
|
|
2517
4048
|
parentConstraints: ["Lesson", "AssessmentSequence"],
|
|
@@ -2535,7 +4066,7 @@ var v2AssessmentEntries = [
|
|
|
2535
4066
|
{ name: "checkId", type: "CheckId", required: true, description: "Stable check id." },
|
|
2536
4067
|
{ name: "items", type: "DragItem[]", required: true, description: "Draggable items." },
|
|
2537
4068
|
{ name: "targets", type: "DropTarget[]", required: true, description: "Drop targets." },
|
|
2538
|
-
...
|
|
4069
|
+
...assessmentBehaviourProps2
|
|
2539
4070
|
],
|
|
2540
4071
|
requiredIds: ["checkId"],
|
|
2541
4072
|
parentConstraints: ["Lesson", "AssessmentSequence"],
|
|
@@ -2559,7 +4090,7 @@ var v2AssessmentEntries = [
|
|
|
2559
4090
|
{ name: "checkId", type: "CheckId", required: true, description: "Stable check id." },
|
|
2560
4091
|
{ name: "template", type: "string", required: true, description: "Sentence with *blank* zones." },
|
|
2561
4092
|
{ name: "words", type: "string[]", required: true, description: "Draggable word bank." },
|
|
2562
|
-
...
|
|
4093
|
+
...assessmentBehaviourProps2
|
|
2563
4094
|
],
|
|
2564
4095
|
requiredIds: ["checkId"],
|
|
2565
4096
|
parentConstraints: ["Lesson", "AssessmentSequence"],
|
|
@@ -2583,7 +4114,7 @@ var v2AssessmentEntries = [
|
|
|
2583
4114
|
{ name: "checkId", type: "CheckId", required: true, description: "Stable check id." },
|
|
2584
4115
|
{ name: "text", type: "string", required: true, description: "Source text." },
|
|
2585
4116
|
{ name: "correctWords", type: "string[]", required: true, description: "Words to mark." },
|
|
2586
|
-
...
|
|
4117
|
+
...assessmentBehaviourProps2
|
|
2587
4118
|
],
|
|
2588
4119
|
requiredIds: ["checkId"],
|
|
2589
4120
|
parentConstraints: ["Lesson", "AssessmentSequence"],
|
|
@@ -2605,7 +4136,7 @@ var v2AssessmentEntries = [
|
|
|
2605
4136
|
props: [
|
|
2606
4137
|
{ name: "children", type: "ReactNode", required: true, description: "Assessment blocks." },
|
|
2607
4138
|
{ name: "sequential", type: "boolean", required: false, description: "One question at a time." },
|
|
2608
|
-
...
|
|
4139
|
+
...assessmentBehaviourProps2.filter((p) => p.name !== "passingScore")
|
|
2609
4140
|
],
|
|
2610
4141
|
requiredIds: [],
|
|
2611
4142
|
parentConstraints: ["Lesson"],
|
|
@@ -2623,6 +4154,7 @@ var BLOCK_CATALOG_V2 = [
|
|
|
2623
4154
|
...BLOCK_CATALOG,
|
|
2624
4155
|
...v2AssessmentEntries
|
|
2625
4156
|
];
|
|
4157
|
+
var BLOCK_CATALOG_V3 = buildV3CatalogFromV2(BLOCK_CATALOG_V2);
|
|
2626
4158
|
function cloneCatalogEntry(entry) {
|
|
2627
4159
|
return {
|
|
2628
4160
|
...entry,
|
|
@@ -2630,6 +4162,7 @@ function cloneCatalogEntry(entry) {
|
|
|
2630
4162
|
aliases: entry.aliases ? [...entry.aliases] : void 0,
|
|
2631
4163
|
optionalIds: entry.optionalIds ? [...entry.optionalIds] : void 0,
|
|
2632
4164
|
parentConstraints: entry.parentConstraints ? [...entry.parentConstraints] : void 0,
|
|
4165
|
+
allowedChildTypes: entry.allowedChildTypes ? [...entry.allowedChildTypes] : void 0,
|
|
2633
4166
|
a11y: { ...entry.a11y },
|
|
2634
4167
|
theming: {
|
|
2635
4168
|
...entry.theming,
|
|
@@ -2642,34 +4175,48 @@ function cloneCatalogEntry(entry) {
|
|
|
2642
4175
|
};
|
|
2643
4176
|
}
|
|
2644
4177
|
function buildBlockCatalog(opts) {
|
|
2645
|
-
const version = opts?.version ??
|
|
2646
|
-
const source = version === 2 ? BLOCK_CATALOG_V2 : BLOCK_CATALOG;
|
|
4178
|
+
const version = opts?.version ?? 3;
|
|
4179
|
+
const source = version === 3 ? BLOCK_CATALOG_V3 : version === 2 ? BLOCK_CATALOG_V2 : BLOCK_CATALOG;
|
|
2647
4180
|
return source.map((entry) => cloneCatalogEntry(entry));
|
|
2648
4181
|
}
|
|
2649
4182
|
function getBlockCatalogEntry(type, opts) {
|
|
2650
|
-
const version = opts?.version ??
|
|
2651
|
-
const source = version === 2 ? BLOCK_CATALOG_V2 : BLOCK_CATALOG;
|
|
4183
|
+
const version = opts?.version ?? 3;
|
|
4184
|
+
const source = version === 3 ? BLOCK_CATALOG_V3 : version === 2 ? BLOCK_CATALOG_V2 : BLOCK_CATALOG;
|
|
2652
4185
|
return source.find((entry) => entry.type === type || entry.aliases?.includes(type));
|
|
2653
4186
|
}
|
|
2654
4187
|
export {
|
|
4188
|
+
Accordion,
|
|
2655
4189
|
AssessmentSequence,
|
|
2656
4190
|
BLOCK_CATALOG,
|
|
2657
4191
|
BLOCK_CATALOG_V2,
|
|
4192
|
+
BLOCK_CATALOG_V3,
|
|
2658
4193
|
Course,
|
|
4194
|
+
DialogCards,
|
|
2659
4195
|
DragAndDrop,
|
|
2660
4196
|
DragTheWords,
|
|
2661
4197
|
FillInTheBlanks,
|
|
4198
|
+
FindHotspot,
|
|
4199
|
+
FindMultipleHotspots,
|
|
4200
|
+
Flashcards,
|
|
4201
|
+
Heading,
|
|
4202
|
+
Image,
|
|
4203
|
+
ImageHotspots,
|
|
4204
|
+
ImageSlider,
|
|
4205
|
+
InteractiveBook,
|
|
2662
4206
|
KnowledgeCheck,
|
|
2663
4207
|
Lesson,
|
|
2664
4208
|
LessonkitProvider,
|
|
2665
4209
|
MarkTheWords,
|
|
4210
|
+
Page,
|
|
2666
4211
|
ProgressTracker,
|
|
2667
4212
|
Quiz,
|
|
2668
4213
|
Reflection,
|
|
2669
4214
|
Scenario,
|
|
4215
|
+
Text,
|
|
2670
4216
|
ThemeProvider,
|
|
2671
4217
|
TrueFalse,
|
|
2672
4218
|
blockCatalogV2Version,
|
|
4219
|
+
blockCatalogV3Version,
|
|
2673
4220
|
blockCatalogVersion,
|
|
2674
4221
|
buildBlockCatalog,
|
|
2675
4222
|
buildTelemetryEvent2 as buildTelemetryEvent,
|