@conform-ed/qti-react 0.0.13 → 0.0.15
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/dist/capability.d.ts +17 -0
- package/dist/content-model.d.ts +42 -0
- package/dist/graphic.d.ts +23 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +155 -162
- package/dist/interactions/associate.d.ts +2 -0
- package/dist/interactions/choice.d.ts +2 -0
- package/dist/interactions/drawing.d.ts +2 -0
- package/dist/interactions/end-attempt.d.ts +2 -0
- package/dist/interactions/extended-text.d.ts +2 -0
- package/dist/interactions/gap-match.d.ts +2 -0
- package/dist/interactions/graphic.d.ts +13 -0
- package/dist/interactions/hottext.d.ts +2 -0
- package/dist/interactions/index.d.ts +18 -0
- package/dist/interactions/inline-choice.d.ts +2 -0
- package/dist/interactions/match.d.ts +2 -0
- package/dist/interactions/media.d.ts +2 -0
- package/dist/interactions/order.d.ts +2 -0
- package/dist/interactions/slider.d.ts +2 -0
- package/dist/interactions/text-entry.d.ts +2 -0
- package/dist/interactions/upload.d.ts +2 -0
- package/dist/normalized-item.d.ts +30 -0
- package/dist/pci/index.d.ts +6 -0
- package/dist/pci/interaction.d.ts +8 -0
- package/dist/pci/markup.d.ts +10 -0
- package/dist/pci/mount.d.ts +50 -0
- package/dist/pci/registry.d.ts +53 -0
- package/dist/pci/response.d.ts +11 -0
- package/dist/pci/skin.d.ts +12 -0
- package/dist/reference-skin/associate.d.ts +8 -0
- package/dist/reference-skin/choice.d.ts +8 -0
- package/dist/reference-skin/content.d.ts +6 -0
- package/dist/reference-skin/drawing.d.ts +9 -0
- package/dist/reference-skin/end-attempt.d.ts +7 -0
- package/dist/reference-skin/extended-text.d.ts +6 -0
- package/dist/reference-skin/gap-match.d.ts +8 -0
- package/dist/reference-skin/graphic-associate.d.ts +8 -0
- package/dist/reference-skin/graphic-base.d.ts +39 -0
- package/dist/reference-skin/graphic-gap-match.d.ts +8 -0
- package/dist/reference-skin/graphic-order.d.ts +8 -0
- package/dist/reference-skin/hotspot.d.ts +8 -0
- package/dist/reference-skin/hottext.d.ts +8 -0
- package/dist/reference-skin/index.d.ts +30 -0
- package/dist/reference-skin/inline-choice.d.ts +7 -0
- package/dist/reference-skin/match.d.ts +8 -0
- package/dist/reference-skin/media.d.ts +9 -0
- package/dist/reference-skin/order.d.ts +8 -0
- package/dist/reference-skin/position-object.d.ts +9 -0
- package/dist/reference-skin/select-point.d.ts +8 -0
- package/dist/reference-skin/slider.d.ts +8 -0
- package/dist/reference-skin/text-entry.d.ts +6 -0
- package/dist/reference-skin/upload.d.ts +8 -0
- package/dist/response-processing.d.ts +48 -0
- package/dist/rp/evaluate.d.ts +35 -0
- package/dist/rp/index.d.ts +4 -0
- package/dist/rp/interpreter.d.ts +15 -0
- package/dist/rp/template-processing.d.ts +49 -0
- package/dist/rp/templates.d.ts +8 -0
- package/dist/rp/types.d.ts +158 -0
- package/dist/rp/values.d.ts +27 -0
- package/dist/runtime.d.ts +164 -0
- package/dist/store.d.ts +61 -0
- package/dist/test/controller.d.ts +11 -0
- package/dist/test/index.d.ts +3 -0
- package/dist/test/session-store.d.ts +46 -0
- package/dist/test/types.d.ts +194 -0
- package/dist/types.d.ts +58 -0
- package/package.json +6 -6
- package/src/interactions/associate.ts +2 -2
- package/src/interactions/choice.ts +2 -2
- package/src/interactions/drawing.ts +2 -2
- package/src/interactions/end-attempt.ts +2 -2
- package/src/interactions/extended-text.ts +2 -2
- package/src/interactions/gap-match.ts +2 -2
- package/src/interactions/graphic.ts +7 -7
- package/src/interactions/hottext.ts +2 -2
- package/src/interactions/index.ts +0 -1
- package/src/interactions/inline-choice.ts +2 -2
- package/src/interactions/match.ts +2 -2
- package/src/interactions/media.ts +2 -2
- package/src/interactions/order.ts +2 -2
- package/src/interactions/slider.ts +2 -2
- package/src/interactions/text-entry.ts +2 -2
- package/src/interactions/upload.ts +2 -2
- package/src/normalized-item.ts +6 -4
- package/src/pci/interaction.ts +2 -2
- package/src/pci/mount.ts +12 -5
- package/src/pci/skin.ts +0 -1
- package/src/reference-skin/drawing.ts +25 -15
- package/src/reference-skin/index.ts +2 -3
- package/src/rp/evaluate.ts +22 -23
- package/src/rp/interpreter.ts +9 -6
- package/src/rp/template-processing.ts +8 -13
- package/src/rp/types.ts +12 -6
- package/src/rp/values.ts +19 -6
- package/src/runtime.ts +9 -7
- package/src/store.ts +14 -8
- package/src/test/controller.ts +10 -7
- package/src/test/session-store.ts +0 -1
package/dist/index.js
CHANGED
|
@@ -743,11 +743,7 @@ function fromResponse(declaration, response) {
|
|
|
743
743
|
if (raw.length === 0) {
|
|
744
744
|
return null;
|
|
745
745
|
}
|
|
746
|
-
return
|
|
747
|
-
cardinality: declaration.cardinality,
|
|
748
|
-
baseType: declaration.baseType,
|
|
749
|
-
values: raw.map((value) => coerceScalar(value, declaration.baseType))
|
|
750
|
-
};
|
|
746
|
+
return rpValue(declaration.cardinality, raw.map((value) => coerceScalar(value, declaration.baseType)), declaration.baseType);
|
|
751
747
|
}
|
|
752
748
|
function singleNumber(value) {
|
|
753
749
|
if (value === null || value.values.length !== 1) {
|
|
@@ -763,6 +759,13 @@ function singleBoolean(value) {
|
|
|
763
759
|
const member = value.values[0];
|
|
764
760
|
return typeof member === "boolean" ? member : null;
|
|
765
761
|
}
|
|
762
|
+
function rpValue(cardinality, values, baseType) {
|
|
763
|
+
return {
|
|
764
|
+
cardinality,
|
|
765
|
+
values,
|
|
766
|
+
...baseType !== undefined ? { baseType } : {}
|
|
767
|
+
};
|
|
768
|
+
}
|
|
766
769
|
function booleanValue(value) {
|
|
767
770
|
return { cardinality: "single", baseType: "boolean", values: [value] };
|
|
768
771
|
}
|
|
@@ -826,7 +829,7 @@ function fromFlatValue(value, cardinality, baseType) {
|
|
|
826
829
|
if (values.length === 0) {
|
|
827
830
|
return null;
|
|
828
831
|
}
|
|
829
|
-
return
|
|
832
|
+
return rpValue(cardinality, values.map((member) => coerceScalar(member, baseType)), baseType);
|
|
830
833
|
}
|
|
831
834
|
function toOutcomeValue(value) {
|
|
832
835
|
if (value === null || value.values.length === 0) {
|
|
@@ -979,7 +982,7 @@ function evaluateExpression(expression, env) {
|
|
|
979
982
|
case "baseValue": {
|
|
980
983
|
const baseType = expression.baseType;
|
|
981
984
|
const value = expression.value;
|
|
982
|
-
return value === undefined ? null :
|
|
985
|
+
return value === undefined ? null : rpValue("single", [coerceScalar(value, baseType)], baseType);
|
|
983
986
|
}
|
|
984
987
|
case "variable":
|
|
985
988
|
return env.lookupVariable(expression.identifier ?? "");
|
|
@@ -988,11 +991,7 @@ function evaluateExpression(expression, env) {
|
|
|
988
991
|
if (!declaration?.correctResponse) {
|
|
989
992
|
return null;
|
|
990
993
|
}
|
|
991
|
-
return
|
|
992
|
-
cardinality: declaration.cardinality,
|
|
993
|
-
baseType: declaration.baseType,
|
|
994
|
-
values: declaration.correctResponse.values.map((entry) => coerceScalar(entry.value, declaration.baseType))
|
|
995
|
-
};
|
|
994
|
+
return rpValue(declaration.cardinality, declaration.correctResponse.values.map((entry) => coerceScalar(entry.value, declaration.baseType)), declaration.baseType);
|
|
996
995
|
}
|
|
997
996
|
case "mapResponse": {
|
|
998
997
|
const identifier = expression.identifier ?? "";
|
|
@@ -1030,7 +1029,7 @@ function evaluateExpression(expression, env) {
|
|
|
1030
1029
|
const operand = expression.expressions?.[0];
|
|
1031
1030
|
const value = operand === undefined ? null : evaluate(operand);
|
|
1032
1031
|
const field = value?.fields?.find((entry) => entry.name === expression.fieldIdentifier);
|
|
1033
|
-
return field === undefined ? null :
|
|
1032
|
+
return field === undefined ? null : rpValue("single", [field.value], field.baseType);
|
|
1034
1033
|
}
|
|
1035
1034
|
case "and":
|
|
1036
1035
|
case "or": {
|
|
@@ -1119,7 +1118,7 @@ function evaluateExpression(expression, env) {
|
|
|
1119
1118
|
if (member === undefined) {
|
|
1120
1119
|
return null;
|
|
1121
1120
|
}
|
|
1122
|
-
return
|
|
1121
|
+
return rpValue("single", [member], container.baseType);
|
|
1123
1122
|
}
|
|
1124
1123
|
case "mathConstant": {
|
|
1125
1124
|
const constant = expression.name === undefined ? undefined : mathConstants[expression.name];
|
|
@@ -1270,7 +1269,7 @@ function evaluateExpression(expression, env) {
|
|
|
1270
1269
|
}
|
|
1271
1270
|
const baseType = container.baseType ?? value.baseType;
|
|
1272
1271
|
const remaining = container.values.filter((member) => !scalarsEqual(member, scalar, baseType, env.normalization));
|
|
1273
|
-
return remaining.length === 0 ? null :
|
|
1272
|
+
return remaining.length === 0 ? null : rpValue(container.cardinality, remaining, container.baseType);
|
|
1274
1273
|
}
|
|
1275
1274
|
case "repeat": {
|
|
1276
1275
|
if (typeof expression.numberRepeats !== "number") {
|
|
@@ -1291,7 +1290,7 @@ function evaluateExpression(expression, env) {
|
|
|
1291
1290
|
members.push(...value.values);
|
|
1292
1291
|
}
|
|
1293
1292
|
}
|
|
1294
|
-
return members.length === 0 ? null :
|
|
1293
|
+
return members.length === 0 ? null : rpValue("ordered", members, baseType);
|
|
1295
1294
|
}
|
|
1296
1295
|
case "stringMatch":
|
|
1297
1296
|
case "substring": {
|
|
@@ -1359,7 +1358,7 @@ function evaluateExpression(expression, env) {
|
|
|
1359
1358
|
if (members.length === 0) {
|
|
1360
1359
|
return null;
|
|
1361
1360
|
}
|
|
1362
|
-
return
|
|
1361
|
+
return rpValue(expression.kind, members, baseType);
|
|
1363
1362
|
}
|
|
1364
1363
|
case "randomInteger": {
|
|
1365
1364
|
if (!env.random) {
|
|
@@ -1411,7 +1410,7 @@ function evaluateExpression(expression, env) {
|
|
|
1411
1410
|
return null;
|
|
1412
1411
|
}
|
|
1413
1412
|
const pick = container.values[Math.floor(env.random() * container.values.length)];
|
|
1414
|
-
return
|
|
1413
|
+
return rpValue("single", [pick], container.baseType);
|
|
1415
1414
|
}
|
|
1416
1415
|
default:
|
|
1417
1416
|
throw new RpUnsupportedError(expression.kind);
|
|
@@ -1608,11 +1607,7 @@ function defaultOutcomes(declarations) {
|
|
|
1608
1607
|
const outcomes = new Map;
|
|
1609
1608
|
for (const declaration of declarations) {
|
|
1610
1609
|
if (declaration.defaultValue) {
|
|
1611
|
-
outcomes.set(declaration.identifier,
|
|
1612
|
-
cardinality: declaration.cardinality,
|
|
1613
|
-
baseType: declaration.baseType,
|
|
1614
|
-
values: declaration.defaultValue.values.map((entry) => coerceScalar(entry.value, declaration.baseType))
|
|
1615
|
-
});
|
|
1610
|
+
outcomes.set(declaration.identifier, rpValue(declaration.cardinality, declaration.defaultValue.values.map((entry) => coerceScalar(entry.value, declaration.baseType)), declaration.baseType));
|
|
1616
1611
|
continue;
|
|
1617
1612
|
}
|
|
1618
1613
|
outcomes.set(declaration.identifier, isNumericBaseType(declaration.baseType) ? floatValue(0) : null);
|
|
@@ -1780,11 +1775,7 @@ function executeTemplateProcessing(view, context) {
|
|
|
1780
1775
|
function initialValues() {
|
|
1781
1776
|
const values = new Map;
|
|
1782
1777
|
for (const declaration of context.templateDeclarations) {
|
|
1783
|
-
values.set(declaration.identifier, declaration.defaultValue ?
|
|
1784
|
-
cardinality: declaration.cardinality,
|
|
1785
|
-
baseType: declaration.baseType,
|
|
1786
|
-
values: declaration.defaultValue.values.map((entry) => coerceScalar(entry.value, declaration.baseType))
|
|
1787
|
-
} : null);
|
|
1778
|
+
values.set(declaration.identifier, declaration.defaultValue ? rpValue(declaration.cardinality, declaration.defaultValue.values.map((entry) => coerceScalar(entry.value, declaration.baseType)), declaration.baseType) : null);
|
|
1788
1779
|
}
|
|
1789
1780
|
return values;
|
|
1790
1781
|
}
|
|
@@ -1815,11 +1806,7 @@ function executeTemplateProcessing(view, context) {
|
|
|
1815
1806
|
if (rule.identifier !== undefined && rule.expression !== undefined) {
|
|
1816
1807
|
const declaration = declarationsById.get(rule.identifier);
|
|
1817
1808
|
const value = evaluateExpression(rule.expression, env);
|
|
1818
|
-
templateValues.set(rule.identifier, value === null || !declaration ? value :
|
|
1819
|
-
cardinality: declaration.cardinality,
|
|
1820
|
-
baseType: declaration.baseType ?? value.baseType,
|
|
1821
|
-
values: value.values
|
|
1822
|
-
});
|
|
1809
|
+
templateValues.set(rule.identifier, value === null || !declaration ? value : rpValue(declaration.cardinality, value.values, declaration.baseType ?? value.baseType));
|
|
1823
1810
|
}
|
|
1824
1811
|
continue;
|
|
1825
1812
|
}
|
|
@@ -2192,11 +2179,7 @@ function createTestController(view, options) {
|
|
|
2192
2179
|
const outcomes = new Map;
|
|
2193
2180
|
for (const declaration of view.outcomeDeclarations ?? []) {
|
|
2194
2181
|
if (declaration.defaultValue) {
|
|
2195
|
-
outcomes.set(declaration.identifier,
|
|
2196
|
-
cardinality: declaration.cardinality,
|
|
2197
|
-
baseType: declaration.baseType,
|
|
2198
|
-
values: declaration.defaultValue.values.map((entry) => coerceScalar(entry.value, declaration.baseType))
|
|
2199
|
-
});
|
|
2182
|
+
outcomes.set(declaration.identifier, rpValue(declaration.cardinality, declaration.defaultValue.values.map((entry) => coerceScalar(entry.value, declaration.baseType)), declaration.baseType));
|
|
2200
2183
|
continue;
|
|
2201
2184
|
}
|
|
2202
2185
|
outcomes.set(declaration.identifier, isNumericBaseType(declaration.baseType) ? floatValue(0) : null);
|
|
@@ -2244,7 +2227,7 @@ function createTestController(view, options) {
|
|
|
2244
2227
|
baseType ??= lifted.baseType;
|
|
2245
2228
|
members.push(...lifted.values);
|
|
2246
2229
|
}
|
|
2247
|
-
return members.length === 0 ? null :
|
|
2230
|
+
return members.length === 0 ? null : rpValue("multiple", members, baseType);
|
|
2248
2231
|
},
|
|
2249
2232
|
testAggregate: (expression) => {
|
|
2250
2233
|
const subset = subsetItems(expression);
|
|
@@ -3021,11 +3004,12 @@ function createQtiRuntime(config) {
|
|
|
3021
3004
|
}
|
|
3022
3005
|
const parsed = descriptor.schema.safeParse(node);
|
|
3023
3006
|
if (!parsed.success) {
|
|
3007
|
+
const detail = parsed.error.issues[0]?.message;
|
|
3024
3008
|
report({
|
|
3025
3009
|
type: "invalid-interaction",
|
|
3026
3010
|
name: node.kind,
|
|
3027
3011
|
responseIdentifier: node.responseIdentifier,
|
|
3028
|
-
detail:
|
|
3012
|
+
...detail !== undefined ? { detail } : {}
|
|
3029
3013
|
});
|
|
3030
3014
|
}
|
|
3031
3015
|
return;
|
|
@@ -3370,10 +3354,13 @@ async function resolveModule(node, registry) {
|
|
|
3370
3354
|
async function mountPci(options) {
|
|
3371
3355
|
const { container, node, registry } = options;
|
|
3372
3356
|
const module = await resolveModule(node, registry);
|
|
3357
|
+
const mountRoot = container.ownerDocument.createElement("div");
|
|
3358
|
+
mountRoot.setAttribute("data-qti-pci-mount", "");
|
|
3359
|
+
container.appendChild(mountRoot);
|
|
3373
3360
|
const markupHost = container.ownerDocument.createElement("div");
|
|
3374
3361
|
markupHost.className = "qti-interaction-markup";
|
|
3375
3362
|
markupHost.innerHTML = serializePciMarkup(node.interactionMarkup?.content);
|
|
3376
|
-
|
|
3363
|
+
mountRoot.appendChild(markupHost);
|
|
3377
3364
|
let resolveReady;
|
|
3378
3365
|
const ready = new Promise((resolve) => {
|
|
3379
3366
|
resolveReady = resolve;
|
|
@@ -3384,9 +3371,9 @@ async function mountPci(options) {
|
|
|
3384
3371
|
...options.declaration ? { boundTo: { [node.responseIdentifier]: valueToPciResponse(options.boundValue ?? null, options.declaration) } } : {},
|
|
3385
3372
|
status: "interacting",
|
|
3386
3373
|
onready: (instance2) => resolveReady(instance2),
|
|
3387
|
-
ondone: (
|
|
3374
|
+
ondone: (_instance, response, state) => options.ondone?.(pciResponseToValue(response), state)
|
|
3388
3375
|
};
|
|
3389
|
-
const returned = module.getInstance(
|
|
3376
|
+
const returned = module.getInstance(mountRoot, configuration, options.state);
|
|
3390
3377
|
if (returned) {
|
|
3391
3378
|
resolveReady(returned);
|
|
3392
3379
|
}
|
|
@@ -3397,7 +3384,7 @@ async function mountPci(options) {
|
|
|
3397
3384
|
getState: () => instance.getState?.(),
|
|
3398
3385
|
unmount: () => {
|
|
3399
3386
|
instance.oncompleted?.();
|
|
3400
|
-
|
|
3387
|
+
mountRoot.remove();
|
|
3401
3388
|
}
|
|
3402
3389
|
};
|
|
3403
3390
|
}
|
|
@@ -3919,7 +3906,12 @@ function ChoiceReferenceSkin(props) {
|
|
|
3919
3906
|
}
|
|
3920
3907
|
|
|
3921
3908
|
// src/reference-skin/drawing.ts
|
|
3922
|
-
import {
|
|
3909
|
+
import {
|
|
3910
|
+
createElement as createElement5,
|
|
3911
|
+
useCallback,
|
|
3912
|
+
useEffect as useEffect2,
|
|
3913
|
+
useRef as useRef2
|
|
3914
|
+
} from "react";
|
|
3923
3915
|
var strokeStyle = "#c2410c";
|
|
3924
3916
|
var strokeWidth = 3;
|
|
3925
3917
|
function DrawingReferenceSkin(props) {
|
|
@@ -3930,7 +3922,8 @@ function DrawingReferenceSkin(props) {
|
|
|
3930
3922
|
const drawingRef = useRef2(false);
|
|
3931
3923
|
const propsRef = useRef2(props);
|
|
3932
3924
|
propsRef.current = props;
|
|
3933
|
-
const
|
|
3925
|
+
const stageData = node.object.data;
|
|
3926
|
+
const paintBackground = useCallback((canvas) => {
|
|
3934
3927
|
const context = canvas.getContext("2d");
|
|
3935
3928
|
if (!context) {
|
|
3936
3929
|
return;
|
|
@@ -3940,13 +3933,13 @@ function DrawingReferenceSkin(props) {
|
|
|
3940
3933
|
image.onload = () => {
|
|
3941
3934
|
context.drawImage(image, 0, 0, canvas.width, canvas.height);
|
|
3942
3935
|
};
|
|
3943
|
-
image.src = propsRef.current.resolveAsset(
|
|
3944
|
-
};
|
|
3936
|
+
image.src = propsRef.current.resolveAsset(stageData);
|
|
3937
|
+
}, [stageData]);
|
|
3945
3938
|
useEffect2(() => {
|
|
3946
3939
|
if (canvasRef.current) {
|
|
3947
3940
|
paintBackground(canvasRef.current);
|
|
3948
3941
|
}
|
|
3949
|
-
}, [
|
|
3942
|
+
}, [paintBackground]);
|
|
3950
3943
|
const pointerPosition = (event) => {
|
|
3951
3944
|
const canvas = event.currentTarget;
|
|
3952
3945
|
const rect = canvas.getBoundingClientRect();
|
|
@@ -4359,124 +4352,27 @@ function HotspotReferenceSkin(props) {
|
|
|
4359
4352
|
});
|
|
4360
4353
|
}
|
|
4361
4354
|
|
|
4362
|
-
// src/reference-skin/position-object.ts
|
|
4363
|
-
import { createElement as createElement14 } from "react";
|
|
4364
|
-
function PositionObjectReferenceSkin(props) {
|
|
4365
|
-
const node = props.node;
|
|
4366
|
-
const maxChoices = node.maxChoices ?? 1;
|
|
4367
|
-
const points = props.value === null ? [] : typeof props.value === "string" ? [props.value] : Array.isArray(props.value) ? [...props.value] : [];
|
|
4368
|
-
if (!node.stageObject || !node.object) {
|
|
4369
|
-
return null;
|
|
4370
|
-
}
|
|
4371
|
-
const movable = node.object;
|
|
4372
|
-
function stageClick(point) {
|
|
4373
|
-
if (props.disabled) {
|
|
4374
|
-
return;
|
|
4375
|
-
}
|
|
4376
|
-
const formatted = formatPoint(point);
|
|
4377
|
-
if (maxChoices === 1) {
|
|
4378
|
-
props.setValue(formatted);
|
|
4379
|
-
return;
|
|
4380
|
-
}
|
|
4381
|
-
if (points.length < maxChoices) {
|
|
4382
|
-
props.setValue([...points, formatted]);
|
|
4383
|
-
}
|
|
4384
|
-
}
|
|
4385
|
-
return createElement14(GraphicStage, {
|
|
4386
|
-
object: node.stageObject,
|
|
4387
|
-
resolveAsset: props.resolveAsset,
|
|
4388
|
-
interaction: "positionObjectStage",
|
|
4389
|
-
status: props.status,
|
|
4390
|
-
onStageClick: stageClick,
|
|
4391
|
-
prompt: node.prompt ? createElement14("div", { "data-qti-prompt": true }, props.renderContent(node.prompt.content)) : null,
|
|
4392
|
-
overlay: points.map((value, index) => {
|
|
4393
|
-
const point = parsePoint(value);
|
|
4394
|
-
if (!point) {
|
|
4395
|
-
return null;
|
|
4396
|
-
}
|
|
4397
|
-
return createElement14("image", {
|
|
4398
|
-
key: `${value}-${index}`,
|
|
4399
|
-
href: props.resolveAsset(movable.data),
|
|
4400
|
-
x: point.x - (movable.width ?? 0) / 2,
|
|
4401
|
-
y: point.y - (movable.height ?? 0) / 2,
|
|
4402
|
-
width: movable.width,
|
|
4403
|
-
height: movable.height,
|
|
4404
|
-
"data-qti-point": value,
|
|
4405
|
-
style: { pointerEvents: "none" }
|
|
4406
|
-
});
|
|
4407
|
-
})
|
|
4408
|
-
});
|
|
4409
|
-
}
|
|
4410
|
-
|
|
4411
|
-
// src/reference-skin/select-point.ts
|
|
4412
|
-
import { Fragment as Fragment5, createElement as createElement15 } from "react";
|
|
4413
|
-
function SelectPointReferenceSkin(props) {
|
|
4414
|
-
const node = props.node;
|
|
4415
|
-
const maxChoices = node.maxChoices ?? 1;
|
|
4416
|
-
const points = props.value === null ? [] : typeof props.value === "string" ? [props.value] : Array.isArray(props.value) ? [...props.value] : [];
|
|
4417
|
-
if (!node.object) {
|
|
4418
|
-
return null;
|
|
4419
|
-
}
|
|
4420
|
-
function stageClick(point) {
|
|
4421
|
-
if (props.disabled) {
|
|
4422
|
-
return;
|
|
4423
|
-
}
|
|
4424
|
-
const formatted = formatPoint(point);
|
|
4425
|
-
if (maxChoices === 1) {
|
|
4426
|
-
props.setValue(formatted);
|
|
4427
|
-
return;
|
|
4428
|
-
}
|
|
4429
|
-
if (points.length < maxChoices) {
|
|
4430
|
-
props.setValue([...points, formatted]);
|
|
4431
|
-
}
|
|
4432
|
-
}
|
|
4433
|
-
return createElement15(GraphicStage, {
|
|
4434
|
-
object: node.object,
|
|
4435
|
-
resolveAsset: props.resolveAsset,
|
|
4436
|
-
interaction: "selectPointInteraction",
|
|
4437
|
-
status: props.status,
|
|
4438
|
-
onStageClick: stageClick,
|
|
4439
|
-
prompt: node.prompt ? createElement15("div", { "data-qti-prompt": true }, props.renderContent(node.prompt.content)) : null,
|
|
4440
|
-
overlay: points.map((value, index) => {
|
|
4441
|
-
const [x, y] = value.split(/\s+/u).map(Number);
|
|
4442
|
-
return createElement15(Fragment5, { key: `${value}-${index}` }, createElement15("circle", {
|
|
4443
|
-
cx: x,
|
|
4444
|
-
cy: y,
|
|
4445
|
-
r: 5,
|
|
4446
|
-
fill: "currentColor",
|
|
4447
|
-
"data-qti-point": value,
|
|
4448
|
-
style: { pointerEvents: "none" }
|
|
4449
|
-
}));
|
|
4450
|
-
}),
|
|
4451
|
-
after: createElement15("button", {
|
|
4452
|
-
type: "button",
|
|
4453
|
-
disabled: props.disabled || points.length === 0,
|
|
4454
|
-
onClick: () => props.setValue(null)
|
|
4455
|
-
}, "Clear points")
|
|
4456
|
-
});
|
|
4457
|
-
}
|
|
4458
|
-
|
|
4459
4355
|
// src/reference-skin/hottext.ts
|
|
4460
|
-
import { createElement as
|
|
4356
|
+
import { createElement as createElement14 } from "react";
|
|
4461
4357
|
function HottextReferenceSkin(props) {
|
|
4462
4358
|
const node = props.node;
|
|
4463
|
-
return
|
|
4359
|
+
return createElement14("div", { "data-qti-interaction": "hottextInteraction", "data-status": props.status }, node.prompt ? createElement14("div", { "data-qti-prompt": true }, props.renderContent(node.prompt.content)) : null, props.renderContent(node.content, {
|
|
4464
4360
|
hottext: (child, key) => {
|
|
4465
4361
|
const view = child;
|
|
4466
4362
|
const identifier = view.identifier ?? "";
|
|
4467
4363
|
const optionProps = props.getOptionProps(identifier);
|
|
4468
|
-
return
|
|
4364
|
+
return createElement14("button", { key, type: "button", disabled: props.disabled, "data-qti-hottext": identifier, ...optionProps }, props.renderContent(view.content) ?? identifier);
|
|
4469
4365
|
}
|
|
4470
4366
|
}));
|
|
4471
4367
|
}
|
|
4472
4368
|
|
|
4473
4369
|
// src/reference-skin/inline-choice.ts
|
|
4474
|
-
import { createElement as
|
|
4370
|
+
import { createElement as createElement15 } from "react";
|
|
4475
4371
|
function InlineChoiceReferenceSkin(props) {
|
|
4476
4372
|
const node = props.node;
|
|
4477
4373
|
const choices = node.inlineChoices ?? [];
|
|
4478
4374
|
const value = typeof props.value === "string" ? props.value : "";
|
|
4479
|
-
return
|
|
4375
|
+
return createElement15("select", {
|
|
4480
4376
|
value,
|
|
4481
4377
|
disabled: props.disabled,
|
|
4482
4378
|
"aria-disabled": props.disabled,
|
|
@@ -4485,11 +4381,11 @@ function InlineChoiceReferenceSkin(props) {
|
|
|
4485
4381
|
onChange: (event) => {
|
|
4486
4382
|
props.setValue(event.target.value === "" ? null : event.target.value);
|
|
4487
4383
|
}
|
|
4488
|
-
},
|
|
4384
|
+
}, createElement15("option", { key: "", value: "" }, ""), choices.map((choice) => createElement15("option", { key: choice.identifier, value: choice.identifier }, textOf(choice.content))));
|
|
4489
4385
|
}
|
|
4490
4386
|
|
|
4491
4387
|
// src/reference-skin/match.ts
|
|
4492
|
-
import { createElement as
|
|
4388
|
+
import { createElement as createElement16 } from "react";
|
|
4493
4389
|
function MatchReferenceSkin(props) {
|
|
4494
4390
|
const node = props.node;
|
|
4495
4391
|
const rows = node.simpleMatchSets?.[0]?.simpleAssociableChoices ?? [];
|
|
@@ -4498,9 +4394,9 @@ function MatchReferenceSkin(props) {
|
|
|
4498
4394
|
function togglePair(pair) {
|
|
4499
4395
|
props.setValue(pairs.includes(pair) ? pairs.filter((entry) => entry !== pair) : [...pairs, pair]);
|
|
4500
4396
|
}
|
|
4501
|
-
return
|
|
4397
|
+
return createElement16("div", { "data-qti-interaction": "matchInteraction", "data-status": props.status }, node.prompt ? createElement16("div", { "data-qti-prompt": true }, props.renderContent(node.prompt.content)) : null, createElement16("table", null, createElement16("thead", null, createElement16("tr", null, createElement16("td", null), columns.map((column) => createElement16("th", { key: column.identifier, scope: "col" }, textOf(column.content) || column.identifier)))), createElement16("tbody", null, rows.map((row) => createElement16("tr", { key: row.identifier }, createElement16("th", { scope: "row" }, textOf(row.content) || row.identifier), columns.map((column) => {
|
|
4502
4398
|
const pair = `${row.identifier} ${column.identifier}`;
|
|
4503
|
-
return
|
|
4399
|
+
return createElement16("td", { key: column.identifier }, createElement16("input", {
|
|
4504
4400
|
type: "checkbox",
|
|
4505
4401
|
checked: pairs.includes(pair),
|
|
4506
4402
|
disabled: props.disabled,
|
|
@@ -4512,7 +4408,7 @@ function MatchReferenceSkin(props) {
|
|
|
4512
4408
|
}
|
|
4513
4409
|
|
|
4514
4410
|
// src/reference-skin/media.ts
|
|
4515
|
-
import { createElement as
|
|
4411
|
+
import { createElement as createElement17 } from "react";
|
|
4516
4412
|
function findMediaElement(nodes) {
|
|
4517
4413
|
for (const node of nodes ?? []) {
|
|
4518
4414
|
if (node.kind === "xml") {
|
|
@@ -4534,10 +4430,10 @@ function MediaReferenceSkin(props) {
|
|
|
4534
4430
|
const plays = typeof props.value === "string" ? Number(props.value) || 0 : 0;
|
|
4535
4431
|
const playsExhausted = node.maxPlays !== undefined && node.maxPlays > 0 && plays >= node.maxPlays;
|
|
4536
4432
|
if (!media) {
|
|
4537
|
-
return
|
|
4433
|
+
return createElement17("div", { "data-qti-interaction": "mediaInteraction", "data-status": props.status }, "No media element.");
|
|
4538
4434
|
}
|
|
4539
4435
|
const src = typeof media.attributes?.["src"] === "string" ? props.resolveAsset(media.attributes["src"]) : undefined;
|
|
4540
|
-
return
|
|
4436
|
+
return createElement17("div", { "data-qti-interaction": "mediaInteraction", "data-status": props.status, "data-qti-plays": plays }, node.prompt ? createElement17("div", { "data-qti-prompt": true }, props.renderContent(node.prompt.content)) : null, createElement17(media.name, {
|
|
4541
4437
|
src,
|
|
4542
4438
|
controls: true,
|
|
4543
4439
|
loop: node.loop ?? false,
|
|
@@ -4548,11 +4444,11 @@ function MediaReferenceSkin(props) {
|
|
|
4548
4444
|
}
|
|
4549
4445
|
props.setValue(String(plays + 1));
|
|
4550
4446
|
}
|
|
4551
|
-
}), playsExhausted ?
|
|
4447
|
+
}), playsExhausted ? createElement17("p", { role: "status" }, "No plays remaining.") : null);
|
|
4552
4448
|
}
|
|
4553
4449
|
|
|
4554
4450
|
// src/reference-skin/order.ts
|
|
4555
|
-
import { createElement as
|
|
4451
|
+
import { createElement as createElement18 } from "react";
|
|
4556
4452
|
function OrderReferenceSkin(props) {
|
|
4557
4453
|
const node = props.node;
|
|
4558
4454
|
const choices = node.simpleChoices ?? [];
|
|
@@ -4570,12 +4466,12 @@ function OrderReferenceSkin(props) {
|
|
|
4570
4466
|
next[target] = moved;
|
|
4571
4467
|
props.setValue(next);
|
|
4572
4468
|
}
|
|
4573
|
-
return
|
|
4469
|
+
return createElement18("div", { "data-qti-interaction": "orderInteraction", "data-status": props.status }, node.prompt ? createElement18("div", { "data-qti-prompt": true }, props.renderContent(node.prompt.content)) : null, createElement18("ol", null, order.map((identifier, index) => createElement18("li", { key: identifier }, props.renderContent(choicesById.get(identifier)?.content) ?? identifier, createElement18("button", {
|
|
4574
4470
|
type: "button",
|
|
4575
4471
|
"aria-label": `Move ${identifier} up`,
|
|
4576
4472
|
disabled: props.disabled || index === 0,
|
|
4577
4473
|
onClick: () => move(index, -1)
|
|
4578
|
-
}, "↑"),
|
|
4474
|
+
}, "↑"), createElement18("button", {
|
|
4579
4475
|
type: "button",
|
|
4580
4476
|
"aria-label": `Move ${identifier} down`,
|
|
4581
4477
|
disabled: props.disabled || index === order.length - 1,
|
|
@@ -4583,6 +4479,103 @@ function OrderReferenceSkin(props) {
|
|
|
4583
4479
|
}, "↓")))));
|
|
4584
4480
|
}
|
|
4585
4481
|
|
|
4482
|
+
// src/reference-skin/position-object.ts
|
|
4483
|
+
import { createElement as createElement19 } from "react";
|
|
4484
|
+
function PositionObjectReferenceSkin(props) {
|
|
4485
|
+
const node = props.node;
|
|
4486
|
+
const maxChoices = node.maxChoices ?? 1;
|
|
4487
|
+
const points = props.value === null ? [] : typeof props.value === "string" ? [props.value] : Array.isArray(props.value) ? [...props.value] : [];
|
|
4488
|
+
if (!node.stageObject || !node.object) {
|
|
4489
|
+
return null;
|
|
4490
|
+
}
|
|
4491
|
+
const movable = node.object;
|
|
4492
|
+
function stageClick(point) {
|
|
4493
|
+
if (props.disabled) {
|
|
4494
|
+
return;
|
|
4495
|
+
}
|
|
4496
|
+
const formatted = formatPoint(point);
|
|
4497
|
+
if (maxChoices === 1) {
|
|
4498
|
+
props.setValue(formatted);
|
|
4499
|
+
return;
|
|
4500
|
+
}
|
|
4501
|
+
if (points.length < maxChoices) {
|
|
4502
|
+
props.setValue([...points, formatted]);
|
|
4503
|
+
}
|
|
4504
|
+
}
|
|
4505
|
+
return createElement19(GraphicStage, {
|
|
4506
|
+
object: node.stageObject,
|
|
4507
|
+
resolveAsset: props.resolveAsset,
|
|
4508
|
+
interaction: "positionObjectStage",
|
|
4509
|
+
status: props.status,
|
|
4510
|
+
onStageClick: stageClick,
|
|
4511
|
+
prompt: node.prompt ? createElement19("div", { "data-qti-prompt": true }, props.renderContent(node.prompt.content)) : null,
|
|
4512
|
+
overlay: points.map((value, index) => {
|
|
4513
|
+
const point = parsePoint(value);
|
|
4514
|
+
if (!point) {
|
|
4515
|
+
return null;
|
|
4516
|
+
}
|
|
4517
|
+
return createElement19("image", {
|
|
4518
|
+
key: `${value}-${index}`,
|
|
4519
|
+
href: props.resolveAsset(movable.data),
|
|
4520
|
+
x: point.x - (movable.width ?? 0) / 2,
|
|
4521
|
+
y: point.y - (movable.height ?? 0) / 2,
|
|
4522
|
+
width: movable.width,
|
|
4523
|
+
height: movable.height,
|
|
4524
|
+
"data-qti-point": value,
|
|
4525
|
+
style: { pointerEvents: "none" }
|
|
4526
|
+
});
|
|
4527
|
+
})
|
|
4528
|
+
});
|
|
4529
|
+
}
|
|
4530
|
+
|
|
4531
|
+
// src/reference-skin/select-point.ts
|
|
4532
|
+
import { Fragment as Fragment5, createElement as createElement20 } from "react";
|
|
4533
|
+
function SelectPointReferenceSkin(props) {
|
|
4534
|
+
const node = props.node;
|
|
4535
|
+
const maxChoices = node.maxChoices ?? 1;
|
|
4536
|
+
const points = props.value === null ? [] : typeof props.value === "string" ? [props.value] : Array.isArray(props.value) ? [...props.value] : [];
|
|
4537
|
+
if (!node.object) {
|
|
4538
|
+
return null;
|
|
4539
|
+
}
|
|
4540
|
+
function stageClick(point) {
|
|
4541
|
+
if (props.disabled) {
|
|
4542
|
+
return;
|
|
4543
|
+
}
|
|
4544
|
+
const formatted = formatPoint(point);
|
|
4545
|
+
if (maxChoices === 1) {
|
|
4546
|
+
props.setValue(formatted);
|
|
4547
|
+
return;
|
|
4548
|
+
}
|
|
4549
|
+
if (points.length < maxChoices) {
|
|
4550
|
+
props.setValue([...points, formatted]);
|
|
4551
|
+
}
|
|
4552
|
+
}
|
|
4553
|
+
return createElement20(GraphicStage, {
|
|
4554
|
+
object: node.object,
|
|
4555
|
+
resolveAsset: props.resolveAsset,
|
|
4556
|
+
interaction: "selectPointInteraction",
|
|
4557
|
+
status: props.status,
|
|
4558
|
+
onStageClick: stageClick,
|
|
4559
|
+
prompt: node.prompt ? createElement20("div", { "data-qti-prompt": true }, props.renderContent(node.prompt.content)) : null,
|
|
4560
|
+
overlay: points.map((value, index) => {
|
|
4561
|
+
const [x, y] = value.split(/\s+/u).map(Number);
|
|
4562
|
+
return createElement20(Fragment5, { key: `${value}-${index}` }, createElement20("circle", {
|
|
4563
|
+
cx: x,
|
|
4564
|
+
cy: y,
|
|
4565
|
+
r: 5,
|
|
4566
|
+
fill: "currentColor",
|
|
4567
|
+
"data-qti-point": value,
|
|
4568
|
+
style: { pointerEvents: "none" }
|
|
4569
|
+
}));
|
|
4570
|
+
}),
|
|
4571
|
+
after: createElement20("button", {
|
|
4572
|
+
type: "button",
|
|
4573
|
+
disabled: props.disabled || points.length === 0,
|
|
4574
|
+
onClick: () => props.setValue(null)
|
|
4575
|
+
}, "Clear points")
|
|
4576
|
+
});
|
|
4577
|
+
}
|
|
4578
|
+
|
|
4586
4579
|
// src/reference-skin/slider.ts
|
|
4587
4580
|
import { createElement as createElement21 } from "react";
|
|
4588
4581
|
function SliderReferenceSkin(props) {
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The graphic interaction family: descriptors share the object (stage image) and
|
|
3
|
+
* hotspot (shape/coords) schemas. Coordinates are numbers in image space, normalized
|
|
4
|
+
* upstream from the QTI coords attribute.
|
|
5
|
+
*/
|
|
6
|
+
import { type InteractionDescriptor } from "../runtime";
|
|
7
|
+
export declare const hotspotInteraction: InteractionDescriptor<"hotspotInteraction">;
|
|
8
|
+
export declare const graphicOrderInteraction: InteractionDescriptor<"graphicOrderInteraction">;
|
|
9
|
+
export declare const graphicAssociateInteraction: InteractionDescriptor<"graphicAssociateInteraction">;
|
|
10
|
+
export declare const graphicGapMatchInteraction: InteractionDescriptor<"graphicGapMatchInteraction">;
|
|
11
|
+
export declare const selectPointInteraction: InteractionDescriptor<"selectPointInteraction">;
|
|
12
|
+
/** The common single-interaction stage; multi-interaction stages fail validation. */
|
|
13
|
+
export declare const positionObjectStage: InteractionDescriptor<"positionObjectStage">;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { InteractionDescriptor } from "../runtime";
|
|
2
|
+
export { associateInteraction } from "./associate";
|
|
3
|
+
export { choiceInteraction } from "./choice";
|
|
4
|
+
export { drawingInteraction } from "./drawing";
|
|
5
|
+
export { endAttemptInteraction } from "./end-attempt";
|
|
6
|
+
export { extendedTextInteraction } from "./extended-text";
|
|
7
|
+
export { gapMatchInteraction } from "./gap-match";
|
|
8
|
+
export { graphicAssociateInteraction, graphicGapMatchInteraction, graphicOrderInteraction, hotspotInteraction, positionObjectStage, selectPointInteraction, } from "./graphic";
|
|
9
|
+
export { hottextInteraction } from "./hottext";
|
|
10
|
+
export { inlineChoiceInteraction } from "./inline-choice";
|
|
11
|
+
export { matchInteraction } from "./match";
|
|
12
|
+
export { mediaInteraction } from "./media";
|
|
13
|
+
export { orderInteraction } from "./order";
|
|
14
|
+
export { sliderInteraction } from "./slider";
|
|
15
|
+
export { textEntryInteraction } from "./text-entry";
|
|
16
|
+
export { uploadInteraction } from "./upload";
|
|
17
|
+
/** The interaction set conform-ed ships; consumers assemble these plus their extensions. */
|
|
18
|
+
export declare const qtiCoreInteractions: readonly InteractionDescriptor[];
|