@webwriter/quiz 1.1.3 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/publish.yml +8 -0
- package/LICENSE +6 -6
- package/README.md +778 -0
- package/assets/icons/grip-horizontal.svg +8 -0
- package/assets/icons/grip-vertical.svg +8 -0
- package/assets/icons/truefalse.svg +6 -0
- package/custom-elements.json +2779 -0
- package/custom.d.ts +180 -176
- package/dist/api/index.d.ts +27 -0
- package/dist/api/index.js +40 -0
- package/dist/migrate.js +1434 -0
- package/dist/widgets/webwriter-choice-item.js +4036 -3895
- package/dist/widgets/webwriter-choice.js +2269 -5821
- package/dist/widgets/webwriter-gap-item.js +3987 -0
- package/dist/widgets/webwriter-gap.js +2908 -0
- package/dist/widgets/webwriter-mark.js +4335 -3489
- package/dist/widgets/webwriter-order-item.js +4014 -3306
- package/dist/widgets/webwriter-order.js +2944 -4386
- package/dist/widgets/webwriter-pairing-item.js +1058 -2421
- package/dist/widgets/webwriter-pairing.js +2993 -4383
- package/dist/widgets/webwriter-quiz-hint.js +2469 -0
- package/dist/widgets/webwriter-quiz.js +4933 -6099
- package/dist/widgets/webwriter-speech.js +6077 -5230
- package/dist/widgets/webwriter-task-prompt.js +4775 -2165
- package/dist/widgets/webwriter-task.js +2364 -8382
- package/dist/widgets/webwriter-text.js +4169 -4337
- package/dist/widgets/webwriter-true-false.js +4674 -0
- package/editing-config.json +106 -0
- package/lit-localize.json +21 -15
- package/localization/de.xlf +407 -74
- package/localization/es.xlf +407 -69
- package/localization/fr.xlf +407 -69
- package/localization/generated/de.ts +112 -29
- package/localization/generated/es.ts +112 -29
- package/localization/generated/fr.ts +112 -29
- package/localization/generated/index.d.ts +5 -0
- package/localization/generated/index.js +2 -2
- package/localization/generated/it.ts +112 -29
- package/localization/generated/locale-codes.js +31 -79
- package/localization/generated/nl.ts +112 -29
- package/localization/it.xlf +407 -69
- package/localization/nl.xlf +407 -69
- package/package.json +119 -252
- package/scripts/apply-translations.mjs +97 -0
- package/src/api/index.ts +72 -0
- package/src/lib/artboard.ts +686 -0
- package/src/lib/draggable-grid.ts +432 -0
- package/src/lib/encrypted-property.ts +202 -0
- package/src/lib/hint-popup.ts +152 -0
- package/src/lib/utils.ts +97 -0
- package/src/lib/webwriter-quirks.ts +121 -0
- package/src/migrate/index.ts +622 -0
- package/src/migrate/legacy-solution.ts +362 -0
- package/src/snippets/order.html +9 -8
- package/src/widgets/choice/webwriter-choice-item.ts +316 -0
- package/src/widgets/choice/webwriter-choice.ts +328 -0
- package/src/widgets/choice/webwriter-true-false.ts +113 -0
- package/src/widgets/dnd/webwriter-dnd-draggable.ts +28 -0
- package/src/widgets/dnd/webwriter-dnd-dropzone.ts +9 -0
- package/src/widgets/dnd/webwriter-dnd.ts +36 -0
- package/src/widgets/gap/webwriter-gap-item.ts +250 -0
- package/src/widgets/gap/webwriter-gap.ts +447 -0
- package/src/widgets/order/webwriter-order-item.ts +243 -0
- package/src/widgets/order/webwriter-order.ts +282 -0
- package/src/widgets/pairing/webwriter-pairing-item.ts +46 -0
- package/src/widgets/pairing/webwriter-pairing.ts +934 -0
- package/src/widgets/speech/audio-player.ts +325 -0
- package/src/widgets/speech/audio-recorder.ts +285 -0
- package/src/widgets/speech/waveform.ts +113 -0
- package/src/widgets/speech/webwriter-speech.ts +87 -0
- package/src/widgets/webwriter-mark.ts +374 -432
- package/src/widgets/webwriter-quiz-hint.ts +36 -0
- package/src/widgets/webwriter-quiz.ts +320 -282
- package/src/widgets/webwriter-task-prompt.ts +49 -24
- package/src/widgets/webwriter-task.ts +216 -547
- package/src/widgets/webwriter-text.ts +291 -156
- package/tsconfig.api.json +12 -0
- package/tsconfig.json +8 -7
- package/dist/widgets/webwriter-choice.css +0 -444
- package/dist/widgets/webwriter-cloze-gap.css +0 -444
- package/dist/widgets/webwriter-cloze-gap.js +0 -5866
- package/dist/widgets/webwriter-cloze.css +0 -444
- package/dist/widgets/webwriter-cloze.js +0 -3786
- package/dist/widgets/webwriter-mark.css +0 -444
- package/dist/widgets/webwriter-order.css +0 -444
- package/dist/widgets/webwriter-pairing-item.css +0 -444
- package/dist/widgets/webwriter-pairing.css +0 -444
- package/dist/widgets/webwriter-quiz.css +0 -444
- package/dist/widgets/webwriter-task-explainer.css +0 -444
- package/dist/widgets/webwriter-task-explainer.js +0 -2872
- package/dist/widgets/webwriter-task-hint.css +0 -444
- package/dist/widgets/webwriter-task-hint.js +0 -2811
- package/dist/widgets/webwriter-task.css +0 -444
- package/dist/widgets/webwriter-text.css +0 -444
- package/localization/bg.xlf +0 -69
- package/localization/cs.xlf +0 -69
- package/localization/da.xlf +0 -69
- package/localization/el.xlf +0 -69
- package/localization/et.xlf +0 -69
- package/localization/fi.xlf +0 -69
- package/localization/generated/bg.ts +0 -30
- package/localization/generated/cs.ts +0 -30
- package/localization/generated/da.ts +0 -30
- package/localization/generated/el.ts +0 -30
- package/localization/generated/et.ts +0 -30
- package/localization/generated/fi.ts +0 -30
- package/localization/generated/hu.ts +0 -30
- package/localization/generated/id.ts +0 -30
- package/localization/generated/ja.ts +0 -30
- package/localization/generated/ko.ts +0 -30
- package/localization/generated/lt.ts +0 -30
- package/localization/generated/lv.ts +0 -30
- package/localization/generated/nb.ts +0 -30
- package/localization/generated/pl.ts +0 -30
- package/localization/generated/pt-BR.ts +0 -30
- package/localization/generated/pt-PT.ts +0 -30
- package/localization/generated/ro.ts +0 -30
- package/localization/generated/ru.ts +0 -30
- package/localization/generated/sk.ts +0 -30
- package/localization/generated/sl.ts +0 -30
- package/localization/generated/sv.ts +0 -30
- package/localization/generated/tr.ts +0 -30
- package/localization/generated/uk.ts +0 -30
- package/localization/generated/zh-hans.ts +0 -30
- package/localization/hu.xlf +0 -69
- package/localization/id.xlf +0 -69
- package/localization/ja.xlf +0 -69
- package/localization/ko.xlf +0 -69
- package/localization/lt.xlf +0 -69
- package/localization/lv.xlf +0 -69
- package/localization/nb.xlf +0 -69
- package/localization/pl.xlf +0 -69
- package/localization/pt-BR.xlf +0 -69
- package/localization/pt-PT.xlf +0 -69
- package/localization/ro.xlf +0 -69
- package/localization/ru.xlf +0 -69
- package/localization/sk.xlf +0 -69
- package/localization/sl.xlf +0 -69
- package/localization/sv.xlf +0 -69
- package/localization/tr.xlf +0 -69
- package/localization/uk.xlf +0 -69
- package/localization/zh-hans.xlf +0 -69
- package/src/lib/combobox.ts +0 -456
- package/src/lib/highlighter-fill.svg +0 -6
- package/src/snippets/Beispiel-Choice.html +0 -57
- package/src/snippets/Beispiel-Mark.html +0 -14
- package/src/snippets/Beispiel-Order.html +0 -52
- package/src/snippets/Beispiel-Text.html +0 -20
- package/src/snippets/blank.html +0 -1
- package/src/snippets/choice.html +0 -8
- package/src/snippets/cloze.html +0 -4
- package/src/snippets/mark.html +0 -4
- package/src/snippets/pairing.html +0 -10
- package/src/snippets/speech.html +0 -4
- package/src/snippets/text.html +0 -4
- package/src/snippets/wordsearch.html +0 -4
- package/src/widgets/webwriter-choice-item.ts +0 -254
- package/src/widgets/webwriter-choice.ts +0 -353
- package/src/widgets/webwriter-cloze-gap.ts +0 -216
- package/src/widgets/webwriter-cloze.ts +0 -136
- package/src/widgets/webwriter-order-item.ts +0 -441
- package/src/widgets/webwriter-order.ts +0 -327
- package/src/widgets/webwriter-pairing-item.ts +0 -161
- package/src/widgets/webwriter-pairing.ts +0 -193
- package/src/widgets/webwriter-speech.ts +0 -273
- package/src/widgets/webwriter-task-explainer.ts +0 -43
- package/src/widgets/webwriter-task-hint.ts +0 -23
|
@@ -1,156 +1,291 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import "
|
|
9
|
-
|
|
10
|
-
import
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
declare global {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
}
|
|
1
|
+
import { msg } from "@lit/localize";
|
|
2
|
+
import SlInput from "@shoelace-style/shoelace/dist/components/input/input.component.js";
|
|
3
|
+
import SlTextarea from "@shoelace-style/shoelace/dist/components/textarea/textarea.component.js";
|
|
4
|
+
import { LitElementWw, type OptionDeclaration } from "@webwriter/lit";
|
|
5
|
+
import TextParagraphIcon from "bootstrap-icons/icons/text-paragraph.svg";
|
|
6
|
+
import { css, html, nothing, type PropertyValues } from "lit";
|
|
7
|
+
import { customElement, property, query, state } from "lit/decorators.js";
|
|
8
|
+
import LOCALIZE from "../../localization/generated";
|
|
9
|
+
import { name as packageId } from "../../package.json";
|
|
10
|
+
import { registerQuizType, type IWebWriterQuizType } from "../api";
|
|
11
|
+
import { encryptedProperty, encryptPlaintextAttributes } from "../lib/encrypted-property";
|
|
12
|
+
|
|
13
|
+
declare global {
|
|
14
|
+
interface HTMLElementTagNameMap {
|
|
15
|
+
"webwriter-text": WebwriterText;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type TextType = "long-text" | "text" | "number" | "date" | "time" | "datetime-local";
|
|
20
|
+
|
|
21
|
+
registerQuizType({
|
|
22
|
+
packageId,
|
|
23
|
+
widgetPosition: 30,
|
|
24
|
+
id: "webwriter-text",
|
|
25
|
+
getName: () => msg("Text"),
|
|
26
|
+
icon: TextParagraphIcon,
|
|
27
|
+
createInstance: () => document.createElement("webwriter-text"),
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* An answer where learners type a value into an input field.
|
|
32
|
+
*
|
|
33
|
+
* It must be assigned to the default slot of a `<webwriter-task>`.
|
|
34
|
+
*/
|
|
35
|
+
@customElement("webwriter-text")
|
|
36
|
+
export class WebwriterText extends LitElementWw implements IWebWriterQuizType {
|
|
37
|
+
/** @internal */
|
|
38
|
+
localize = LOCALIZE;
|
|
39
|
+
|
|
40
|
+
/** @internal */
|
|
41
|
+
static scopedElements = {
|
|
42
|
+
"sl-textarea": SlTextarea,
|
|
43
|
+
"sl-input": SlInput,
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
static styles = css`
|
|
47
|
+
:host {
|
|
48
|
+
display: block;
|
|
49
|
+
width: 100%;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/* Highlight the author's solution while editing. */
|
|
53
|
+
:host(:is([contenteditable="true"], [contenteditable=""])) :is(sl-textarea::part(textarea), sl-input::part(input)) {
|
|
54
|
+
color: var(--sl-color-success-700);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
sl-textarea::part(base),
|
|
58
|
+
sl-input::part(base) {
|
|
59
|
+
font-size: 1em !important;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
sl-textarea {
|
|
63
|
+
resize: vertical;
|
|
64
|
+
overflow: hidden;
|
|
65
|
+
min-height: 40px;
|
|
66
|
+
|
|
67
|
+
&::part(form-control),
|
|
68
|
+
&::part(form-control-input),
|
|
69
|
+
&::part(base),
|
|
70
|
+
&::part(textarea) {
|
|
71
|
+
height: 100%;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
:is(sl-textarea, sl-input):disabled::part(base) {
|
|
76
|
+
background-color: var(--sl-input-background-color);
|
|
77
|
+
border-color: var(--sl-input-border-color);
|
|
78
|
+
opacity: 1;
|
|
79
|
+
cursor: inherit;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
:is(sl-textarea, sl-input)[data-state="correct"]::part(base) {
|
|
83
|
+
background: var(--sl-color-success-50);
|
|
84
|
+
border-color: var(--sl-color-success-400);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
:is(sl-textarea, sl-input)[data-state="incorrect"]::part(base) {
|
|
88
|
+
background: var(--sl-color-danger-50);
|
|
89
|
+
border-color: var(--sl-color-danger-400);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.feedback {
|
|
93
|
+
margin-top: var(--sl-spacing-x-small);
|
|
94
|
+
color: var(--sl-color-neutral-500);
|
|
95
|
+
font-size: var(--sl-font-size-small);
|
|
96
|
+
overflow-wrap: anywhere;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.solution {
|
|
100
|
+
margin-top: var(--sl-spacing-x-small);
|
|
101
|
+
}
|
|
102
|
+
`;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* The kind of input field learners answer in.
|
|
106
|
+
*
|
|
107
|
+
* - `long-text`: A multi-line text area.
|
|
108
|
+
* - `text`: A single-line text field.
|
|
109
|
+
* - `number`: A number field.
|
|
110
|
+
* - `date`: A date picker.
|
|
111
|
+
* - `time`: A time picker.
|
|
112
|
+
* - `datetime-local`: A combined date and time picker.
|
|
113
|
+
*/
|
|
114
|
+
@property({ type: String, attribute: true, reflect: true })
|
|
115
|
+
accessor type: TextType = "long-text";
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* The placeholder shown while the field is empty.
|
|
119
|
+
*/
|
|
120
|
+
@property({ type: String, attribute: true, reflect: true })
|
|
121
|
+
accessor placeholder: string = "";
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Whether any non-empty answer counts as correct, without comparing it to the solution.
|
|
125
|
+
*/
|
|
126
|
+
@property({ type: Boolean, attribute: "free-text", reflect: true })
|
|
127
|
+
accessor freeText = false;
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Whether the answer is compared to the solution case-insensitively.
|
|
131
|
+
*/
|
|
132
|
+
@property({ type: Boolean, attribute: "ignore-case", reflect: true })
|
|
133
|
+
accessor ignoreCase = false;
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* The feedback shown after submitting if the answer is correct.
|
|
137
|
+
*/
|
|
138
|
+
@property({ type: String, attribute: "correct-message", reflect: true })
|
|
139
|
+
accessor correctMessage: string = "";
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* The feedback shown after submitting if the answer is wrong.
|
|
143
|
+
*/
|
|
144
|
+
@property({ type: String, attribute: "wrong-message", reflect: true })
|
|
145
|
+
accessor wrongMessage: string = "";
|
|
146
|
+
|
|
147
|
+
/** @internal */
|
|
148
|
+
get dynamicOptions(): Record<string, OptionDeclaration> {
|
|
149
|
+
const isText = this.type === "long-text" || this.type === "text";
|
|
150
|
+
const options: Record<string, OptionDeclaration> = {
|
|
151
|
+
type: {
|
|
152
|
+
type: "select",
|
|
153
|
+
label: { _: msg("Type") },
|
|
154
|
+
options: [
|
|
155
|
+
{ value: "long-text", label: { _: msg("Long Text") } },
|
|
156
|
+
{ value: "text", label: { _: msg("Short Text") } },
|
|
157
|
+
{ value: "number", label: { _: msg("Number") } },
|
|
158
|
+
{ value: "date", label: { _: msg("Date") } },
|
|
159
|
+
{ value: "time", label: { _: msg("Time") } },
|
|
160
|
+
{ value: "datetime-local", label: { _: msg("Date & Time") } },
|
|
161
|
+
],
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
if (isText || this.type === "number") {
|
|
166
|
+
options.placeholder = { type: "string", label: { _: msg("Placeholder") } };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
options["free-text"] = { type: "boolean", label: { _: msg("Free answer") } };
|
|
170
|
+
|
|
171
|
+
if (!this.freeText) {
|
|
172
|
+
if (isText) {
|
|
173
|
+
options["ignore-case"] = {
|
|
174
|
+
type: "boolean",
|
|
175
|
+
label: { _: msg("Ignore capitalization") },
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
options["correct-message"] = {
|
|
179
|
+
type: "string",
|
|
180
|
+
label: { _: msg("Message for correct solution") },
|
|
181
|
+
};
|
|
182
|
+
options["wrong-message"] = {
|
|
183
|
+
type: "string",
|
|
184
|
+
label: { _: msg("Message for wrong solution") },
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return options;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* The value counting as correct. It is obfuscated in the markup so that learners cannot read it directly.
|
|
193
|
+
*/
|
|
194
|
+
@property({ type: String, attribute: true, reflect: true, converter: encryptedProperty })
|
|
195
|
+
accessor solution: string = "";
|
|
196
|
+
|
|
197
|
+
@state() private accessor currentAnswer: string | undefined;
|
|
198
|
+
@state() private accessor graded: boolean = false;
|
|
199
|
+
@state() private accessor detailedFeedback: boolean = false;
|
|
200
|
+
|
|
201
|
+
@query("sl-textarea, sl-input")
|
|
202
|
+
private accessor input!: SlTextarea | SlInput;
|
|
203
|
+
|
|
204
|
+
protected firstUpdated(changed: PropertyValues): void {
|
|
205
|
+
super.firstUpdated(changed);
|
|
206
|
+
encryptPlaintextAttributes(this);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
private get value(): string {
|
|
210
|
+
return this.isContentEditable ? this.solution : (this.currentAnswer ?? "");
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
private set value(value: string) {
|
|
214
|
+
if (this.isContentEditable) this.solution = value;
|
|
215
|
+
else this.currentAnswer = value;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
private isCorrect(): boolean {
|
|
219
|
+
const answer = (this.currentAnswer ?? "").trim();
|
|
220
|
+
if (this.freeText) return answer !== "";
|
|
221
|
+
const solution = this.solution.trim();
|
|
222
|
+
if (!solution) return false;
|
|
223
|
+
return this.ignoreCase ? answer.toLowerCase() === solution.toLowerCase() : answer === solution;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
override focus() {
|
|
227
|
+
this.input?.focus();
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
checkValidity(): boolean {
|
|
231
|
+
return (this.currentAnswer ?? "").trim() !== "";
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
reset() {
|
|
235
|
+
this.graded = false;
|
|
236
|
+
this.detailedFeedback = false;
|
|
237
|
+
this.currentAnswer = undefined;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
checkAnswer(detailedFeedback: boolean): number {
|
|
241
|
+
this.graded = true;
|
|
242
|
+
this.detailedFeedback = detailedFeedback;
|
|
243
|
+
return this.isCorrect() ? 1 : 0;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
private handleChange = (e: Event) => {
|
|
247
|
+
const target = e.target as SlTextarea | SlInput;
|
|
248
|
+
this.value = target.value?.trim() ?? "";
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
private get feedbackState(): "correct" | "incorrect" | undefined {
|
|
252
|
+
// Free answers are locked on submit but never marked correct/incorrect.
|
|
253
|
+
if (this.freeText || this.isContentEditable || !this.graded || !this.detailedFeedback) return undefined;
|
|
254
|
+
return this.isCorrect() ? "correct" : "incorrect";
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
render() {
|
|
258
|
+
const value = this.value;
|
|
259
|
+
const state = this.feedbackState;
|
|
260
|
+
const message = state === "correct" ? this.correctMessage : this.wrongMessage;
|
|
261
|
+
const showSolution = this.detailedFeedback && this.solution.trim() !== "";
|
|
262
|
+
|
|
263
|
+
const field =
|
|
264
|
+
this.type === "long-text"
|
|
265
|
+
? html`<sl-textarea
|
|
266
|
+
data-state=${state ?? nothing}
|
|
267
|
+
value=${value}
|
|
268
|
+
placeholder=${this.placeholder}
|
|
269
|
+
resize="none"
|
|
270
|
+
?disabled=${this.graded}
|
|
271
|
+
@sl-change=${this.handleChange}
|
|
272
|
+
></sl-textarea>`
|
|
273
|
+
: html`<sl-input
|
|
274
|
+
data-state=${state ?? nothing}
|
|
275
|
+
value=${value}
|
|
276
|
+
placeholder=${this.placeholder}
|
|
277
|
+
type=${this.type}
|
|
278
|
+
?disabled=${this.graded}
|
|
279
|
+
@sl-change=${this.handleChange}
|
|
280
|
+
></sl-input>`;
|
|
281
|
+
|
|
282
|
+
return html`${field}${state && (message || showSolution)
|
|
283
|
+
? html`<div class="feedback" data-state=${state} role="status">
|
|
284
|
+
${message ? html`<div>${message}</div>` : nothing}
|
|
285
|
+
${showSolution
|
|
286
|
+
? html`<div class="solution"><strong>${msg("Solution")}:</strong> ${this.solution}</div>`
|
|
287
|
+
: nothing}
|
|
288
|
+
</div>`
|
|
289
|
+
: nothing}`;
|
|
290
|
+
}
|
|
291
|
+
}
|
package/tsconfig.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "es2022",
|
|
4
|
+
"moduleResolution": "Bundler",
|
|
5
|
+
"module": "ES2022",
|
|
6
|
+
"lib": ["ESNext", "DOM", "DOM.Iterable"]
|
|
7
|
+
}
|
|
8
|
+
}
|