@qti-components/base 1.5.0 → 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/dist/abstract/interaction.d.ts +54 -64
- package/dist/abstract/interaction.js +103 -276
- package/dist/abstract/interaction.js.map +1 -1
- package/dist/abstract/qti-expression.d.ts +1 -1
- package/dist/abstract/qti-expression.js +23 -2
- package/dist/abstract/qti-expression.js.map +1 -1
- package/dist/abstract/qti-feedback.js +1 -1
- package/dist/abstract/qti-feedback.js.map +1 -1
- package/dist/context/computed-item.context.d.ts +1 -16
- package/dist/context/computed-item.context.js.map +1 -1
- package/dist/context/computed.context.d.ts +1 -26
- package/dist/context/computed.context.js.map +1 -1
- package/dist/context/config.context.d.ts +61 -16
- package/dist/context/config.context.js.map +1 -1
- package/dist/context/interaction.context.d.ts +36 -0
- package/dist/context/interaction.context.js +3 -0
- package/dist/context/interaction.context.js.map +1 -0
- package/dist/context/item.context.d.ts +4 -12
- package/dist/context/item.context.js +2 -16
- package/dist/context/item.context.js.map +1 -1
- package/dist/context/qti.context.d.ts +36 -0
- package/dist/context/qti.context.js +13 -0
- package/dist/context/qti.context.js.map +1 -1
- package/dist/context/test.context.d.ts +1 -1
- package/dist/context/test.context.js.map +1 -1
- package/dist/context/types/computed-item.types.d.ts +16 -0
- package/dist/context/types/computed-item.types.js +2 -0
- package/dist/context/types/computed-item.types.js.map +1 -0
- package/dist/context/types/computed.types.d.ts +26 -0
- package/dist/context/types/computed.types.js +2 -0
- package/dist/context/types/computed.types.js.map +1 -0
- package/dist/context/types/item.types.d.ts +12 -0
- package/dist/context/types/item.types.js +17 -0
- package/dist/context/types/item.types.js.map +1 -0
- package/dist/elements.d.ts +24 -0
- package/dist/elements.js +22 -0
- package/dist/elements.js.map +1 -0
- package/dist/index.d.ts +9 -1
- package/dist/index.js +9 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/interaction.interface.d.ts +39 -15
- package/dist/lib/interaction.interface.js.map +1 -1
- package/dist/lib/response.d.ts +104 -0
- package/dist/lib/response.js +168 -0
- package/dist/lib/response.js.map +1 -0
- package/dist/register.d.ts +1 -0
- package/dist/register.js +7 -0
- package/dist/register.js.map +1 -0
- package/dist/styles/box-sizing.styles.d.ts +19 -0
- package/dist/styles/box-sizing.styles.js +31 -0
- package/dist/styles/box-sizing.styles.js.map +1 -0
- package/dist/styles/drop-region.styles.d.ts +36 -0
- package/dist/styles/drop-region.styles.js +78 -0
- package/dist/styles/drop-region.styles.js.map +1 -0
- package/dist/styles/validation-message.styles.d.ts +18 -0
- package/dist/styles/validation-message.styles.js +24 -0
- package/dist/styles/validation-message.styles.js.map +1 -0
- package/package.json +16 -1
- package/dist/context/qti-assessment-item.context.d.ts +0 -4
- package/dist/context/qti-assessment-item.context.js +0 -3
- package/dist/context/qti-assessment-item.context.js.map +0 -1
|
@@ -1,15 +1,42 @@
|
|
|
1
1
|
import { LitElement } from 'lit';
|
|
2
|
-
import type {
|
|
2
|
+
import type { PropertyValues } from 'lit';
|
|
3
|
+
import type { InteractionContext } from '../context/interaction.context';
|
|
4
|
+
import type { ConfigContext, ValidationDisplayMode } from '../context/config.context';
|
|
5
|
+
import type { ValidatableInteraction } from '../lib/interaction.interface';
|
|
3
6
|
import type { ResponseVariable } from '../lib/variables';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
export declare abstract class Interaction extends LitElement implements IInteraction {
|
|
7
|
+
/**
|
|
8
|
+
* Shared QTI interaction base. Formative correction behavior is supplied by the
|
|
9
|
+
* `@qti-components/corrections` package.
|
|
10
|
+
*/
|
|
11
|
+
export declare abstract class Interaction extends LitElement implements ValidatableInteraction {
|
|
12
|
+
#private;
|
|
11
13
|
private _context;
|
|
12
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Delivery configuration, from the nearest provider — `qti-test` or `qti-item`.
|
|
16
|
+
*
|
|
17
|
+
* **Public so it can be assigned directly, which is the development-time route.** An interaction
|
|
18
|
+
* has to work standalone, and in a story or a spec there is usually no provider above it — so
|
|
19
|
+
* setting the field is all that is needed:
|
|
20
|
+
*
|
|
21
|
+
* orderInteraction.configContext = { allowReorder: false, validationDisplayMode: 'none' };
|
|
22
|
+
*
|
|
23
|
+
* With no provider, nothing ever overwrites it. That is the whole contract, and its limit: where a
|
|
24
|
+
* provider DOES exist it re-emits on its next update and wins, because `@consume` writes this same
|
|
25
|
+
* field. There is no merging — set the value on the provider in that case.
|
|
26
|
+
*
|
|
27
|
+
* In a lit-html template, bind it as a property rather than assigning after the fact:
|
|
28
|
+
*
|
|
29
|
+
* html`<qti-order-interaction .configContext=${{ allowReorder: false }}>…`
|
|
30
|
+
*
|
|
31
|
+
* The binding is committed on the detached template clone, before the fragment is inserted, so it
|
|
32
|
+
* is in place by `connectedCallback` — which matters for anything that reads config on connect.
|
|
33
|
+
*
|
|
34
|
+
* It was `protected`, which fooled nobody: every caller reached it anyway through an
|
|
35
|
+
* `as any` / `as { configContext?: … }` cast. Public says what was already true and removes the
|
|
36
|
+
* casts, rather than pretending at an encapsulation the type system was the only thing enforcing.
|
|
37
|
+
*/
|
|
38
|
+
configContext: ConfigContext;
|
|
39
|
+
protected _interactionContext: Readonly<InteractionContext>;
|
|
13
40
|
static formAssociated: boolean;
|
|
14
41
|
protected _internals: ElementInternals;
|
|
15
42
|
get internals(): ElementInternals;
|
|
@@ -17,71 +44,34 @@ export declare abstract class Interaction extends LitElement implements IInterac
|
|
|
17
44
|
disabled: boolean;
|
|
18
45
|
readonly: boolean;
|
|
19
46
|
name: any;
|
|
20
|
-
/**
|
|
21
|
-
|
|
22
|
-
* string via attribute or as string/string[] via property.
|
|
23
|
-
*
|
|
24
|
-
* @example
|
|
25
|
-
* ```html
|
|
26
|
-
* <qti-choice-interaction correct-response="A,B">
|
|
27
|
-
* ```
|
|
28
|
-
*/
|
|
29
|
-
set correctResponseAttr(val: string | null);
|
|
30
|
-
get correctResponseAttr(): string | null;
|
|
31
|
-
/**
|
|
32
|
-
* Shows which choices are correct with inline indicators.
|
|
33
|
-
* Adds `correct-response` / `incorrect-response` states to choices.
|
|
34
|
-
*/
|
|
35
|
-
showCorrectResponse: boolean;
|
|
36
|
-
protected _handleShowCorrectResponseChange: (_: boolean, show: boolean) => void;
|
|
37
|
-
/**
|
|
38
|
-
* Shows a cloned interaction with the correct answers filled in.
|
|
39
|
-
* Creates a disabled copy positioned after this interaction.
|
|
40
|
-
*/
|
|
41
|
-
showFullCorrectResponse: boolean;
|
|
42
|
-
protected _handleShowFullCorrectResponseChange: (_: boolean, show: boolean) => void;
|
|
43
|
-
/**
|
|
44
|
-
* Shows feedback on candidate's selections compared to correct response.
|
|
45
|
-
* Adds `candidate-correct` / `candidate-incorrect` states to choices.
|
|
46
|
-
*/
|
|
47
|
-
showCandidateCorrection: boolean;
|
|
48
|
-
protected _handleShowCandidateCorrectionChange: (_: boolean, show: boolean) => void;
|
|
49
|
-
protected _isFullCorrectResponse: boolean;
|
|
50
|
-
get isFullCorrectResponse(): Readonly<boolean>;
|
|
51
|
-
set isFullCorrectResponse(val: Readonly<boolean>);
|
|
52
|
-
protected _correctResponse: string | string[] | null;
|
|
53
|
-
/**
|
|
54
|
-
* Get/set correct response programmatically.
|
|
55
|
-
* This property syncs with the `correct-response` attribute.
|
|
56
|
-
*/
|
|
57
|
-
get correctResponse(): Readonly<string | string[] | null>;
|
|
58
|
-
set correctResponse(val: Readonly<string | string[] | null>);
|
|
59
|
-
/**
|
|
60
|
-
* Returns the correctness of the current response compared to correct response.
|
|
61
|
-
* Works in both standalone mode (using `correct-response` attribute) and
|
|
62
|
-
* item context mode (using responseVariable).
|
|
63
|
-
*/
|
|
64
|
-
get correctness(): Readonly<Correctness | null>;
|
|
47
|
+
/** Extension point used by wrappers that should not register as candidate inputs. */
|
|
48
|
+
protected get registersWithItem(): boolean;
|
|
65
49
|
get isInline(): boolean;
|
|
66
50
|
get responseVariable(): ResponseVariable | undefined;
|
|
67
|
-
toggleCorrectResponse(show: boolean): void;
|
|
68
|
-
protected toggleFullCorrectResponse(show: boolean): Promise<void>;
|
|
69
|
-
protected toggleInternalCorrectResponse(show: boolean): void;
|
|
70
|
-
toggleCandidateCorrection(show: boolean): void;
|
|
71
|
-
constructor();
|
|
72
51
|
abstract validate(): boolean;
|
|
73
52
|
get value(): string | null;
|
|
74
53
|
set value(val: string | null);
|
|
75
54
|
abstract get response(): string | string[] | null;
|
|
76
55
|
abstract set response(val: string | string[] | null);
|
|
77
56
|
reportValidity(): boolean;
|
|
57
|
+
protected get validationDisplayMode(): ValidationDisplayMode;
|
|
58
|
+
protected resolveAllowReorder(options?: {
|
|
59
|
+
defaultWhenUnset?: boolean;
|
|
60
|
+
}): boolean;
|
|
61
|
+
protected resolveDisableAfterMaxReached(options?: {
|
|
62
|
+
defaultWhenUnset?: boolean;
|
|
63
|
+
}): boolean;
|
|
64
|
+
protected setInteractionValidity(isValid: boolean, validityMessage?: string, anchor?: HTMLElement | null, options?: {
|
|
65
|
+
suppressInline?: boolean;
|
|
66
|
+
}): void;
|
|
67
|
+
protected updateInlineValidationMessage(): void;
|
|
68
|
+
protected clearInlineValidationMessage(): void;
|
|
69
|
+
protected getValidationMessageElement(): HTMLElement | null;
|
|
78
70
|
reset(): void;
|
|
79
|
-
/**
|
|
80
|
-
* Called by the browser when the associated form is reset.
|
|
81
|
-
* Part of the Form-Associated Custom Elements API.
|
|
82
|
-
*/
|
|
83
71
|
formResetCallback(): void;
|
|
84
72
|
connectedCallback(): void;
|
|
85
|
-
protected firstUpdated(): void;
|
|
86
73
|
saveResponse(value: string | string[], state?: string | null): void;
|
|
74
|
+
constructor();
|
|
75
|
+
/** Optional lifecycle bridge for interaction subclasses that do not need changed-property data. */
|
|
76
|
+
protected firstUpdated(_changedProperties?: PropertyValues<this>): void;
|
|
87
77
|
}
|
|
@@ -4,284 +4,133 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
4
4
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
|
-
import {
|
|
7
|
+
import { consume, provide } from '@lit/context';
|
|
8
8
|
import { LitElement } from 'lit';
|
|
9
|
-
import {
|
|
10
|
-
import { watch } from '@qti-components/utilities';
|
|
9
|
+
import { property } from 'lit/decorators.js';
|
|
11
10
|
import { configContext } from '../context/config.context';
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
})(Correctness || (Correctness = {}));
|
|
11
|
+
import { interactionContext } from '../context/interaction.context';
|
|
12
|
+
import { itemContext } from '../context/item.context';
|
|
13
|
+
/**
|
|
14
|
+
* Shared QTI interaction base. Formative correction behavior is supplied by the
|
|
15
|
+
* `@qti-components/corrections` package.
|
|
16
|
+
*/
|
|
19
17
|
export class Interaction extends LitElement {
|
|
18
|
+
#didLogDisableAfterIfMaxChoicesReachedDeprecation;
|
|
20
19
|
static { this.formAssociated = true; }
|
|
21
20
|
get internals() {
|
|
22
21
|
return this._internals;
|
|
23
22
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* The correct response for this interaction. Can be set as a comma-separated
|
|
29
|
-
* string via attribute or as string/string[] via property.
|
|
30
|
-
*
|
|
31
|
-
* @example
|
|
32
|
-
* ```html
|
|
33
|
-
* <qti-choice-interaction correct-response="A,B">
|
|
34
|
-
* ```
|
|
35
|
-
*/
|
|
36
|
-
set correctResponseAttr(val) {
|
|
37
|
-
if (val === null) {
|
|
38
|
-
this._correctResponse = null;
|
|
39
|
-
}
|
|
40
|
-
else if (val.includes(',')) {
|
|
41
|
-
this._correctResponse = val.split(',').map(v => v.trim());
|
|
42
|
-
}
|
|
43
|
-
else {
|
|
44
|
-
this._correctResponse = val;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
get correctResponseAttr() {
|
|
48
|
-
if (this._correctResponse === null || this._correctResponse === undefined) {
|
|
49
|
-
return null;
|
|
50
|
-
}
|
|
51
|
-
return Array.isArray(this._correctResponse) ? this._correctResponse.join(',') : this._correctResponse;
|
|
52
|
-
}
|
|
53
|
-
get isFullCorrectResponse() {
|
|
54
|
-
return this._isFullCorrectResponse;
|
|
55
|
-
}
|
|
56
|
-
set isFullCorrectResponse(val) {
|
|
57
|
-
this._isFullCorrectResponse = val;
|
|
58
|
-
if (val) {
|
|
59
|
-
this.disabled = true;
|
|
60
|
-
this.setAttribute('inert', '');
|
|
61
|
-
this.setAttribute('response-identifier', this.responseIdentifier + '_cr');
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* Get/set correct response programmatically.
|
|
66
|
-
* This property syncs with the `correct-response` attribute.
|
|
67
|
-
*/
|
|
68
|
-
get correctResponse() {
|
|
69
|
-
// In standalone mode, use the local _correctResponse
|
|
70
|
-
// In item context mode, prefer responseVariable.correctResponse
|
|
71
|
-
const responseVariable = this.responseVariable;
|
|
72
|
-
if (responseVariable?.correctResponse) {
|
|
73
|
-
return responseVariable.correctResponse;
|
|
74
|
-
}
|
|
75
|
-
return this._correctResponse;
|
|
76
|
-
}
|
|
77
|
-
set correctResponse(val) {
|
|
78
|
-
const oldVal = this._correctResponse;
|
|
79
|
-
this._correctResponse = val;
|
|
80
|
-
// Sync to attribute
|
|
81
|
-
if (val === null || val === undefined) {
|
|
82
|
-
this.removeAttribute('correct-response');
|
|
83
|
-
}
|
|
84
|
-
else {
|
|
85
|
-
this.setAttribute('correct-response', Array.isArray(val) ? val.join(',') : val);
|
|
86
|
-
}
|
|
87
|
-
this.requestUpdate('_correctResponse', oldVal);
|
|
88
|
-
}
|
|
89
|
-
/**
|
|
90
|
-
* Returns the correctness of the current response compared to correct response.
|
|
91
|
-
* Works in both standalone mode (using `correct-response` attribute) and
|
|
92
|
-
* item context mode (using responseVariable).
|
|
93
|
-
*/
|
|
94
|
-
get correctness() {
|
|
95
|
-
const correctResponse = this.correctResponse;
|
|
96
|
-
if (!correctResponse)
|
|
97
|
-
return null;
|
|
98
|
-
const currentResponse = this.response;
|
|
99
|
-
// No response yet (null, undefined, empty string, or empty array)
|
|
100
|
-
if (currentResponse === null ||
|
|
101
|
-
currentResponse === undefined ||
|
|
102
|
-
currentResponse === '' ||
|
|
103
|
-
(Array.isArray(currentResponse) && currentResponse.length === 0)) {
|
|
104
|
-
return null;
|
|
105
|
-
}
|
|
106
|
-
// Normalize both to arrays for comparison
|
|
107
|
-
const correctArr = Array.isArray(correctResponse) ? correctResponse : [correctResponse];
|
|
108
|
-
const responseArr = Array.isArray(currentResponse) ? currentResponse : [currentResponse];
|
|
109
|
-
// Filter out empty strings
|
|
110
|
-
const filteredResponseArr = responseArr.filter(r => r !== '');
|
|
111
|
-
if (filteredResponseArr.length === 0)
|
|
112
|
-
return null;
|
|
113
|
-
// Check for partial correctness - some correct selections but not all, or extra incorrect selections
|
|
114
|
-
const correctCount = filteredResponseArr.filter(r => correctArr.includes(r)).length;
|
|
115
|
-
const incorrectCount = filteredResponseArr.filter(r => !correctArr.includes(r)).length;
|
|
116
|
-
// All responses are correct and we have all correct answers
|
|
117
|
-
if (correctCount === correctArr.length &&
|
|
118
|
-
incorrectCount === 0 &&
|
|
119
|
-
filteredResponseArr.length === correctArr.length) {
|
|
120
|
-
return Correctness.Correct;
|
|
121
|
-
}
|
|
122
|
-
// Some correct responses but not complete or has incorrect responses
|
|
123
|
-
if (correctCount > 0 && (correctCount < correctArr.length || incorrectCount > 0)) {
|
|
124
|
-
return Correctness.PartiallyCorrect;
|
|
125
|
-
}
|
|
126
|
-
return Correctness.Incorrect;
|
|
23
|
+
/** Extension point used by wrappers that should not register as candidate inputs. */
|
|
24
|
+
get registersWithItem() {
|
|
25
|
+
return true;
|
|
127
26
|
}
|
|
128
27
|
get isInline() {
|
|
129
28
|
return false;
|
|
130
29
|
}
|
|
131
30
|
get responseVariable() {
|
|
132
|
-
// In standalone mode (no item context), return undefined
|
|
133
31
|
if (!this._context?.variables) {
|
|
134
32
|
return undefined;
|
|
135
33
|
}
|
|
136
|
-
// Get all response variables
|
|
137
34
|
const responseVariables = this._context.variables.filter(v => v.type === 'response');
|
|
138
|
-
// Get the response identifier for this interaction
|
|
139
35
|
const responseIdentifier = this.getAttribute('response-identifier');
|
|
140
|
-
// Return the matching response variable for this interaction
|
|
141
36
|
return responseVariables.find(v => v.identifier === responseIdentifier);
|
|
142
37
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
38
|
+
get value() {
|
|
39
|
+
return JSON.stringify(this.response);
|
|
40
|
+
}
|
|
41
|
+
set value(val) {
|
|
42
|
+
this.response = val ? JSON.parse(val) : null;
|
|
43
|
+
}
|
|
44
|
+
reportValidity() {
|
|
45
|
+
const isValid = this._internals.validity.valid;
|
|
46
|
+
const mode = this.validationDisplayMode;
|
|
47
|
+
if (mode === 'native' || mode === 'both') {
|
|
48
|
+
this._internals.reportValidity();
|
|
49
|
+
}
|
|
50
|
+
if (mode === 'inline' || mode === 'both') {
|
|
51
|
+
this.updateInlineValidationMessage();
|
|
147
52
|
}
|
|
148
53
|
else {
|
|
149
|
-
this.
|
|
54
|
+
this.clearInlineValidationMessage();
|
|
55
|
+
}
|
|
56
|
+
if (mode === 'none') {
|
|
57
|
+
this.clearInlineValidationMessage();
|
|
150
58
|
}
|
|
59
|
+
return isValid;
|
|
151
60
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
61
|
+
get validationDisplayMode() {
|
|
62
|
+
return this.configContext?.validationDisplayMode ?? 'inline';
|
|
63
|
+
}
|
|
64
|
+
resolveAllowReorder(options) {
|
|
65
|
+
const defaultWhenUnset = options?.defaultWhenUnset ?? true;
|
|
66
|
+
const config = this.configContext;
|
|
67
|
+
if (!config) {
|
|
68
|
+
return defaultWhenUnset;
|
|
159
69
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
if (!showFullCorrectResponse) {
|
|
163
|
-
if (!nextSiblingIsFullCorrectResponse) {
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
// Remove cloned interaction
|
|
167
|
-
this.parentElement?.removeChild(nextSibling);
|
|
168
|
-
return;
|
|
70
|
+
if (config.allowReorder !== undefined) {
|
|
71
|
+
return config.allowReorder;
|
|
169
72
|
}
|
|
170
|
-
|
|
171
|
-
|
|
73
|
+
return defaultWhenUnset;
|
|
74
|
+
}
|
|
75
|
+
resolveDisableAfterMaxReached(options) {
|
|
76
|
+
const defaultWhenUnset = options?.defaultWhenUnset ?? false;
|
|
77
|
+
const config = this.configContext;
|
|
78
|
+
if (!config) {
|
|
79
|
+
return defaultWhenUnset;
|
|
172
80
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
// Remove attributes that could cause issues on the clone
|
|
176
|
-
clone.removeAttribute('show-full-correct-response');
|
|
177
|
-
clone.removeAttribute('show-correct-response');
|
|
178
|
-
clone.removeAttribute('show-candidate-correction');
|
|
179
|
-
const containerDiv = document.createElement('div');
|
|
180
|
-
containerDiv.classList.add('full-correct-response');
|
|
181
|
-
if (this.isInline) {
|
|
182
|
-
containerDiv.classList.add('full-correct-response-inline');
|
|
81
|
+
if (config.disableAfterMaxReached !== undefined) {
|
|
82
|
+
return config.disableAfterMaxReached;
|
|
183
83
|
}
|
|
184
|
-
|
|
185
|
-
|
|
84
|
+
if (config.disableAfterIfMaxChoicesReached !== undefined) {
|
|
85
|
+
if (!this.#didLogDisableAfterIfMaxChoicesReachedDeprecation) {
|
|
86
|
+
this.#didLogDisableAfterIfMaxChoicesReachedDeprecation = true;
|
|
87
|
+
console.log('[QTI Config] `disableAfterIfMaxChoicesReached` is deprecated. Use `disableAfterMaxReached` instead.');
|
|
88
|
+
}
|
|
89
|
+
return config.disableAfterIfMaxChoicesReached;
|
|
186
90
|
}
|
|
187
|
-
|
|
188
|
-
containerDiv.appendChild(clone);
|
|
189
|
-
clone.isFullCorrectResponse = true;
|
|
190
|
-
this.parentElement?.insertBefore(containerDiv, this.nextSibling);
|
|
191
|
-
await clone.updateComplete;
|
|
192
|
-
clone.response = Array.isArray(correctResponse) ? [...correctResponse] : correctResponse;
|
|
91
|
+
return defaultWhenUnset;
|
|
193
92
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
this.
|
|
93
|
+
setInteractionValidity(isValid, validityMessage = '', anchor, options) {
|
|
94
|
+
const validityAnchor = anchor ?? this;
|
|
95
|
+
this._internals.setValidity(isValid ? {} : { customError: true }, validityMessage, validityAnchor);
|
|
96
|
+
if (options?.suppressInline !== true &&
|
|
97
|
+
(this.validationDisplayMode === 'inline' || this.validationDisplayMode === 'both')) {
|
|
98
|
+
this.updateInlineValidationMessage();
|
|
200
99
|
}
|
|
201
100
|
}
|
|
202
|
-
|
|
203
|
-
this.
|
|
204
|
-
|
|
205
|
-
this._internals.states.delete('candidate-incorrect');
|
|
206
|
-
if (!show) {
|
|
101
|
+
updateInlineValidationMessage() {
|
|
102
|
+
const validationMessageElement = this.getValidationMessageElement();
|
|
103
|
+
if (!validationMessageElement) {
|
|
207
104
|
return;
|
|
208
105
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
if (this.correctness === Correctness.PartiallyCorrect) {
|
|
214
|
-
this._internals.states.add('candidate-partially-correct');
|
|
215
|
-
}
|
|
216
|
-
if (this.correctness === Correctness.Incorrect) {
|
|
217
|
-
this._internals.states.add('candidate-incorrect');
|
|
106
|
+
if (!this._internals.validity.valid) {
|
|
107
|
+
validationMessageElement.textContent = this._internals.validationMessage;
|
|
108
|
+
validationMessageElement.style.setProperty('display', 'block', 'important');
|
|
109
|
+
return;
|
|
218
110
|
}
|
|
111
|
+
validationMessageElement.textContent = '';
|
|
112
|
+
validationMessageElement.style.display = 'none';
|
|
219
113
|
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
*/
|
|
228
|
-
this.showCorrectResponse = false;
|
|
229
|
-
this._handleShowCorrectResponseChange = (_, show) => {
|
|
230
|
-
this.toggleInternalCorrectResponse(show);
|
|
231
|
-
};
|
|
232
|
-
/**
|
|
233
|
-
* Shows a cloned interaction with the correct answers filled in.
|
|
234
|
-
* Creates a disabled copy positioned after this interaction.
|
|
235
|
-
*/
|
|
236
|
-
this.showFullCorrectResponse = false;
|
|
237
|
-
this._handleShowFullCorrectResponseChange = (_, show) => {
|
|
238
|
-
this.toggleFullCorrectResponse(show);
|
|
239
|
-
};
|
|
240
|
-
/**
|
|
241
|
-
* Shows feedback on candidate's selections compared to correct response.
|
|
242
|
-
* Adds `candidate-correct` / `candidate-incorrect` states to choices.
|
|
243
|
-
*/
|
|
244
|
-
this.showCandidateCorrection = false;
|
|
245
|
-
this._handleShowCandidateCorrectionChange = (_, show) => {
|
|
246
|
-
this.toggleCandidateCorrection(show);
|
|
247
|
-
};
|
|
248
|
-
/* PK: Correct response */
|
|
249
|
-
this._correctResponse = null;
|
|
250
|
-
this._internals = this.attachInternals();
|
|
251
|
-
}
|
|
252
|
-
get value() {
|
|
253
|
-
return JSON.stringify(this.response);
|
|
254
|
-
}
|
|
255
|
-
set value(val) {
|
|
256
|
-
this.response = val ? JSON.parse(val) : null;
|
|
114
|
+
clearInlineValidationMessage() {
|
|
115
|
+
const validationMessageElement = this.getValidationMessageElement();
|
|
116
|
+
if (!validationMessageElement) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
validationMessageElement.textContent = '';
|
|
120
|
+
validationMessageElement.style.display = 'none';
|
|
257
121
|
}
|
|
258
|
-
|
|
259
|
-
return this.
|
|
122
|
+
getValidationMessageElement() {
|
|
123
|
+
return this.shadowRoot?.querySelector('#validation-message');
|
|
260
124
|
}
|
|
261
125
|
reset() {
|
|
262
126
|
this.response = null;
|
|
263
127
|
}
|
|
264
|
-
/**
|
|
265
|
-
* Called by the browser when the associated form is reset.
|
|
266
|
-
* Part of the Form-Associated Custom Elements API.
|
|
267
|
-
*/
|
|
268
128
|
formResetCallback() {
|
|
269
129
|
this.reset();
|
|
270
130
|
}
|
|
271
|
-
// attributeChangedCallback(name: string, _old: string | null, value: string | null): void {
|
|
272
|
-
// super.attributeChangedCallback(name, _old, value);
|
|
273
|
-
// // changing attributes in lit is not a thing, they are defined in the QTI XML and will never change
|
|
274
|
-
// // except in storybook where we can change the value of the attribute
|
|
275
|
-
// // this can make the internal state out of sync with the attribute
|
|
276
|
-
// // so we reset the value to null to force the internal state to be reset
|
|
277
|
-
// const attributeNamesToExclude = ['style', 'class'];
|
|
278
|
-
// if (!attributeNamesToExclude.includes(name)) {
|
|
279
|
-
// this.reset();
|
|
280
|
-
// }
|
|
281
|
-
// }
|
|
282
131
|
connectedCallback() {
|
|
283
132
|
super.connectedCallback();
|
|
284
|
-
if (this.
|
|
133
|
+
if (!this.registersWithItem) {
|
|
285
134
|
return;
|
|
286
135
|
}
|
|
287
136
|
this.dispatchEvent(new CustomEvent('qti-register-interaction', {
|
|
@@ -294,22 +143,6 @@ export class Interaction extends LitElement {
|
|
|
294
143
|
}
|
|
295
144
|
}));
|
|
296
145
|
}
|
|
297
|
-
firstUpdated() {
|
|
298
|
-
// Skip for full correct response clones to avoid infinite loops
|
|
299
|
-
if (this.isFullCorrectResponse) {
|
|
300
|
-
return;
|
|
301
|
-
}
|
|
302
|
-
// Apply initial correct response display states after first render
|
|
303
|
-
if (this.showCorrectResponse) {
|
|
304
|
-
this.toggleInternalCorrectResponse(true);
|
|
305
|
-
}
|
|
306
|
-
if (this.showFullCorrectResponse) {
|
|
307
|
-
this.toggleFullCorrectResponse(true);
|
|
308
|
-
}
|
|
309
|
-
if (this.showCandidateCorrection) {
|
|
310
|
-
this.toggleCandidateCorrection(true);
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
146
|
saveResponse(value, state) {
|
|
314
147
|
this.dispatchEvent(new CustomEvent('qti-interaction-response', {
|
|
315
148
|
bubbles: true,
|
|
@@ -322,6 +155,24 @@ export class Interaction extends LitElement {
|
|
|
322
155
|
}
|
|
323
156
|
}));
|
|
324
157
|
}
|
|
158
|
+
constructor() {
|
|
159
|
+
super();
|
|
160
|
+
this.#didLogDisableAfterIfMaxChoicesReachedDeprecation = false;
|
|
161
|
+
/*
|
|
162
|
+
* Published to the choice elements inside this interaction — their role, and how to tell
|
|
163
|
+
* whether they are draggable. Provided here on the base rather than in a mixin so that
|
|
164
|
+
* ChoicesMixin and the drag-drop mixins can each contribute without two providers of the same
|
|
165
|
+
* context competing on one element.
|
|
166
|
+
*
|
|
167
|
+
* Reassign, never mutate: an in-place change does not notify consumers.
|
|
168
|
+
*/
|
|
169
|
+
this._interactionContext = { choiceRole: null, draggablesSelector: null };
|
|
170
|
+
this.disabled = false;
|
|
171
|
+
this.readonly = false;
|
|
172
|
+
this._internals = this.attachInternals();
|
|
173
|
+
}
|
|
174
|
+
/** Optional lifecycle bridge for interaction subclasses that do not need changed-property data. */
|
|
175
|
+
firstUpdated(_changedProperties) { }
|
|
325
176
|
}
|
|
326
177
|
__decorate([
|
|
327
178
|
consume({ context: itemContext, subscribe: true })
|
|
@@ -329,6 +180,9 @@ __decorate([
|
|
|
329
180
|
__decorate([
|
|
330
181
|
consume({ context: configContext, subscribe: true })
|
|
331
182
|
], Interaction.prototype, "configContext", void 0);
|
|
183
|
+
__decorate([
|
|
184
|
+
provide({ context: interactionContext })
|
|
185
|
+
], Interaction.prototype, "_interactionContext", void 0);
|
|
332
186
|
__decorate([
|
|
333
187
|
property({ type: String, attribute: 'response-identifier' })
|
|
334
188
|
], Interaction.prototype, "responseIdentifier", void 0);
|
|
@@ -341,31 +195,4 @@ __decorate([
|
|
|
341
195
|
__decorate([
|
|
342
196
|
property({ type: String })
|
|
343
197
|
], Interaction.prototype, "name", void 0);
|
|
344
|
-
__decorate([
|
|
345
|
-
property({ type: String, attribute: 'correct-response', reflect: true })
|
|
346
|
-
], Interaction.prototype, "correctResponseAttr", null);
|
|
347
|
-
__decorate([
|
|
348
|
-
property({ type: Boolean, attribute: 'show-correct-response', reflect: true })
|
|
349
|
-
], Interaction.prototype, "showCorrectResponse", void 0);
|
|
350
|
-
__decorate([
|
|
351
|
-
watch('showCorrectResponse', { waitUntilFirstUpdate: true })
|
|
352
|
-
], Interaction.prototype, "_handleShowCorrectResponseChange", void 0);
|
|
353
|
-
__decorate([
|
|
354
|
-
property({ type: Boolean, attribute: 'show-full-correct-response', reflect: true })
|
|
355
|
-
], Interaction.prototype, "showFullCorrectResponse", void 0);
|
|
356
|
-
__decorate([
|
|
357
|
-
watch('showFullCorrectResponse', { waitUntilFirstUpdate: true })
|
|
358
|
-
], Interaction.prototype, "_handleShowFullCorrectResponseChange", void 0);
|
|
359
|
-
__decorate([
|
|
360
|
-
property({ type: Boolean, attribute: 'show-candidate-correction', reflect: true })
|
|
361
|
-
], Interaction.prototype, "showCandidateCorrection", void 0);
|
|
362
|
-
__decorate([
|
|
363
|
-
watch('showCandidateCorrection', { waitUntilFirstUpdate: true })
|
|
364
|
-
], Interaction.prototype, "_handleShowCandidateCorrectionChange", void 0);
|
|
365
|
-
__decorate([
|
|
366
|
-
state()
|
|
367
|
-
], Interaction.prototype, "_isFullCorrectResponse", void 0);
|
|
368
|
-
__decorate([
|
|
369
|
-
state()
|
|
370
|
-
], Interaction.prototype, "_correctResponse", void 0);
|
|
371
198
|
//# sourceMappingURL=interaction.js.map
|