@conform-ed/qti-react 0.0.13 → 0.0.14
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 +149 -159
- 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 +1 -2
- 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
|
@@ -0,0 +1,17 @@
|
|
|
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
|
+
export type CapabilityIssueType = "unsupported-interaction" | "invalid-interaction" | "unsupported-element" | "unsupported-rp";
|
|
7
|
+
export interface CapabilityIssue {
|
|
8
|
+
readonly type: CapabilityIssueType;
|
|
9
|
+
/** The interaction kind, element name, or RP rule/operator/template at issue. */
|
|
10
|
+
readonly name: string;
|
|
11
|
+
readonly responseIdentifier?: string;
|
|
12
|
+
readonly detail?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface CapabilityReport {
|
|
15
|
+
readonly deliverable: boolean;
|
|
16
|
+
readonly issues: readonly CapabilityIssue[];
|
|
17
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The single source of truth for what may appear inside an item/stimulus body.
|
|
3
|
+
*
|
|
4
|
+
* Both the renderer's allowlist tree-walk and (later) the authoring editor schema
|
|
5
|
+
* derive from this definition. QTI 3 bodies are validated for *structure* by
|
|
6
|
+
* `@conform-ed/contracts`, but their embedded HTML flow content is modelled as a
|
|
7
|
+
* generic node tree — so validation does not sanitize. This allowlist is the
|
|
8
|
+
* sanitizer: the renderer emits React only for elements/attributes named here and
|
|
9
|
+
* drops everything else. It never injects HTML strings.
|
|
10
|
+
*
|
|
11
|
+
* v0 scope: the minimal flow/inline vocabulary plus the language-critical bits
|
|
12
|
+
* (ruby/furigana, MathML). It grows incrementally with the renderer — never "all of
|
|
13
|
+
* HTML5 at once".
|
|
14
|
+
*/
|
|
15
|
+
/** Interaction node kinds conform-ed ships descriptors and Reference Skins for. */
|
|
16
|
+
export declare const v0InteractionKinds: readonly ["associateInteraction", "choiceInteraction", "drawingInteraction", "endAttemptInteraction", "extendedTextInteraction", "gapMatchInteraction", "graphicAssociateInteraction", "graphicGapMatchInteraction", "graphicOrderInteraction", "hotspotInteraction", "hottextInteraction", "inlineChoiceInteraction", "matchInteraction", "mediaInteraction", "orderInteraction", "positionObjectStage", "selectPointInteraction", "sliderInteraction", "textEntryInteraction", "uploadInteraction"];
|
|
17
|
+
export type V0InteractionKind = (typeof v0InteractionKinds)[number];
|
|
18
|
+
export interface ContentModel {
|
|
19
|
+
readonly interactionKinds: ReadonlySet<string>;
|
|
20
|
+
readonly flowElements: ReadonlySet<string>;
|
|
21
|
+
readonly mathRoot: string;
|
|
22
|
+
readonly globalAttributes: ReadonlySet<string>;
|
|
23
|
+
/** Per-element attribute allowlists, additive to `globalAttributes`. */
|
|
24
|
+
readonly elementAttributes: ReadonlyMap<string, ReadonlySet<string>>;
|
|
25
|
+
/** Attributes whose values are asset references, routed through the Asset Resolver. */
|
|
26
|
+
readonly urlAttributes: ReadonlySet<string>;
|
|
27
|
+
}
|
|
28
|
+
export declare const v0ContentModel: ContentModel;
|
|
29
|
+
export declare function isAllowedFlowElement(model: ContentModel, name: string): boolean;
|
|
30
|
+
export declare function isInteractionKind(model: ContentModel, kind: string): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Reduce a raw attribute bag to the safe, allowlisted subset for one element. Used by
|
|
33
|
+
* the body walk so a node that validates against QTI structure still cannot carry
|
|
34
|
+
* script or handlers. The allowlist is the global set plus the element's own entries.
|
|
35
|
+
*/
|
|
36
|
+
export declare function sanitizeAttributes(model: ContentModel, elementName: string, attributes: Record<string, unknown> | undefined): Record<string, string>;
|
|
37
|
+
/**
|
|
38
|
+
* Attribute hardening for MathML subtrees: presentation attributes (mathvariant,
|
|
39
|
+
* linethickness, …) are not individually allowlisted — MathML has no scripting surface
|
|
40
|
+
* once event handlers and javascript: URLs are stripped.
|
|
41
|
+
*/
|
|
42
|
+
export declare function sanitizeMathAttributes(attributes: Record<string, unknown> | undefined): Record<string, string>;
|
|
@@ -0,0 +1,23 @@
|
|
|
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
|
+
export type QtiShape = "circle" | "rect" | "poly" | "ellipse" | "default";
|
|
13
|
+
export interface Point {
|
|
14
|
+
readonly x: number;
|
|
15
|
+
readonly y: number;
|
|
16
|
+
}
|
|
17
|
+
/** Parse a QTI coords attribute ("10,20,30") into numbers. */
|
|
18
|
+
export declare function parseCoords(coords: string): number[];
|
|
19
|
+
/** Parse a QTI point value ("x y") or null when malformed. */
|
|
20
|
+
export declare function parsePoint(value: string): Point | null;
|
|
21
|
+
export declare function formatPoint(point: Point): string;
|
|
22
|
+
/** QTI hit test: is `point` inside the area described by (shape, coords)? */
|
|
23
|
+
export declare function pointInShape(shape: string, coords: readonly number[], point: Point): boolean;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare const qtiReactPackageName = "@conform-ed/qti-react";
|
|
2
|
+
export { v0ContentModel, v0InteractionKinds, isAllowedFlowElement, isInteractionKind, sanitizeAttributes, type ContentModel, type V0InteractionKind, } from "./content-model";
|
|
3
|
+
export { foldString, mapResponse, matchCorrect, mapResponsePoint, scoreResponse } from "./response-processing";
|
|
4
|
+
export { assessmentItemViewFromNormalized, assessmentTestViewFromNormalized } from "./normalized-item";
|
|
5
|
+
export { formatPoint, parseCoords, parsePoint, pointInShape, type Point, type QtiShape } from "./graphic";
|
|
6
|
+
export { applyCorrectResponseOverrides, collectRpIssues, collectTemplateIssues, executeResponseProcessing, executeTemplateProcessing, mulberry32, resolveTemplate, } from "./rp";
|
|
7
|
+
export type { CustomOperatorImplementation, MaybeRpValue, OutcomeDeclarationView, OutcomeValue, ResponseNormalization, ResponseProcessingContext, ResponseProcessingResult, ResponseProcessingView, RpConditionBranch, RpExpressionView, RpRecordField, RpRuleView, RpScalar, RpValue, TemplateConditionBranch, TemplateDeclarationView, TemplateProcessingContext, TemplateProcessingResult, TemplateProcessingView, TemplateRuleView, } from "./rp";
|
|
8
|
+
export { createAttemptStore, type AttemptSnapshot, type AttemptStore, type AttemptStoreOptions } from "./store";
|
|
9
|
+
export { createTestController, createTestSessionStore, type TestSessionSnapshot, type TestSessionStore, type TestSessionStoreOptions, type AssessmentItemRefView, type AssessmentSectionView, type AssessmentTestView, type BranchRuleView, type ItemSessionControlView, type OutcomeConditionBranch, type OutcomeRuleView, type TestController, type TestFeedbackView, type TestItemResult, type TestPartView, type TestPlan, type TestPlanItem, type TestPlanPart, type TestSessionState, type TimeLimitsView, } from "./test";
|
|
10
|
+
export { createQtiRuntime, defineInteraction, type AssessmentItemView, type AttemptController, type BodyNode, type CapabilityIssue, type CapabilityIssueType, type CapabilityReport, type ContentRendererProps, type FeedbackView, type InteractionDescriptor, type InteractionNode, type InteractionRenderProps, type InteractionSkin, type InteractionStatus, type ItemRendererProps, type NodeOverrides, type OptionProps, type OptionStatus, type QtiRuntime, type QtiRuntimeConfig, type SkinRegistry, type XmlContentNode, } from "./runtime";
|
|
11
|
+
export { createPciModuleRegistry, createPciSkin, mountPci, pciResponseToValue, portableCustomInteraction, serializePciMarkup, valueToPciResponse, type PciConfiguration, type PciInstance, type PciInteractionNode, type PciModule, type PciModuleRegistry, type PciModuleRegistryOptions, type PciMountHandle, type PciMountOptions, type PciSkinOptions, } from "./pci";
|
|
12
|
+
export { associateInteraction, choiceInteraction, drawingInteraction, endAttemptInteraction, extendedTextInteraction, gapMatchInteraction, graphicAssociateInteraction, graphicGapMatchInteraction, graphicOrderInteraction, hotspotInteraction, hottextInteraction, inlineChoiceInteraction, matchInteraction, mediaInteraction, orderInteraction, positionObjectStage, qtiCoreInteractions, selectPointInteraction, sliderInteraction, textEntryInteraction, uploadInteraction, } from "./interactions";
|
|
13
|
+
export { AssociateReferenceSkin, ChoiceReferenceSkin, DrawingReferenceSkin, EndAttemptReferenceSkin, ExtendedTextReferenceSkin, GapMatchReferenceSkin, GraphicAssociateReferenceSkin, GraphicGapMatchReferenceSkin, GraphicOrderReferenceSkin, GraphicStage, HotspotReferenceSkin, HottextReferenceSkin, InlineChoiceReferenceSkin, MatchReferenceSkin, MediaReferenceSkin, OrderReferenceSkin, PositionObjectReferenceSkin, SelectPointReferenceSkin, SliderReferenceSkin, TextEntryReferenceSkin, UploadReferenceSkin, referenceSkin, textOf, } from "./reference-skin";
|
|
14
|
+
export type { AreaMapEntryView, AreaMappingView, Cardinality, CorrectResponseView, MapEntryView, MappingView, ResponseDeclarationView, ResponseValue, ScoreResult, } from "./types";
|
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;
|
|
@@ -3384,7 +3368,7 @@ async function mountPci(options) {
|
|
|
3384
3368
|
...options.declaration ? { boundTo: { [node.responseIdentifier]: valueToPciResponse(options.boundValue ?? null, options.declaration) } } : {},
|
|
3385
3369
|
status: "interacting",
|
|
3386
3370
|
onready: (instance2) => resolveReady(instance2),
|
|
3387
|
-
ondone: (
|
|
3371
|
+
ondone: (_instance, response, state) => options.ondone?.(pciResponseToValue(response), state)
|
|
3388
3372
|
};
|
|
3389
3373
|
const returned = module.getInstance(container, configuration, options.state);
|
|
3390
3374
|
if (returned) {
|
|
@@ -3919,7 +3903,12 @@ function ChoiceReferenceSkin(props) {
|
|
|
3919
3903
|
}
|
|
3920
3904
|
|
|
3921
3905
|
// src/reference-skin/drawing.ts
|
|
3922
|
-
import {
|
|
3906
|
+
import {
|
|
3907
|
+
createElement as createElement5,
|
|
3908
|
+
useCallback,
|
|
3909
|
+
useEffect as useEffect2,
|
|
3910
|
+
useRef as useRef2
|
|
3911
|
+
} from "react";
|
|
3923
3912
|
var strokeStyle = "#c2410c";
|
|
3924
3913
|
var strokeWidth = 3;
|
|
3925
3914
|
function DrawingReferenceSkin(props) {
|
|
@@ -3930,7 +3919,8 @@ function DrawingReferenceSkin(props) {
|
|
|
3930
3919
|
const drawingRef = useRef2(false);
|
|
3931
3920
|
const propsRef = useRef2(props);
|
|
3932
3921
|
propsRef.current = props;
|
|
3933
|
-
const
|
|
3922
|
+
const stageData = node.object.data;
|
|
3923
|
+
const paintBackground = useCallback((canvas) => {
|
|
3934
3924
|
const context = canvas.getContext("2d");
|
|
3935
3925
|
if (!context) {
|
|
3936
3926
|
return;
|
|
@@ -3940,13 +3930,13 @@ function DrawingReferenceSkin(props) {
|
|
|
3940
3930
|
image.onload = () => {
|
|
3941
3931
|
context.drawImage(image, 0, 0, canvas.width, canvas.height);
|
|
3942
3932
|
};
|
|
3943
|
-
image.src = propsRef.current.resolveAsset(
|
|
3944
|
-
};
|
|
3933
|
+
image.src = propsRef.current.resolveAsset(stageData);
|
|
3934
|
+
}, [stageData]);
|
|
3945
3935
|
useEffect2(() => {
|
|
3946
3936
|
if (canvasRef.current) {
|
|
3947
3937
|
paintBackground(canvasRef.current);
|
|
3948
3938
|
}
|
|
3949
|
-
}, [
|
|
3939
|
+
}, [paintBackground]);
|
|
3950
3940
|
const pointerPosition = (event) => {
|
|
3951
3941
|
const canvas = event.currentTarget;
|
|
3952
3942
|
const rect = canvas.getBoundingClientRect();
|
|
@@ -4359,124 +4349,27 @@ function HotspotReferenceSkin(props) {
|
|
|
4359
4349
|
});
|
|
4360
4350
|
}
|
|
4361
4351
|
|
|
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
4352
|
// src/reference-skin/hottext.ts
|
|
4460
|
-
import { createElement as
|
|
4353
|
+
import { createElement as createElement14 } from "react";
|
|
4461
4354
|
function HottextReferenceSkin(props) {
|
|
4462
4355
|
const node = props.node;
|
|
4463
|
-
return
|
|
4356
|
+
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
4357
|
hottext: (child, key) => {
|
|
4465
4358
|
const view = child;
|
|
4466
4359
|
const identifier = view.identifier ?? "";
|
|
4467
4360
|
const optionProps = props.getOptionProps(identifier);
|
|
4468
|
-
return
|
|
4361
|
+
return createElement14("button", { key, type: "button", disabled: props.disabled, "data-qti-hottext": identifier, ...optionProps }, props.renderContent(view.content) ?? identifier);
|
|
4469
4362
|
}
|
|
4470
4363
|
}));
|
|
4471
4364
|
}
|
|
4472
4365
|
|
|
4473
4366
|
// src/reference-skin/inline-choice.ts
|
|
4474
|
-
import { createElement as
|
|
4367
|
+
import { createElement as createElement15 } from "react";
|
|
4475
4368
|
function InlineChoiceReferenceSkin(props) {
|
|
4476
4369
|
const node = props.node;
|
|
4477
4370
|
const choices = node.inlineChoices ?? [];
|
|
4478
4371
|
const value = typeof props.value === "string" ? props.value : "";
|
|
4479
|
-
return
|
|
4372
|
+
return createElement15("select", {
|
|
4480
4373
|
value,
|
|
4481
4374
|
disabled: props.disabled,
|
|
4482
4375
|
"aria-disabled": props.disabled,
|
|
@@ -4485,11 +4378,11 @@ function InlineChoiceReferenceSkin(props) {
|
|
|
4485
4378
|
onChange: (event) => {
|
|
4486
4379
|
props.setValue(event.target.value === "" ? null : event.target.value);
|
|
4487
4380
|
}
|
|
4488
|
-
},
|
|
4381
|
+
}, createElement15("option", { key: "", value: "" }, ""), choices.map((choice) => createElement15("option", { key: choice.identifier, value: choice.identifier }, textOf(choice.content))));
|
|
4489
4382
|
}
|
|
4490
4383
|
|
|
4491
4384
|
// src/reference-skin/match.ts
|
|
4492
|
-
import { createElement as
|
|
4385
|
+
import { createElement as createElement16 } from "react";
|
|
4493
4386
|
function MatchReferenceSkin(props) {
|
|
4494
4387
|
const node = props.node;
|
|
4495
4388
|
const rows = node.simpleMatchSets?.[0]?.simpleAssociableChoices ?? [];
|
|
@@ -4498,9 +4391,9 @@ function MatchReferenceSkin(props) {
|
|
|
4498
4391
|
function togglePair(pair) {
|
|
4499
4392
|
props.setValue(pairs.includes(pair) ? pairs.filter((entry) => entry !== pair) : [...pairs, pair]);
|
|
4500
4393
|
}
|
|
4501
|
-
return
|
|
4394
|
+
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
4395
|
const pair = `${row.identifier} ${column.identifier}`;
|
|
4503
|
-
return
|
|
4396
|
+
return createElement16("td", { key: column.identifier }, createElement16("input", {
|
|
4504
4397
|
type: "checkbox",
|
|
4505
4398
|
checked: pairs.includes(pair),
|
|
4506
4399
|
disabled: props.disabled,
|
|
@@ -4512,7 +4405,7 @@ function MatchReferenceSkin(props) {
|
|
|
4512
4405
|
}
|
|
4513
4406
|
|
|
4514
4407
|
// src/reference-skin/media.ts
|
|
4515
|
-
import { createElement as
|
|
4408
|
+
import { createElement as createElement17 } from "react";
|
|
4516
4409
|
function findMediaElement(nodes) {
|
|
4517
4410
|
for (const node of nodes ?? []) {
|
|
4518
4411
|
if (node.kind === "xml") {
|
|
@@ -4534,10 +4427,10 @@ function MediaReferenceSkin(props) {
|
|
|
4534
4427
|
const plays = typeof props.value === "string" ? Number(props.value) || 0 : 0;
|
|
4535
4428
|
const playsExhausted = node.maxPlays !== undefined && node.maxPlays > 0 && plays >= node.maxPlays;
|
|
4536
4429
|
if (!media) {
|
|
4537
|
-
return
|
|
4430
|
+
return createElement17("div", { "data-qti-interaction": "mediaInteraction", "data-status": props.status }, "No media element.");
|
|
4538
4431
|
}
|
|
4539
4432
|
const src = typeof media.attributes?.["src"] === "string" ? props.resolveAsset(media.attributes["src"]) : undefined;
|
|
4540
|
-
return
|
|
4433
|
+
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
4434
|
src,
|
|
4542
4435
|
controls: true,
|
|
4543
4436
|
loop: node.loop ?? false,
|
|
@@ -4548,11 +4441,11 @@ function MediaReferenceSkin(props) {
|
|
|
4548
4441
|
}
|
|
4549
4442
|
props.setValue(String(plays + 1));
|
|
4550
4443
|
}
|
|
4551
|
-
}), playsExhausted ?
|
|
4444
|
+
}), playsExhausted ? createElement17("p", { role: "status" }, "No plays remaining.") : null);
|
|
4552
4445
|
}
|
|
4553
4446
|
|
|
4554
4447
|
// src/reference-skin/order.ts
|
|
4555
|
-
import { createElement as
|
|
4448
|
+
import { createElement as createElement18 } from "react";
|
|
4556
4449
|
function OrderReferenceSkin(props) {
|
|
4557
4450
|
const node = props.node;
|
|
4558
4451
|
const choices = node.simpleChoices ?? [];
|
|
@@ -4570,12 +4463,12 @@ function OrderReferenceSkin(props) {
|
|
|
4570
4463
|
next[target] = moved;
|
|
4571
4464
|
props.setValue(next);
|
|
4572
4465
|
}
|
|
4573
|
-
return
|
|
4466
|
+
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
4467
|
type: "button",
|
|
4575
4468
|
"aria-label": `Move ${identifier} up`,
|
|
4576
4469
|
disabled: props.disabled || index === 0,
|
|
4577
4470
|
onClick: () => move(index, -1)
|
|
4578
|
-
}, "↑"),
|
|
4471
|
+
}, "↑"), createElement18("button", {
|
|
4579
4472
|
type: "button",
|
|
4580
4473
|
"aria-label": `Move ${identifier} down`,
|
|
4581
4474
|
disabled: props.disabled || index === order.length - 1,
|
|
@@ -4583,6 +4476,103 @@ function OrderReferenceSkin(props) {
|
|
|
4583
4476
|
}, "↓")))));
|
|
4584
4477
|
}
|
|
4585
4478
|
|
|
4479
|
+
// src/reference-skin/position-object.ts
|
|
4480
|
+
import { createElement as createElement19 } from "react";
|
|
4481
|
+
function PositionObjectReferenceSkin(props) {
|
|
4482
|
+
const node = props.node;
|
|
4483
|
+
const maxChoices = node.maxChoices ?? 1;
|
|
4484
|
+
const points = props.value === null ? [] : typeof props.value === "string" ? [props.value] : Array.isArray(props.value) ? [...props.value] : [];
|
|
4485
|
+
if (!node.stageObject || !node.object) {
|
|
4486
|
+
return null;
|
|
4487
|
+
}
|
|
4488
|
+
const movable = node.object;
|
|
4489
|
+
function stageClick(point) {
|
|
4490
|
+
if (props.disabled) {
|
|
4491
|
+
return;
|
|
4492
|
+
}
|
|
4493
|
+
const formatted = formatPoint(point);
|
|
4494
|
+
if (maxChoices === 1) {
|
|
4495
|
+
props.setValue(formatted);
|
|
4496
|
+
return;
|
|
4497
|
+
}
|
|
4498
|
+
if (points.length < maxChoices) {
|
|
4499
|
+
props.setValue([...points, formatted]);
|
|
4500
|
+
}
|
|
4501
|
+
}
|
|
4502
|
+
return createElement19(GraphicStage, {
|
|
4503
|
+
object: node.stageObject,
|
|
4504
|
+
resolveAsset: props.resolveAsset,
|
|
4505
|
+
interaction: "positionObjectStage",
|
|
4506
|
+
status: props.status,
|
|
4507
|
+
onStageClick: stageClick,
|
|
4508
|
+
prompt: node.prompt ? createElement19("div", { "data-qti-prompt": true }, props.renderContent(node.prompt.content)) : null,
|
|
4509
|
+
overlay: points.map((value, index) => {
|
|
4510
|
+
const point = parsePoint(value);
|
|
4511
|
+
if (!point) {
|
|
4512
|
+
return null;
|
|
4513
|
+
}
|
|
4514
|
+
return createElement19("image", {
|
|
4515
|
+
key: `${value}-${index}`,
|
|
4516
|
+
href: props.resolveAsset(movable.data),
|
|
4517
|
+
x: point.x - (movable.width ?? 0) / 2,
|
|
4518
|
+
y: point.y - (movable.height ?? 0) / 2,
|
|
4519
|
+
width: movable.width,
|
|
4520
|
+
height: movable.height,
|
|
4521
|
+
"data-qti-point": value,
|
|
4522
|
+
style: { pointerEvents: "none" }
|
|
4523
|
+
});
|
|
4524
|
+
})
|
|
4525
|
+
});
|
|
4526
|
+
}
|
|
4527
|
+
|
|
4528
|
+
// src/reference-skin/select-point.ts
|
|
4529
|
+
import { Fragment as Fragment5, createElement as createElement20 } from "react";
|
|
4530
|
+
function SelectPointReferenceSkin(props) {
|
|
4531
|
+
const node = props.node;
|
|
4532
|
+
const maxChoices = node.maxChoices ?? 1;
|
|
4533
|
+
const points = props.value === null ? [] : typeof props.value === "string" ? [props.value] : Array.isArray(props.value) ? [...props.value] : [];
|
|
4534
|
+
if (!node.object) {
|
|
4535
|
+
return null;
|
|
4536
|
+
}
|
|
4537
|
+
function stageClick(point) {
|
|
4538
|
+
if (props.disabled) {
|
|
4539
|
+
return;
|
|
4540
|
+
}
|
|
4541
|
+
const formatted = formatPoint(point);
|
|
4542
|
+
if (maxChoices === 1) {
|
|
4543
|
+
props.setValue(formatted);
|
|
4544
|
+
return;
|
|
4545
|
+
}
|
|
4546
|
+
if (points.length < maxChoices) {
|
|
4547
|
+
props.setValue([...points, formatted]);
|
|
4548
|
+
}
|
|
4549
|
+
}
|
|
4550
|
+
return createElement20(GraphicStage, {
|
|
4551
|
+
object: node.object,
|
|
4552
|
+
resolveAsset: props.resolveAsset,
|
|
4553
|
+
interaction: "selectPointInteraction",
|
|
4554
|
+
status: props.status,
|
|
4555
|
+
onStageClick: stageClick,
|
|
4556
|
+
prompt: node.prompt ? createElement20("div", { "data-qti-prompt": true }, props.renderContent(node.prompt.content)) : null,
|
|
4557
|
+
overlay: points.map((value, index) => {
|
|
4558
|
+
const [x, y] = value.split(/\s+/u).map(Number);
|
|
4559
|
+
return createElement20(Fragment5, { key: `${value}-${index}` }, createElement20("circle", {
|
|
4560
|
+
cx: x,
|
|
4561
|
+
cy: y,
|
|
4562
|
+
r: 5,
|
|
4563
|
+
fill: "currentColor",
|
|
4564
|
+
"data-qti-point": value,
|
|
4565
|
+
style: { pointerEvents: "none" }
|
|
4566
|
+
}));
|
|
4567
|
+
}),
|
|
4568
|
+
after: createElement20("button", {
|
|
4569
|
+
type: "button",
|
|
4570
|
+
disabled: props.disabled || points.length === 0,
|
|
4571
|
+
onClick: () => props.setValue(null)
|
|
4572
|
+
}, "Clear points")
|
|
4573
|
+
});
|
|
4574
|
+
}
|
|
4575
|
+
|
|
4586
4576
|
// src/reference-skin/slider.ts
|
|
4587
4577
|
import { createElement as createElement21 } from "react";
|
|
4588
4578
|
function SliderReferenceSkin(props) {
|