@longsightgroup/qti3-player 0.3.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +153 -13
- package/dist/index.d.ts +13 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/player-adapter.d.ts +62 -0
- package/dist/player-adapter.d.ts.map +1 -0
- package/dist/player-adapter.js +119 -0
- package/dist/player-adapter.js.map +1 -0
- package/dist/player-dev.d.ts +4 -0
- package/dist/player-dev.d.ts.map +1 -0
- package/dist/player-dev.js +14 -0
- package/dist/player-dev.js.map +1 -0
- package/dist/player-element.d.ts +14 -1
- package/dist/player-element.d.ts.map +1 -1
- package/dist/player-element.js +57 -5
- package/dist/player-element.js.map +1 -1
- package/dist/player-locale.d.ts +8 -3
- package/dist/player-locale.d.ts.map +1 -1
- package/dist/player-locale.js +16 -175
- package/dist/player-locale.js.map +1 -1
- package/dist/player-message-catalog-default.d.ts +4 -0
- package/dist/player-message-catalog-default.d.ts.map +1 -0
- package/dist/player-message-catalog-default.js +118 -0
- package/dist/player-message-catalog-default.js.map +1 -0
- package/dist/player-message-catalog-validate.d.ts +31 -0
- package/dist/player-message-catalog-validate.d.ts.map +1 -0
- package/dist/player-message-catalog-validate.js +327 -0
- package/dist/player-message-catalog-validate.js.map +1 -0
- package/dist/player-message-catalog.d.ts +18 -0
- package/dist/player-message-catalog.d.ts.map +1 -0
- package/dist/player-message-catalog.js +40 -0
- package/dist/player-message-catalog.js.map +1 -0
- package/dist/player-message-keys.d.ts +6 -0
- package/dist/player-message-keys.d.ts.map +1 -0
- package/dist/player-message-keys.js +7 -0
- package/dist/player-message-keys.js.map +1 -0
- package/dist/player-message-manifest.d.ts +272 -0
- package/dist/player-message-manifest.d.ts.map +1 -0
- package/dist/player-message-manifest.js +83 -0
- package/dist/player-message-manifest.js.map +1 -0
- package/dist/player-message-overrides.d.ts +3 -0
- package/dist/player-message-overrides.d.ts.map +1 -0
- package/dist/player-message-overrides.js +28 -0
- package/dist/player-message-overrides.js.map +1 -0
- package/dist/player-message-resolver.d.ts +31 -0
- package/dist/player-message-resolver.d.ts.map +1 -0
- package/dist/player-message-resolver.js +110 -0
- package/dist/player-message-resolver.js.map +1 -0
- package/dist/player-messages.d.ts +0 -38
- package/dist/player-messages.d.ts.map +1 -1
- package/dist/player-types.d.ts +12 -2
- package/dist/player-types.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/controls/remove-button.ts +8 -5
- package/src/index.ts +61 -5
- package/src/interactions/choice-interaction.ts +6 -2
- package/src/interactions/drawing-interaction.ts +14 -9
- package/src/interactions/end-attempt-interaction.ts +3 -3
- package/src/interactions/gap-match-interaction.ts +32 -13
- package/src/interactions/graphic-associate-interaction.ts +15 -10
- package/src/interactions/hotspot-interaction.ts +10 -6
- package/src/interactions/inline-choice-interaction.ts +4 -4
- package/src/interactions/interaction-registry.ts +12 -12
- package/src/interactions/match-interaction.ts +9 -6
- package/src/interactions/pair-interaction.ts +22 -14
- package/src/interactions/position-object-interaction.ts +22 -13
- package/src/interactions/select-point-interaction.ts +25 -13
- package/src/interactions/shared.ts +21 -4
- package/src/interactions/text-interaction.ts +14 -4
- package/src/interactions/upload-interaction.ts +6 -3
- package/src/player/content-state.ts +12 -1
- package/src/player/interaction-render.ts +4 -4
- package/src/player-adapter.ts +253 -0
- package/src/player-dev.ts +14 -0
- package/src/player-element.ts +78 -8
- package/src/player-locale.ts +28 -199
- package/src/player-message-catalog-default.ts +119 -0
- package/src/player-message-catalog-validate.ts +425 -0
- package/src/player-message-catalog.ts +72 -0
- package/src/player-message-keys.ts +12 -0
- package/src/player-message-manifest.ts +103 -0
- package/src/player-message-overrides.ts +38 -0
- package/src/player-message-resolver.ts +205 -0
- package/src/player-messages.ts +0 -30
- package/src/player-types.ts +15 -4
- package/src/reorder/a11y.ts +22 -7
- package/src/reorder/graphic-order-interaction.ts +23 -16
- package/src/reorder/list-controls.ts +8 -6
- package/src/reorder/order-interaction.ts +7 -5
- package/src/styles/base-styles.ts +20 -5
- package/src/styles/graphic-styles.ts +0 -6
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { QtiChoice, QtiInteraction, QtiValue } from "@longsightgroup/qti3-core";
|
|
2
2
|
import { removeButton } from "../controls/remove-button.js";
|
|
3
3
|
import { missingChoicesMessage, responseGroup, valueToStrings } from "../interaction-support.js";
|
|
4
|
-
import type {
|
|
4
|
+
import type { PlayerMessageResolver } from "../player-message-resolver.js";
|
|
5
5
|
import { parseUnlimitedMaximum } from "../response-limits.js";
|
|
6
6
|
import { choiceText, sourceChoices, targetChoices, tokenButton, tokenRegion } from "./shared.js";
|
|
7
7
|
|
|
@@ -9,7 +9,7 @@ export function renderMatchResponse(
|
|
|
9
9
|
interaction: QtiInteraction,
|
|
10
10
|
update: (value: QtiValue) => void,
|
|
11
11
|
currentValue: QtiValue,
|
|
12
|
-
messages:
|
|
12
|
+
messages: PlayerMessageResolver,
|
|
13
13
|
): HTMLElement {
|
|
14
14
|
const group = responseGroup();
|
|
15
15
|
|
|
@@ -26,13 +26,13 @@ export function renderMatchResponse(
|
|
|
26
26
|
|
|
27
27
|
const selector = document.createElement("div");
|
|
28
28
|
selector.className = "qti3-match-selector";
|
|
29
|
-
const sourceRegion = tokenRegion("
|
|
29
|
+
const sourceRegion = tokenRegion(messages.message("matchSourcesBank"));
|
|
30
30
|
sourceRegion.classList.add("qti3-match-source-bank");
|
|
31
|
-
const targetRegion = tokenRegion("
|
|
31
|
+
const targetRegion = tokenRegion(messages.message("matchTargetsBank"));
|
|
32
32
|
targetRegion.classList.add("qti3-match-target-bank");
|
|
33
33
|
const pairList = document.createElement("ul");
|
|
34
34
|
pairList.className = "qti3-pair-list";
|
|
35
|
-
pairList.setAttribute("aria-label", "
|
|
35
|
+
pairList.setAttribute("aria-label", messages.message("matchSelectedPairsList"));
|
|
36
36
|
|
|
37
37
|
const commit = () => {
|
|
38
38
|
if (interaction.responseCardinality === "single") update(selectedPairs[0] ?? null);
|
|
@@ -68,7 +68,10 @@ export function renderMatchResponse(
|
|
|
68
68
|
pairList.replaceChildren(
|
|
69
69
|
...selectedPairs.map((pair) => {
|
|
70
70
|
const [source, target] = pair.split(" ");
|
|
71
|
-
const label =
|
|
71
|
+
const label = messages.message("associationPairLabel", {
|
|
72
|
+
source: choiceText(sources, source),
|
|
73
|
+
target: choiceText(targets, target),
|
|
74
|
+
});
|
|
72
75
|
const item = document.createElement("li");
|
|
73
76
|
item.className = "qti3-pair-chip";
|
|
74
77
|
const text = document.createElement("span");
|
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
import type { QtiChoice, QtiInteraction, QtiValue } from "@longsightgroup/qti3-core";
|
|
2
2
|
import { removeButton } from "../controls/remove-button.js";
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
readableType,
|
|
6
|
-
responseGroup,
|
|
7
|
-
valueToStrings,
|
|
8
|
-
} from "../interaction-support.js";
|
|
9
|
-
import type { QtiPlayerMessages } from "../player-messages.js";
|
|
3
|
+
import { missingChoicesMessage, responseGroup, valueToStrings } from "../interaction-support.js";
|
|
4
|
+
import type { PlayerMessageResolver } from "../player-message-resolver.js";
|
|
10
5
|
import {
|
|
11
6
|
choiceText,
|
|
12
7
|
pairRegionLabels,
|
|
@@ -20,7 +15,7 @@ export function renderPairResponse(
|
|
|
20
15
|
interaction: QtiInteraction,
|
|
21
16
|
update: (value: QtiValue) => void,
|
|
22
17
|
currentValue: QtiValue,
|
|
23
|
-
messages:
|
|
18
|
+
messages: PlayerMessageResolver,
|
|
24
19
|
): HTMLElement {
|
|
25
20
|
const group = responseGroup();
|
|
26
21
|
|
|
@@ -33,15 +28,24 @@ export function renderPairResponse(
|
|
|
33
28
|
const selectedPairs: string[] = valueToStrings(currentValue);
|
|
34
29
|
let selectedSource: QtiChoice | undefined;
|
|
35
30
|
let selectedTarget: QtiChoice | undefined;
|
|
36
|
-
const labels = pairRegionLabels(interaction);
|
|
31
|
+
const labels = pairRegionLabels(interaction, messages);
|
|
37
32
|
|
|
38
|
-
const sourceRegion = tokenRegion(
|
|
39
|
-
|
|
33
|
+
const sourceRegion = tokenRegion(
|
|
34
|
+
messages.message("interactionSourcesBank", { type: interaction.type }),
|
|
35
|
+
labels.source,
|
|
36
|
+
);
|
|
37
|
+
const targetRegion = tokenRegion(
|
|
38
|
+
messages.message("interactionTargetsBank", { type: interaction.type }),
|
|
39
|
+
labels.target,
|
|
40
|
+
);
|
|
40
41
|
const selector = document.createElement("div");
|
|
41
42
|
selector.className = "qti3-pair-selector";
|
|
42
43
|
const pairList = document.createElement("ul");
|
|
43
44
|
pairList.className = "qti3-pair-list";
|
|
44
|
-
pairList.setAttribute(
|
|
45
|
+
pairList.setAttribute(
|
|
46
|
+
"aria-label",
|
|
47
|
+
messages.message("interactionSelectedPairsList", { type: interaction.type }),
|
|
48
|
+
);
|
|
45
49
|
let draggedSource: string | undefined;
|
|
46
50
|
|
|
47
51
|
const commit = () => {
|
|
@@ -84,11 +88,15 @@ export function renderPairResponse(
|
|
|
84
88
|
pairList.replaceChildren(
|
|
85
89
|
...selectedPairs.map((pair) => {
|
|
86
90
|
const [source, target] = pair.split(" ");
|
|
91
|
+
const label = messages.message("associationPairLabel", {
|
|
92
|
+
source: choiceText(sources, source),
|
|
93
|
+
target: choiceText(targets, target),
|
|
94
|
+
});
|
|
87
95
|
const item = document.createElement("li");
|
|
88
96
|
item.className = "qti3-pair-chip";
|
|
89
97
|
const text = document.createElement("span");
|
|
90
|
-
text.textContent =
|
|
91
|
-
const remove = removeButton(
|
|
98
|
+
text.textContent = label;
|
|
99
|
+
const remove = removeButton(label, messages);
|
|
92
100
|
remove.addEventListener("click", () => {
|
|
93
101
|
const index = selectedPairs.indexOf(pair);
|
|
94
102
|
if (index >= 0) selectedPairs.splice(index, 1);
|
|
@@ -6,11 +6,10 @@ import {
|
|
|
6
6
|
appendGraphicObjectImage,
|
|
7
7
|
objectIsImage,
|
|
8
8
|
percent,
|
|
9
|
-
readableType,
|
|
10
9
|
responseGroup,
|
|
11
10
|
} from "../interaction-support.js";
|
|
12
11
|
import { movementButton } from "../movement.js";
|
|
13
|
-
import type {
|
|
12
|
+
import type { PlayerMessageResolver } from "../player-message-resolver.js";
|
|
14
13
|
import {
|
|
15
14
|
objectAssetHeight,
|
|
16
15
|
objectAssetWidth,
|
|
@@ -22,11 +21,14 @@ export function renderPositionObjectResponse(
|
|
|
22
21
|
interaction: QtiInteraction,
|
|
23
22
|
update: (value: QtiValue) => void,
|
|
24
23
|
currentValue: QtiValue,
|
|
25
|
-
messages:
|
|
24
|
+
messages: PlayerMessageResolver,
|
|
26
25
|
): HTMLElement {
|
|
27
26
|
const group = responseGroup();
|
|
28
27
|
group.role = "group";
|
|
29
|
-
group.setAttribute(
|
|
28
|
+
group.setAttribute(
|
|
29
|
+
"aria-label",
|
|
30
|
+
messages.message("interactionPlacementResponse", { type: interaction.type }),
|
|
31
|
+
);
|
|
30
32
|
|
|
31
33
|
const stageObject = interaction.positionObjectStage ?? interaction.object;
|
|
32
34
|
const movableObject = interaction.positionObjectStage ? interaction.object : undefined;
|
|
@@ -43,7 +45,10 @@ export function renderPositionObjectResponse(
|
|
|
43
45
|
stage.style.setProperty("--qti3-position-object-marker-block-size", `${movableHeight}px`);
|
|
44
46
|
stage.tabIndex = 0;
|
|
45
47
|
stage.role = "group";
|
|
46
|
-
stage.setAttribute(
|
|
48
|
+
stage.setAttribute(
|
|
49
|
+
"aria-label",
|
|
50
|
+
messages.message("interactionPlacementStage", { type: interaction.type }),
|
|
51
|
+
);
|
|
47
52
|
|
|
48
53
|
if (stageObject?.data && objectIsImage(stageObject)) {
|
|
49
54
|
appendGraphicObjectImage(stage, stageObject, stageObject.text || "");
|
|
@@ -52,7 +57,7 @@ export function renderPositionObjectResponse(
|
|
|
52
57
|
const marker = document.createElement("button");
|
|
53
58
|
marker.type = "button";
|
|
54
59
|
marker.className = "qti3-position-object-marker";
|
|
55
|
-
marker.setAttribute("aria-label", messages.movableObject
|
|
60
|
+
marker.setAttribute("aria-label", messages.message("movableObject"));
|
|
56
61
|
applyPositionObjectMarkerSize(marker, movableWidth, movableHeight);
|
|
57
62
|
marker.draggable = false;
|
|
58
63
|
|
|
@@ -62,12 +67,13 @@ export function renderPositionObjectResponse(
|
|
|
62
67
|
image.alt = "";
|
|
63
68
|
marker.append(image);
|
|
64
69
|
} else {
|
|
65
|
-
marker.textContent = messages.placeObject
|
|
70
|
+
marker.textContent = messages.message("placeObject");
|
|
66
71
|
}
|
|
67
72
|
stage.append(marker);
|
|
68
73
|
|
|
69
74
|
const coordinate = document.createElement("output");
|
|
70
|
-
coordinate.className = "qti3-coordinate-output";
|
|
75
|
+
coordinate.className = "qti3-coordinate-output qti-visually-hidden";
|
|
76
|
+
coordinate.setAttribute("aria-live", "polite");
|
|
71
77
|
const clamp = () => {
|
|
72
78
|
point.x = Math.max(0, Math.min(width, point.x));
|
|
73
79
|
point.y = Math.max(0, Math.min(height, point.y));
|
|
@@ -90,10 +96,10 @@ export function renderPositionObjectResponse(
|
|
|
90
96
|
`calc(100% + ${Math.round(movableHeight / 2 + 8)}px)`,
|
|
91
97
|
);
|
|
92
98
|
coordinate.value = "";
|
|
93
|
-
coordinate.textContent = "
|
|
99
|
+
coordinate.textContent = messages.message("objectNotPlaced");
|
|
94
100
|
stage.setAttribute(
|
|
95
101
|
"aria-label",
|
|
96
|
-
|
|
102
|
+
messages.message("interactionPlacementStageEmpty", { type: interaction.type }),
|
|
97
103
|
);
|
|
98
104
|
return;
|
|
99
105
|
}
|
|
@@ -107,10 +113,11 @@ export function renderPositionObjectResponse(
|
|
|
107
113
|
`${percent(point.y, height)}%`,
|
|
108
114
|
);
|
|
109
115
|
coordinate.value = pointToString(point);
|
|
110
|
-
|
|
116
|
+
const coordinates = pointToString(point);
|
|
117
|
+
coordinate.textContent = messages.message("objectPositionedAt", { coordinates });
|
|
111
118
|
stage.setAttribute(
|
|
112
119
|
"aria-label",
|
|
113
|
-
|
|
120
|
+
messages.message("interactionPlacementStageAt", { type: interaction.type, coordinates }),
|
|
114
121
|
);
|
|
115
122
|
};
|
|
116
123
|
const pointFromPointer = (event: MouseEvent | PointerEvent) => {
|
|
@@ -200,7 +207,9 @@ export function renderPositionObjectResponse(
|
|
|
200
207
|
["down", 0, 1],
|
|
201
208
|
] as const) {
|
|
202
209
|
controls.append(
|
|
203
|
-
movementButton(direction, messages.moveObject
|
|
210
|
+
movementButton(direction, messages.message("moveObject", { direction }), () =>
|
|
211
|
+
moveBy(dx, dy),
|
|
212
|
+
),
|
|
204
213
|
);
|
|
205
214
|
}
|
|
206
215
|
|
|
@@ -5,11 +5,10 @@ import {
|
|
|
5
5
|
appendGraphicObjectImage,
|
|
6
6
|
objectHeight,
|
|
7
7
|
objectWidth,
|
|
8
|
-
readableType,
|
|
9
8
|
responseGroup,
|
|
10
9
|
} from "../interaction-support.js";
|
|
11
10
|
import { movementButton } from "../movement.js";
|
|
12
|
-
import type {
|
|
11
|
+
import type { PlayerMessageResolver } from "../player-message-resolver.js";
|
|
13
12
|
import { maximumAllowedResponses } from "../response-limits.js";
|
|
14
13
|
import { parsePointValues, pointToString } from "./point-value.js";
|
|
15
14
|
|
|
@@ -17,11 +16,14 @@ export function renderSelectPointResponse(
|
|
|
17
16
|
interaction: QtiInteraction,
|
|
18
17
|
update: (value: QtiValue) => void,
|
|
19
18
|
currentValue: QtiValue,
|
|
20
|
-
messages:
|
|
19
|
+
messages: PlayerMessageResolver,
|
|
21
20
|
): HTMLElement {
|
|
22
21
|
const group = responseGroup();
|
|
23
22
|
group.role = "group";
|
|
24
|
-
group.setAttribute(
|
|
23
|
+
group.setAttribute(
|
|
24
|
+
"aria-label",
|
|
25
|
+
messages.message("interactionCoordinateResponse", { type: interaction.type }),
|
|
26
|
+
);
|
|
25
27
|
const isMultiple = interaction.responseCardinality === "multiple";
|
|
26
28
|
const maxPoints = isMultiple ? maximumAllowedResponses(interaction) : 1;
|
|
27
29
|
|
|
@@ -33,7 +35,10 @@ export function renderSelectPointResponse(
|
|
|
33
35
|
objectHeight(interaction),
|
|
34
36
|
"qti3-point-surface",
|
|
35
37
|
);
|
|
36
|
-
surface.setAttribute(
|
|
38
|
+
surface.setAttribute(
|
|
39
|
+
"aria-label",
|
|
40
|
+
messages.message("interactionCoordinateArea", { type: interaction.type }),
|
|
41
|
+
);
|
|
37
42
|
|
|
38
43
|
const object = interaction.object;
|
|
39
44
|
if (object) {
|
|
@@ -45,7 +50,8 @@ export function renderSelectPointResponse(
|
|
|
45
50
|
let points = parsePointValues(currentValue);
|
|
46
51
|
let activeIndex = points.length > 0 ? points.length - 1 : -1;
|
|
47
52
|
const coordinate = document.createElement("output");
|
|
48
|
-
coordinate.className = "qti3-coordinate-output";
|
|
53
|
+
coordinate.className = "qti3-coordinate-output qti-visually-hidden";
|
|
54
|
+
coordinate.setAttribute("aria-live", "polite");
|
|
49
55
|
const initialPoint = () => ({
|
|
50
56
|
x: Math.round(width / 2),
|
|
51
57
|
y: Math.round(height / 2),
|
|
@@ -62,8 +68,11 @@ export function renderSelectPointResponse(
|
|
|
62
68
|
surface.querySelectorAll(".qti3-point-marker").forEach((marker) => marker.remove());
|
|
63
69
|
if (points.length === 0) {
|
|
64
70
|
coordinate.value = "";
|
|
65
|
-
coordinate.textContent = messages.noPointSelected
|
|
66
|
-
surface.setAttribute(
|
|
71
|
+
coordinate.textContent = messages.message("noPointSelected");
|
|
72
|
+
surface.setAttribute(
|
|
73
|
+
"aria-label",
|
|
74
|
+
messages.message("interactionCoordinateArea", { type: interaction.type }),
|
|
75
|
+
);
|
|
67
76
|
return;
|
|
68
77
|
}
|
|
69
78
|
points.forEach((point, index) => {
|
|
@@ -83,11 +92,14 @@ export function renderSelectPointResponse(
|
|
|
83
92
|
? points.map(pointToString).join(" | ")
|
|
84
93
|
: pointToString(points[0]);
|
|
85
94
|
coordinate.textContent = isMultiple
|
|
86
|
-
?
|
|
87
|
-
:
|
|
95
|
+
? messages.message("selectedPointsSummary", { count: points.length, coordinates: text })
|
|
96
|
+
: messages.message("selectedPointAt", { coordinates: pointToString(points[0]) });
|
|
88
97
|
surface.setAttribute(
|
|
89
98
|
"aria-label",
|
|
90
|
-
|
|
99
|
+
messages.message("interactionCoordinateAreaSelected", {
|
|
100
|
+
type: interaction.type,
|
|
101
|
+
coordinates: text,
|
|
102
|
+
}),
|
|
91
103
|
);
|
|
92
104
|
};
|
|
93
105
|
const clampPoint = (point: { x: number; y: number }) => {
|
|
@@ -158,7 +170,7 @@ export function renderSelectPointResponse(
|
|
|
158
170
|
["down", 0, 1],
|
|
159
171
|
] as const) {
|
|
160
172
|
controls.append(
|
|
161
|
-
movementButton(direction, messages.movePoint
|
|
173
|
+
movementButton(direction, messages.message("movePoint", { direction }), () => {
|
|
162
174
|
const point = mutableActivePoint();
|
|
163
175
|
point.x += dx;
|
|
164
176
|
point.y += dy;
|
|
@@ -171,7 +183,7 @@ export function renderSelectPointResponse(
|
|
|
171
183
|
if (isMultiple) {
|
|
172
184
|
const clear = document.createElement("button");
|
|
173
185
|
clear.type = "button";
|
|
174
|
-
clear.textContent = messages.clearPoints
|
|
186
|
+
clear.textContent = messages.message("clearPoints");
|
|
175
187
|
clear.addEventListener("click", () => {
|
|
176
188
|
points = [];
|
|
177
189
|
activeIndex = -1;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { QtiChoice, QtiInteraction } from "@longsightgroup/qti3-core";
|
|
2
|
+
import type { PlayerMessageResolver } from "../player-message-resolver.js";
|
|
2
3
|
import { interactionChoices } from "../interaction-support.js";
|
|
3
4
|
|
|
4
5
|
export function tokenRegion(label: string, visibleLabel?: string): HTMLElement {
|
|
@@ -49,8 +50,24 @@ export function targetChoices(interaction: QtiInteraction): QtiChoice[] {
|
|
|
49
50
|
return targets.length > 0 ? targets : choices;
|
|
50
51
|
}
|
|
51
52
|
|
|
52
|
-
export function pairRegionLabels(
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
export function pairRegionLabels(
|
|
54
|
+
interaction: QtiInteraction,
|
|
55
|
+
messages: PlayerMessageResolver,
|
|
56
|
+
): { source: string; target: string } {
|
|
57
|
+
if (interaction.type === "associate") {
|
|
58
|
+
return {
|
|
59
|
+
source: messages.message("associateFirstConceptRegion"),
|
|
60
|
+
target: messages.message("associatePairWithRegion"),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
if (interaction.type === "match") {
|
|
64
|
+
return {
|
|
65
|
+
source: messages.message("matchPromptRegion"),
|
|
66
|
+
target: messages.message("matchMatchRegion"),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
source: messages.message("genericSourceRegion"),
|
|
71
|
+
target: messages.message("genericTargetRegion"),
|
|
72
|
+
};
|
|
56
73
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { QtiInteraction, QtiValue } from "@longsightgroup/qti3-core";
|
|
2
|
+
import type { PlayerMessageResolver } from "../player-message-resolver.js";
|
|
2
3
|
|
|
3
4
|
function scalarString(value: QtiValue): string {
|
|
4
5
|
if (value === null || Array.isArray(value) || typeof value === "object") return "";
|
|
@@ -32,6 +33,7 @@ export function renderTextResponse(
|
|
|
32
33
|
update: (value: QtiValue) => void,
|
|
33
34
|
mode: "entry" | "extended",
|
|
34
35
|
currentValue: QtiValue,
|
|
36
|
+
messages: PlayerMessageResolver,
|
|
35
37
|
): HTMLElement {
|
|
36
38
|
const group = document.createElement("div");
|
|
37
39
|
group.className = "qti3-text-response";
|
|
@@ -43,7 +45,10 @@ export function renderTextResponse(
|
|
|
43
45
|
control.value = scalarString(currentValue);
|
|
44
46
|
control.setAttribute(
|
|
45
47
|
"aria-label",
|
|
46
|
-
interaction.prompt ??
|
|
48
|
+
interaction.prompt ??
|
|
49
|
+
(mode === "extended"
|
|
50
|
+
? messages.message("extendedTextResponseLabel")
|
|
51
|
+
: messages.message("textResponseLabel")),
|
|
47
52
|
);
|
|
48
53
|
if (mode === "extended" && expectedLines > 0) {
|
|
49
54
|
(control as HTMLTextAreaElement).rows = expectedLines;
|
|
@@ -60,7 +65,10 @@ export function renderTextResponse(
|
|
|
60
65
|
const value = control.value;
|
|
61
66
|
if (counter) {
|
|
62
67
|
const words = value.trim().length > 0 ? value.trim().split(/\s+/).length : 0;
|
|
63
|
-
counter.textContent =
|
|
68
|
+
counter.textContent = messages.message("extendedTextCounter", {
|
|
69
|
+
characters: value.length,
|
|
70
|
+
words,
|
|
71
|
+
});
|
|
64
72
|
}
|
|
65
73
|
if (emitResponse) update(value);
|
|
66
74
|
};
|
|
@@ -76,6 +84,7 @@ export function renderInlineTextEntry(
|
|
|
76
84
|
interaction: QtiInteraction,
|
|
77
85
|
update: (value: QtiValue) => void,
|
|
78
86
|
currentValue: QtiValue,
|
|
87
|
+
messages: PlayerMessageResolver,
|
|
79
88
|
): HTMLElement {
|
|
80
89
|
const group = document.createElement("span");
|
|
81
90
|
group.className = "qti3-inline-text-response";
|
|
@@ -84,7 +93,7 @@ export function renderInlineTextEntry(
|
|
|
84
93
|
input.value = scalarString(currentValue);
|
|
85
94
|
input.setAttribute(
|
|
86
95
|
"aria-label",
|
|
87
|
-
interaction.prompt ?? interaction.contextText ?? "
|
|
96
|
+
interaction.prompt ?? interaction.contextText ?? messages.message("textResponseLabel"),
|
|
88
97
|
);
|
|
89
98
|
const expectedLength = Number(interaction.attributes["expected-length"] ?? 0);
|
|
90
99
|
applyExpectedTextEntryWidth(input, expectedLength);
|
|
@@ -102,6 +111,7 @@ export function renderSliderResponse(
|
|
|
102
111
|
interaction: QtiInteraction,
|
|
103
112
|
update: (value: QtiValue) => void,
|
|
104
113
|
currentValue: QtiValue,
|
|
114
|
+
messages: PlayerMessageResolver,
|
|
105
115
|
): HTMLElement {
|
|
106
116
|
const group = document.createElement("div");
|
|
107
117
|
group.className = "qti3-slider-response";
|
|
@@ -111,7 +121,7 @@ export function renderSliderResponse(
|
|
|
111
121
|
input.max = interaction.attributes["upper-bound"] ?? "100";
|
|
112
122
|
input.step = interaction.attributes.step ?? "1";
|
|
113
123
|
input.value = scalarString(currentValue) || interaction.attributes["lower-bound"] || "0";
|
|
114
|
-
input.setAttribute("aria-label", interaction.prompt ?? "
|
|
124
|
+
input.setAttribute("aria-label", interaction.prompt ?? messages.message("sliderResponseLabel"));
|
|
115
125
|
const output = document.createElement("output");
|
|
116
126
|
output.className = "qti3-slider-output";
|
|
117
127
|
output.value = input.value;
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
import type { QtiInteraction, QtiValue } from "@longsightgroup/qti3-core";
|
|
2
|
-
import type {
|
|
2
|
+
import type { PlayerMessageResolver } from "../player-message-resolver.js";
|
|
3
3
|
import { interactionLabel } from "./interaction-label.js";
|
|
4
4
|
|
|
5
5
|
export function renderUploadResponse(
|
|
6
6
|
interaction: QtiInteraction,
|
|
7
7
|
update: (value: QtiValue) => void,
|
|
8
|
-
messages:
|
|
8
|
+
messages: PlayerMessageResolver,
|
|
9
9
|
): HTMLElement {
|
|
10
10
|
const input = document.createElement("input");
|
|
11
11
|
input.type = "file";
|
|
12
12
|
input.className = "qti3-upload-input";
|
|
13
|
-
input.setAttribute(
|
|
13
|
+
input.setAttribute(
|
|
14
|
+
"aria-label",
|
|
15
|
+
interactionLabel(interaction) || messages.message("uploadResponse"),
|
|
16
|
+
);
|
|
14
17
|
input.addEventListener("change", () => update(input.files?.[0]?.name ?? ""));
|
|
15
18
|
return input;
|
|
16
19
|
}
|
|
@@ -36,7 +36,18 @@ export function isFeedbackVisible(
|
|
|
36
36
|
return node.showHide === "show" ? hasIdentifier : !hasIdentifier;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
interface TemplateContentVisibilityElement {
|
|
40
|
+
dataset: {
|
|
41
|
+
templateIdentifier?: string;
|
|
42
|
+
templateValueIdentifier?: string;
|
|
43
|
+
showHide?: string;
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function isTemplateContentVisible(
|
|
48
|
+
element: TemplateContentVisibilityElement,
|
|
49
|
+
value: QtiValue,
|
|
50
|
+
): boolean {
|
|
40
51
|
const templateIdentifier = element.dataset.templateIdentifier;
|
|
41
52
|
const identifier = element.dataset.templateValueIdentifier;
|
|
42
53
|
if (!templateIdentifier || !identifier) return true;
|
|
@@ -5,12 +5,12 @@ import { interactionLabel, qtiSharedClassNames } from "../interactions/interacti
|
|
|
5
5
|
import { renderSelect } from "../interactions/inline-choice-interaction.js";
|
|
6
6
|
import { renderInlineTextEntry } from "../interactions/text-interaction.js";
|
|
7
7
|
import { renderUnsupportedEmbeddedInteraction } from "../interactions/unsupported-interaction.js";
|
|
8
|
-
import type {
|
|
8
|
+
import type { PlayerMessageResolver } from "../player-message-resolver.js";
|
|
9
9
|
import { inlineValidationMessageElement, validationMessageElement } from "../player-validation.js";
|
|
10
10
|
|
|
11
11
|
export interface BlockInteractionRenderOptions {
|
|
12
12
|
interaction: QtiInteraction;
|
|
13
|
-
messages:
|
|
13
|
+
messages: PlayerMessageResolver;
|
|
14
14
|
update: (value: QtiValue) => void;
|
|
15
15
|
currentValue: QtiValue;
|
|
16
16
|
isCompleted: () => boolean;
|
|
@@ -65,7 +65,7 @@ export function renderEmbeddedInteractionSection(
|
|
|
65
65
|
interaction: QtiInteraction,
|
|
66
66
|
update: (value: QtiValue) => void,
|
|
67
67
|
currentValue: QtiValue,
|
|
68
|
-
messages:
|
|
68
|
+
messages: PlayerMessageResolver,
|
|
69
69
|
): HTMLElement {
|
|
70
70
|
if (interaction.type !== "inlineChoice" && interaction.type !== "textEntry") {
|
|
71
71
|
return renderUnsupportedEmbeddedInteraction(interaction);
|
|
@@ -83,7 +83,7 @@ export function renderEmbeddedInteractionSection(
|
|
|
83
83
|
wrapper.append(
|
|
84
84
|
interaction.type === "inlineChoice"
|
|
85
85
|
? renderSelect(interaction, update, currentValue, messages)
|
|
86
|
-
: renderInlineTextEntry(interaction, update, currentValue),
|
|
86
|
+
: renderInlineTextEntry(interaction, update, currentValue, messages),
|
|
87
87
|
);
|
|
88
88
|
return wrapper;
|
|
89
89
|
}
|