@conform-ed/qti-react 0.0.12 → 0.0.13
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/index.js +4566 -212
- package/package.json +3 -1
- package/src/capability.ts +24 -0
- package/src/content-model.ts +104 -5
- package/src/graphic.ts +103 -0
- package/src/index.ts +139 -3
- package/src/interactions/associate.ts +22 -0
- package/src/interactions/drawing.ts +24 -0
- package/src/interactions/end-attempt.ts +19 -0
- package/src/interactions/extended-text.ts +21 -0
- package/src/interactions/gap-match.ts +22 -0
- package/src/interactions/graphic.ts +104 -0
- package/src/interactions/hottext.ts +21 -0
- package/src/interactions/index.ts +57 -2
- package/src/interactions/match.ts +27 -0
- package/src/interactions/media.ts +24 -0
- package/src/interactions/order.ts +21 -0
- package/src/interactions/slider.ts +24 -0
- package/src/interactions/upload.ts +19 -0
- package/src/normalized-item.ts +561 -0
- package/src/pci/index.ts +22 -0
- package/src/pci/interaction.ts +42 -0
- package/src/pci/markup.ts +102 -0
- package/src/pci/mount.ts +135 -0
- package/src/pci/registry.ts +240 -0
- package/src/pci/response.ts +138 -0
- package/src/pci/skin.ts +87 -0
- package/src/reference-skin/associate.ts +98 -0
- package/src/reference-skin/choice.ts +44 -0
- package/src/reference-skin/content.ts +30 -0
- package/src/reference-skin/drawing.ts +150 -0
- package/src/reference-skin/end-attempt.ts +27 -0
- package/src/reference-skin/extended-text.ts +35 -0
- package/src/reference-skin/gap-match.ts +69 -0
- package/src/reference-skin/graphic-associate.ts +123 -0
- package/src/reference-skin/graphic-base.ts +142 -0
- package/src/reference-skin/graphic-gap-match.ts +143 -0
- package/src/reference-skin/graphic-order.ts +76 -0
- package/src/reference-skin/hotspot.ts +43 -0
- package/src/reference-skin/hottext.ts +42 -0
- package/src/reference-skin/index.ts +75 -0
- package/src/reference-skin/inline-choice.ts +42 -0
- package/src/reference-skin/match.ts +80 -0
- package/src/reference-skin/media.ts +74 -0
- package/src/reference-skin/order.ts +79 -0
- package/src/reference-skin/position-object.ts +84 -0
- package/src/reference-skin/select-point.ts +87 -0
- package/src/reference-skin/slider.ts +41 -0
- package/src/reference-skin/text-entry.ts +31 -0
- package/src/reference-skin/upload.ts +46 -0
- package/src/response-processing.ts +178 -29
- package/src/rp/evaluate.ts +828 -0
- package/src/rp/index.ts +30 -0
- package/src/rp/interpreter.ts +251 -0
- package/src/rp/template-processing.ts +295 -0
- package/src/rp/templates.ts +190 -0
- package/src/rp/types.ts +161 -0
- package/src/rp/values.ts +198 -0
- package/src/runtime.ts +474 -28
- package/src/store.ts +155 -5
- package/src/test/controller.ts +806 -0
- package/src/test/index.ts +25 -0
- package/src/test/session-store.ts +244 -0
- package/src/test/types.ts +203 -0
- package/src/types.ts +27 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@conform-ed/qti-react",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.13",
|
|
4
4
|
"files": [
|
|
5
5
|
"src",
|
|
6
6
|
"dist"
|
|
@@ -21,8 +21,10 @@
|
|
|
21
21
|
"test": "bun test"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
|
+
"@conform-ed/qti-xml": "0.0.13",
|
|
24
25
|
"@types/react": "^19.2.17",
|
|
25
26
|
"@types/react-dom": "^19",
|
|
27
|
+
"happy-dom": "^20.10.2",
|
|
26
28
|
"react": "^19.2.7",
|
|
27
29
|
"react-dom": "^19"
|
|
28
30
|
},
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Capability Report types (ADR-0003): the runtime's answer to "can this content be
|
|
3
|
+
* delivered, and if not, why". In their own module so the RP interpreter can report
|
|
4
|
+
* issues without importing the React runtime.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export type CapabilityIssueType =
|
|
8
|
+
| "unsupported-interaction"
|
|
9
|
+
| "invalid-interaction"
|
|
10
|
+
| "unsupported-element"
|
|
11
|
+
| "unsupported-rp";
|
|
12
|
+
|
|
13
|
+
export interface CapabilityIssue {
|
|
14
|
+
readonly type: CapabilityIssueType;
|
|
15
|
+
/** The interaction kind, element name, or RP rule/operator/template at issue. */
|
|
16
|
+
readonly name: string;
|
|
17
|
+
readonly responseIdentifier?: string;
|
|
18
|
+
readonly detail?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface CapabilityReport {
|
|
22
|
+
readonly deliverable: boolean;
|
|
23
|
+
readonly issues: readonly CapabilityIssue[];
|
|
24
|
+
}
|
package/src/content-model.ts
CHANGED
|
@@ -13,8 +13,29 @@
|
|
|
13
13
|
* HTML5 at once".
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
/** Interaction node kinds
|
|
17
|
-
export const v0InteractionKinds = [
|
|
16
|
+
/** Interaction node kinds conform-ed ships descriptors and Reference Skins for. */
|
|
17
|
+
export const v0InteractionKinds = [
|
|
18
|
+
"associateInteraction",
|
|
19
|
+
"choiceInteraction",
|
|
20
|
+
"drawingInteraction",
|
|
21
|
+
"endAttemptInteraction",
|
|
22
|
+
"extendedTextInteraction",
|
|
23
|
+
"gapMatchInteraction",
|
|
24
|
+
"graphicAssociateInteraction",
|
|
25
|
+
"graphicGapMatchInteraction",
|
|
26
|
+
"graphicOrderInteraction",
|
|
27
|
+
"hotspotInteraction",
|
|
28
|
+
"hottextInteraction",
|
|
29
|
+
"inlineChoiceInteraction",
|
|
30
|
+
"matchInteraction",
|
|
31
|
+
"mediaInteraction",
|
|
32
|
+
"orderInteraction",
|
|
33
|
+
"positionObjectStage",
|
|
34
|
+
"selectPointInteraction",
|
|
35
|
+
"sliderInteraction",
|
|
36
|
+
"textEntryInteraction",
|
|
37
|
+
"uploadInteraction",
|
|
38
|
+
] as const;
|
|
18
39
|
|
|
19
40
|
export type V0InteractionKind = (typeof v0InteractionKinds)[number];
|
|
20
41
|
|
|
@@ -26,16 +47,63 @@ const v0FlowElements = new Set<string>([
|
|
|
26
47
|
"em",
|
|
27
48
|
"b",
|
|
28
49
|
"i",
|
|
50
|
+
"sub",
|
|
51
|
+
"sup",
|
|
29
52
|
"br",
|
|
30
53
|
"ul",
|
|
31
54
|
"ol",
|
|
32
55
|
"li",
|
|
33
56
|
// language-critical
|
|
34
57
|
"ruby",
|
|
58
|
+
"rb",
|
|
35
59
|
"rt",
|
|
36
60
|
"rp",
|
|
61
|
+
// media (the first media-milestone growth; src/poster route through the Asset Resolver)
|
|
62
|
+
"img",
|
|
63
|
+
"audio",
|
|
64
|
+
"video",
|
|
65
|
+
"source",
|
|
66
|
+
"track",
|
|
67
|
+
"picture",
|
|
68
|
+
"figure",
|
|
69
|
+
"figcaption",
|
|
70
|
+
// embedded media the corpus uses for stages and standalone assets
|
|
71
|
+
"object",
|
|
72
|
+
// structural vocabulary the official corpus uses (fixture-driven growth, ADR-0002)
|
|
73
|
+
"div",
|
|
74
|
+
"section",
|
|
75
|
+
"h1",
|
|
76
|
+
"h2",
|
|
77
|
+
"h3",
|
|
78
|
+
"h4",
|
|
79
|
+
"h5",
|
|
80
|
+
"h6",
|
|
81
|
+
"blockquote",
|
|
82
|
+
"hr",
|
|
83
|
+
// tables (gradebook-style content; conservative semantics)
|
|
84
|
+
"table",
|
|
85
|
+
"caption",
|
|
86
|
+
"thead",
|
|
87
|
+
"tbody",
|
|
88
|
+
"tfoot",
|
|
89
|
+
"tr",
|
|
90
|
+
"th",
|
|
91
|
+
"td",
|
|
37
92
|
]);
|
|
38
93
|
|
|
94
|
+
/** Element-specific attribute allowlists, additive to the global set. */
|
|
95
|
+
const v0ElementAttributes: ReadonlyMap<string, ReadonlySet<string>> = new Map([
|
|
96
|
+
["img", new Set(["src", "alt", "width", "height"])],
|
|
97
|
+
["audio", new Set(["src", "controls", "loop", "muted", "preload"])],
|
|
98
|
+
["video", new Set(["src", "controls", "loop", "muted", "preload", "poster", "width", "height"])],
|
|
99
|
+
["source", new Set(["src", "type"])],
|
|
100
|
+
["track", new Set(["src", "kind", "srclang", "label", "default"])],
|
|
101
|
+
["object", new Set(["data", "type", "width", "height"])],
|
|
102
|
+
]);
|
|
103
|
+
|
|
104
|
+
/** Attribute names treated as packaged-asset references (rewritten by the Asset Resolver). */
|
|
105
|
+
const v0UrlAttributes = new Set<string>(["src", "poster", "data"]);
|
|
106
|
+
|
|
39
107
|
/**
|
|
40
108
|
* The MathML root. Its subtree is rendered structurally (presentation MathML) with the
|
|
41
109
|
* same attribute hardening, but element names inside are not individually allowlisted
|
|
@@ -51,6 +119,10 @@ export interface ContentModel {
|
|
|
51
119
|
readonly flowElements: ReadonlySet<string>;
|
|
52
120
|
readonly mathRoot: string;
|
|
53
121
|
readonly globalAttributes: ReadonlySet<string>;
|
|
122
|
+
/** Per-element attribute allowlists, additive to `globalAttributes`. */
|
|
123
|
+
readonly elementAttributes: ReadonlyMap<string, ReadonlySet<string>>;
|
|
124
|
+
/** Attributes whose values are asset references, routed through the Asset Resolver. */
|
|
125
|
+
readonly urlAttributes: ReadonlySet<string>;
|
|
54
126
|
}
|
|
55
127
|
|
|
56
128
|
export const v0ContentModel: ContentModel = {
|
|
@@ -58,6 +130,8 @@ export const v0ContentModel: ContentModel = {
|
|
|
58
130
|
flowElements: v0FlowElements,
|
|
59
131
|
mathRoot: v0MathRoot,
|
|
60
132
|
globalAttributes: v0GlobalAttributes,
|
|
133
|
+
elementAttributes: v0ElementAttributes,
|
|
134
|
+
urlAttributes: v0UrlAttributes,
|
|
61
135
|
};
|
|
62
136
|
|
|
63
137
|
export function isAllowedFlowElement(model: ContentModel, name: string): boolean {
|
|
@@ -84,11 +158,13 @@ function isUnsafeAttribute(name: string, value: unknown): boolean {
|
|
|
84
158
|
}
|
|
85
159
|
|
|
86
160
|
/**
|
|
87
|
-
* Reduce a raw attribute bag to the safe, allowlisted subset. Used by
|
|
88
|
-
* a node that validates against QTI structure still cannot carry
|
|
161
|
+
* Reduce a raw attribute bag to the safe, allowlisted subset for one element. Used by
|
|
162
|
+
* the body walk so a node that validates against QTI structure still cannot carry
|
|
163
|
+
* script or handlers. The allowlist is the global set plus the element's own entries.
|
|
89
164
|
*/
|
|
90
165
|
export function sanitizeAttributes(
|
|
91
166
|
model: ContentModel,
|
|
167
|
+
elementName: string,
|
|
92
168
|
attributes: Record<string, unknown> | undefined,
|
|
93
169
|
): Record<string, string> {
|
|
94
170
|
const safe: Record<string, string> = {};
|
|
@@ -97,12 +173,14 @@ export function sanitizeAttributes(
|
|
|
97
173
|
return safe;
|
|
98
174
|
}
|
|
99
175
|
|
|
176
|
+
const elementAllowed = model.elementAttributes.get(elementName);
|
|
177
|
+
|
|
100
178
|
for (const [name, value] of Object.entries(attributes)) {
|
|
101
179
|
if (isUnsafeAttribute(name, value)) {
|
|
102
180
|
continue;
|
|
103
181
|
}
|
|
104
182
|
|
|
105
|
-
if (!model.globalAttributes.has(name)) {
|
|
183
|
+
if (!model.globalAttributes.has(name) && !elementAllowed?.has(name)) {
|
|
106
184
|
continue;
|
|
107
185
|
}
|
|
108
186
|
|
|
@@ -113,3 +191,24 @@ export function sanitizeAttributes(
|
|
|
113
191
|
|
|
114
192
|
return safe;
|
|
115
193
|
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Attribute hardening for MathML subtrees: presentation attributes (mathvariant,
|
|
197
|
+
* linethickness, …) are not individually allowlisted — MathML has no scripting surface
|
|
198
|
+
* once event handlers and javascript: URLs are stripped.
|
|
199
|
+
*/
|
|
200
|
+
export function sanitizeMathAttributes(attributes: Record<string, unknown> | undefined): Record<string, string> {
|
|
201
|
+
const safe: Record<string, string> = {};
|
|
202
|
+
|
|
203
|
+
if (!attributes) {
|
|
204
|
+
return safe;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
for (const [name, value] of Object.entries(attributes)) {
|
|
208
|
+
if (!isUnsafeAttribute(name, value) && typeof value === "string") {
|
|
209
|
+
safe[name] = value;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return safe;
|
|
214
|
+
}
|
package/src/graphic.ts
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Graphic primitives shared by the graphic interaction family and areaMapping scoring:
|
|
3
|
+
* QTI shape/coords parsing and point-in-shape hit testing. Pure logic, no React.
|
|
4
|
+
*
|
|
5
|
+
* Shapes follow the QTI (HTML image-map) conventions:
|
|
6
|
+
* - `circle`: center-x, center-y, radius
|
|
7
|
+
* - `rect`: left-x, top-y, right-x, bottom-y
|
|
8
|
+
* - `poly`: x1, y1, ..., xn, yn
|
|
9
|
+
* - `ellipse`: center-x, center-y, radius-x, radius-y
|
|
10
|
+
* - `default`: the entire image
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export type QtiShape = "circle" | "rect" | "poly" | "ellipse" | "default";
|
|
14
|
+
|
|
15
|
+
export interface Point {
|
|
16
|
+
readonly x: number;
|
|
17
|
+
readonly y: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Parse a QTI coords attribute ("10,20,30") into numbers. */
|
|
21
|
+
export function parseCoords(coords: string): number[] {
|
|
22
|
+
return coords
|
|
23
|
+
.split(",")
|
|
24
|
+
.map((entry) => Number(entry.trim()))
|
|
25
|
+
.filter((value) => !Number.isNaN(value));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Parse a QTI point value ("x y") or null when malformed. */
|
|
29
|
+
export function parsePoint(value: string): Point | null {
|
|
30
|
+
const [x, y, ...rest] = value.trim().split(/\s+/u).map(Number);
|
|
31
|
+
|
|
32
|
+
if (x === undefined || y === undefined || rest.length > 0 || Number.isNaN(x) || Number.isNaN(y)) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return { x, y };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function formatPoint(point: Point): string {
|
|
40
|
+
return `${point.x} ${point.y}`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function pointInPolygon(coords: readonly number[], point: Point): boolean {
|
|
44
|
+
let inside = false;
|
|
45
|
+
|
|
46
|
+
for (let i = 0, j = coords.length - 2; i < coords.length; j = i, i += 2) {
|
|
47
|
+
const xi = coords[i]!;
|
|
48
|
+
const yi = coords[i + 1]!;
|
|
49
|
+
const xj = coords[j]!;
|
|
50
|
+
const yj = coords[j + 1]!;
|
|
51
|
+
const intersects = yi > point.y !== yj > point.y && point.x < ((xj - xi) * (point.y - yi)) / (yj - yi) + xi;
|
|
52
|
+
|
|
53
|
+
if (intersects) {
|
|
54
|
+
inside = !inside;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return inside;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** QTI hit test: is `point` inside the area described by (shape, coords)? */
|
|
62
|
+
export function pointInShape(shape: string, coords: readonly number[], point: Point): boolean {
|
|
63
|
+
switch (shape) {
|
|
64
|
+
case "default":
|
|
65
|
+
return true;
|
|
66
|
+
|
|
67
|
+
case "circle": {
|
|
68
|
+
const [cx, cy, r] = coords;
|
|
69
|
+
|
|
70
|
+
if (cx === undefined || cy === undefined || r === undefined) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return (point.x - cx) ** 2 + (point.y - cy) ** 2 <= r ** 2;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
case "rect": {
|
|
78
|
+
const [left, top, right, bottom] = coords;
|
|
79
|
+
|
|
80
|
+
if (left === undefined || top === undefined || right === undefined || bottom === undefined) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return point.x >= left && point.x <= right && point.y >= top && point.y <= bottom;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
case "ellipse": {
|
|
88
|
+
const [cx, cy, rx, ry] = coords;
|
|
89
|
+
|
|
90
|
+
if (cx === undefined || cy === undefined || rx === undefined || ry === undefined || rx === 0 || ry === 0) {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return (point.x - cx) ** 2 / rx ** 2 + (point.y - cy) ** 2 / ry ** 2 <= 1;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
case "poly":
|
|
98
|
+
return coords.length >= 6 && pointInPolygon(coords, point);
|
|
99
|
+
|
|
100
|
+
default:
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -12,9 +12,70 @@ export {
|
|
|
12
12
|
type V0InteractionKind,
|
|
13
13
|
} from "./content-model";
|
|
14
14
|
|
|
15
|
-
export { foldString, mapResponse, matchCorrect, scoreResponse } from "./response-processing";
|
|
15
|
+
export { foldString, mapResponse, matchCorrect, mapResponsePoint, scoreResponse } from "./response-processing";
|
|
16
16
|
|
|
17
|
-
export {
|
|
17
|
+
export { assessmentItemViewFromNormalized, assessmentTestViewFromNormalized } from "./normalized-item";
|
|
18
|
+
|
|
19
|
+
export { formatPoint, parseCoords, parsePoint, pointInShape, type Point, type QtiShape } from "./graphic";
|
|
20
|
+
|
|
21
|
+
export {
|
|
22
|
+
applyCorrectResponseOverrides,
|
|
23
|
+
collectRpIssues,
|
|
24
|
+
collectTemplateIssues,
|
|
25
|
+
executeResponseProcessing,
|
|
26
|
+
executeTemplateProcessing,
|
|
27
|
+
mulberry32,
|
|
28
|
+
resolveTemplate,
|
|
29
|
+
} from "./rp";
|
|
30
|
+
|
|
31
|
+
export type {
|
|
32
|
+
CustomOperatorImplementation,
|
|
33
|
+
MaybeRpValue,
|
|
34
|
+
OutcomeDeclarationView,
|
|
35
|
+
OutcomeValue,
|
|
36
|
+
ResponseNormalization,
|
|
37
|
+
ResponseProcessingContext,
|
|
38
|
+
ResponseProcessingResult,
|
|
39
|
+
ResponseProcessingView,
|
|
40
|
+
RpConditionBranch,
|
|
41
|
+
RpExpressionView,
|
|
42
|
+
RpRecordField,
|
|
43
|
+
RpRuleView,
|
|
44
|
+
RpScalar,
|
|
45
|
+
RpValue,
|
|
46
|
+
TemplateConditionBranch,
|
|
47
|
+
TemplateDeclarationView,
|
|
48
|
+
TemplateProcessingContext,
|
|
49
|
+
TemplateProcessingResult,
|
|
50
|
+
TemplateProcessingView,
|
|
51
|
+
TemplateRuleView,
|
|
52
|
+
} from "./rp";
|
|
53
|
+
|
|
54
|
+
export { createAttemptStore, type AttemptSnapshot, type AttemptStore, type AttemptStoreOptions } from "./store";
|
|
55
|
+
|
|
56
|
+
export {
|
|
57
|
+
createTestController,
|
|
58
|
+
createTestSessionStore,
|
|
59
|
+
type TestSessionSnapshot,
|
|
60
|
+
type TestSessionStore,
|
|
61
|
+
type TestSessionStoreOptions,
|
|
62
|
+
type AssessmentItemRefView,
|
|
63
|
+
type AssessmentSectionView,
|
|
64
|
+
type AssessmentTestView,
|
|
65
|
+
type BranchRuleView,
|
|
66
|
+
type ItemSessionControlView,
|
|
67
|
+
type OutcomeConditionBranch,
|
|
68
|
+
type OutcomeRuleView,
|
|
69
|
+
type TestController,
|
|
70
|
+
type TestFeedbackView,
|
|
71
|
+
type TestItemResult,
|
|
72
|
+
type TestPartView,
|
|
73
|
+
type TestPlan,
|
|
74
|
+
type TestPlanItem,
|
|
75
|
+
type TestPlanPart,
|
|
76
|
+
type TestSessionState,
|
|
77
|
+
type TimeLimitsView,
|
|
78
|
+
} from "./test";
|
|
18
79
|
|
|
19
80
|
export {
|
|
20
81
|
createQtiRuntime,
|
|
@@ -22,12 +83,18 @@ export {
|
|
|
22
83
|
type AssessmentItemView,
|
|
23
84
|
type AttemptController,
|
|
24
85
|
type BodyNode,
|
|
86
|
+
type CapabilityIssue,
|
|
87
|
+
type CapabilityIssueType,
|
|
88
|
+
type CapabilityReport,
|
|
89
|
+
type ContentRendererProps,
|
|
90
|
+
type FeedbackView,
|
|
25
91
|
type InteractionDescriptor,
|
|
26
92
|
type InteractionNode,
|
|
27
93
|
type InteractionRenderProps,
|
|
28
94
|
type InteractionSkin,
|
|
29
95
|
type InteractionStatus,
|
|
30
96
|
type ItemRendererProps,
|
|
97
|
+
type NodeOverrides,
|
|
31
98
|
type OptionProps,
|
|
32
99
|
type OptionStatus,
|
|
33
100
|
type QtiRuntime,
|
|
@@ -36,9 +103,78 @@ export {
|
|
|
36
103
|
type XmlContentNode,
|
|
37
104
|
} from "./runtime";
|
|
38
105
|
|
|
39
|
-
export {
|
|
106
|
+
export {
|
|
107
|
+
createPciModuleRegistry,
|
|
108
|
+
createPciSkin,
|
|
109
|
+
mountPci,
|
|
110
|
+
pciResponseToValue,
|
|
111
|
+
portableCustomInteraction,
|
|
112
|
+
serializePciMarkup,
|
|
113
|
+
valueToPciResponse,
|
|
114
|
+
type PciConfiguration,
|
|
115
|
+
type PciInstance,
|
|
116
|
+
type PciInteractionNode,
|
|
117
|
+
type PciModule,
|
|
118
|
+
type PciModuleRegistry,
|
|
119
|
+
type PciModuleRegistryOptions,
|
|
120
|
+
type PciMountHandle,
|
|
121
|
+
type PciMountOptions,
|
|
122
|
+
type PciSkinOptions,
|
|
123
|
+
} from "./pci";
|
|
124
|
+
|
|
125
|
+
export {
|
|
126
|
+
associateInteraction,
|
|
127
|
+
choiceInteraction,
|
|
128
|
+
drawingInteraction,
|
|
129
|
+
endAttemptInteraction,
|
|
130
|
+
extendedTextInteraction,
|
|
131
|
+
gapMatchInteraction,
|
|
132
|
+
graphicAssociateInteraction,
|
|
133
|
+
graphicGapMatchInteraction,
|
|
134
|
+
graphicOrderInteraction,
|
|
135
|
+
hotspotInteraction,
|
|
136
|
+
hottextInteraction,
|
|
137
|
+
inlineChoiceInteraction,
|
|
138
|
+
matchInteraction,
|
|
139
|
+
mediaInteraction,
|
|
140
|
+
orderInteraction,
|
|
141
|
+
positionObjectStage,
|
|
142
|
+
qtiCoreInteractions,
|
|
143
|
+
selectPointInteraction,
|
|
144
|
+
sliderInteraction,
|
|
145
|
+
textEntryInteraction,
|
|
146
|
+
uploadInteraction,
|
|
147
|
+
} from "./interactions";
|
|
148
|
+
|
|
149
|
+
export {
|
|
150
|
+
AssociateReferenceSkin,
|
|
151
|
+
ChoiceReferenceSkin,
|
|
152
|
+
DrawingReferenceSkin,
|
|
153
|
+
EndAttemptReferenceSkin,
|
|
154
|
+
ExtendedTextReferenceSkin,
|
|
155
|
+
GapMatchReferenceSkin,
|
|
156
|
+
GraphicAssociateReferenceSkin,
|
|
157
|
+
GraphicGapMatchReferenceSkin,
|
|
158
|
+
GraphicOrderReferenceSkin,
|
|
159
|
+
GraphicStage,
|
|
160
|
+
HotspotReferenceSkin,
|
|
161
|
+
HottextReferenceSkin,
|
|
162
|
+
InlineChoiceReferenceSkin,
|
|
163
|
+
MatchReferenceSkin,
|
|
164
|
+
MediaReferenceSkin,
|
|
165
|
+
OrderReferenceSkin,
|
|
166
|
+
PositionObjectReferenceSkin,
|
|
167
|
+
SelectPointReferenceSkin,
|
|
168
|
+
SliderReferenceSkin,
|
|
169
|
+
TextEntryReferenceSkin,
|
|
170
|
+
UploadReferenceSkin,
|
|
171
|
+
referenceSkin,
|
|
172
|
+
textOf,
|
|
173
|
+
} from "./reference-skin";
|
|
40
174
|
|
|
41
175
|
export type {
|
|
176
|
+
AreaMapEntryView,
|
|
177
|
+
AreaMappingView,
|
|
42
178
|
Cardinality,
|
|
43
179
|
CorrectResponseView,
|
|
44
180
|
MapEntryView,
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
import { defineInteraction } from "../runtime";
|
|
4
|
+
import type { ResponseValue } from "../types";
|
|
5
|
+
|
|
6
|
+
const associateInteractionNodeSchema = z.object({
|
|
7
|
+
kind: z.literal("associateInteraction"),
|
|
8
|
+
responseIdentifier: z.string().min(1),
|
|
9
|
+
simpleAssociableChoices: z
|
|
10
|
+
.array(z.looseObject({ identifier: z.string().min(1), matchMax: z.number().int().optional() }))
|
|
11
|
+
.min(2),
|
|
12
|
+
maxAssociations: z.number().int().optional(),
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export const associateInteraction = defineInteraction({
|
|
16
|
+
kind: "associateInteraction",
|
|
17
|
+
schema: associateInteractionNodeSchema,
|
|
18
|
+
scoring: "qti-standard",
|
|
19
|
+
initialResponse(): ResponseValue {
|
|
20
|
+
return null;
|
|
21
|
+
},
|
|
22
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
import { defineInteraction } from "../runtime";
|
|
4
|
+
import type { ResponseValue } from "../types";
|
|
5
|
+
|
|
6
|
+
const drawingInteractionNodeSchema = z.object({
|
|
7
|
+
kind: z.literal("drawingInteraction"),
|
|
8
|
+
responseIdentifier: z.string().min(1),
|
|
9
|
+
object: z.object({
|
|
10
|
+
data: z.string().min(1),
|
|
11
|
+
width: z.number().optional(),
|
|
12
|
+
height: z.number().optional(),
|
|
13
|
+
type: z.string().optional(),
|
|
14
|
+
}),
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
export const drawingInteraction = defineInteraction({
|
|
18
|
+
kind: "drawingInteraction",
|
|
19
|
+
schema: drawingInteractionNodeSchema,
|
|
20
|
+
scoring: "qti-standard",
|
|
21
|
+
initialResponse(): ResponseValue {
|
|
22
|
+
return null;
|
|
23
|
+
},
|
|
24
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
import { defineInteraction } from "../runtime";
|
|
4
|
+
import type { ResponseValue } from "../types";
|
|
5
|
+
|
|
6
|
+
const endAttemptInteractionNodeSchema = z.object({
|
|
7
|
+
kind: z.literal("endAttemptInteraction"),
|
|
8
|
+
responseIdentifier: z.string().min(1),
|
|
9
|
+
title: z.string().min(1),
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
export const endAttemptInteraction = defineInteraction({
|
|
13
|
+
kind: "endAttemptInteraction",
|
|
14
|
+
schema: endAttemptInteractionNodeSchema,
|
|
15
|
+
scoring: "qti-standard",
|
|
16
|
+
initialResponse(): ResponseValue {
|
|
17
|
+
return null;
|
|
18
|
+
},
|
|
19
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
import { defineInteraction } from "../runtime";
|
|
4
|
+
import type { ResponseValue } from "../types";
|
|
5
|
+
|
|
6
|
+
const extendedTextInteractionNodeSchema = z.object({
|
|
7
|
+
kind: z.literal("extendedTextInteraction"),
|
|
8
|
+
responseIdentifier: z.string().min(1),
|
|
9
|
+
expectedLength: z.number().int().optional(),
|
|
10
|
+
expectedLines: z.number().int().optional(),
|
|
11
|
+
placeholderText: z.string().optional(),
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
export const extendedTextInteraction = defineInteraction({
|
|
15
|
+
kind: "extendedTextInteraction",
|
|
16
|
+
schema: extendedTextInteractionNodeSchema,
|
|
17
|
+
scoring: "qti-standard",
|
|
18
|
+
initialResponse(): ResponseValue {
|
|
19
|
+
return null;
|
|
20
|
+
},
|
|
21
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
import { defineInteraction } from "../runtime";
|
|
4
|
+
import type { ResponseValue } from "../types";
|
|
5
|
+
|
|
6
|
+
const gapMatchInteractionNodeSchema = z.object({
|
|
7
|
+
kind: z.literal("gapMatchInteraction"),
|
|
8
|
+
responseIdentifier: z.string().min(1),
|
|
9
|
+
gapTexts: z.array(z.looseObject({ identifier: z.string().min(1), matchMax: z.number().int().optional() })).min(1),
|
|
10
|
+
// Flow content with `kind: "gap"` nodes nested anywhere inside it. Responses are
|
|
11
|
+
// directedPairs gapText→gap.
|
|
12
|
+
content: z.array(z.looseObject({ kind: z.string().min(1) })).min(1),
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export const gapMatchInteraction = defineInteraction({
|
|
16
|
+
kind: "gapMatchInteraction",
|
|
17
|
+
schema: gapMatchInteractionNodeSchema,
|
|
18
|
+
scoring: "qti-standard",
|
|
19
|
+
initialResponse(): ResponseValue {
|
|
20
|
+
return null;
|
|
21
|
+
},
|
|
22
|
+
});
|