@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.
Files changed (61) hide show
  1. package/dist/abstract/interaction.d.ts +54 -64
  2. package/dist/abstract/interaction.js +103 -276
  3. package/dist/abstract/interaction.js.map +1 -1
  4. package/dist/abstract/qti-expression.d.ts +1 -1
  5. package/dist/abstract/qti-expression.js +23 -2
  6. package/dist/abstract/qti-expression.js.map +1 -1
  7. package/dist/abstract/qti-feedback.js +1 -1
  8. package/dist/abstract/qti-feedback.js.map +1 -1
  9. package/dist/context/computed-item.context.d.ts +1 -16
  10. package/dist/context/computed-item.context.js.map +1 -1
  11. package/dist/context/computed.context.d.ts +1 -26
  12. package/dist/context/computed.context.js.map +1 -1
  13. package/dist/context/config.context.d.ts +61 -16
  14. package/dist/context/config.context.js.map +1 -1
  15. package/dist/context/interaction.context.d.ts +36 -0
  16. package/dist/context/interaction.context.js +3 -0
  17. package/dist/context/interaction.context.js.map +1 -0
  18. package/dist/context/item.context.d.ts +4 -12
  19. package/dist/context/item.context.js +2 -16
  20. package/dist/context/item.context.js.map +1 -1
  21. package/dist/context/qti.context.d.ts +36 -0
  22. package/dist/context/qti.context.js +13 -0
  23. package/dist/context/qti.context.js.map +1 -1
  24. package/dist/context/test.context.d.ts +1 -1
  25. package/dist/context/test.context.js.map +1 -1
  26. package/dist/context/types/computed-item.types.d.ts +16 -0
  27. package/dist/context/types/computed-item.types.js +2 -0
  28. package/dist/context/types/computed-item.types.js.map +1 -0
  29. package/dist/context/types/computed.types.d.ts +26 -0
  30. package/dist/context/types/computed.types.js +2 -0
  31. package/dist/context/types/computed.types.js.map +1 -0
  32. package/dist/context/types/item.types.d.ts +12 -0
  33. package/dist/context/types/item.types.js +17 -0
  34. package/dist/context/types/item.types.js.map +1 -0
  35. package/dist/elements.d.ts +24 -0
  36. package/dist/elements.js +22 -0
  37. package/dist/elements.js.map +1 -0
  38. package/dist/index.d.ts +9 -1
  39. package/dist/index.js +9 -1
  40. package/dist/index.js.map +1 -1
  41. package/dist/lib/interaction.interface.d.ts +39 -15
  42. package/dist/lib/interaction.interface.js.map +1 -1
  43. package/dist/lib/response.d.ts +104 -0
  44. package/dist/lib/response.js +168 -0
  45. package/dist/lib/response.js.map +1 -0
  46. package/dist/register.d.ts +1 -0
  47. package/dist/register.js +7 -0
  48. package/dist/register.js.map +1 -0
  49. package/dist/styles/box-sizing.styles.d.ts +19 -0
  50. package/dist/styles/box-sizing.styles.js +31 -0
  51. package/dist/styles/box-sizing.styles.js.map +1 -0
  52. package/dist/styles/drop-region.styles.d.ts +36 -0
  53. package/dist/styles/drop-region.styles.js +78 -0
  54. package/dist/styles/drop-region.styles.js.map +1 -0
  55. package/dist/styles/validation-message.styles.d.ts +18 -0
  56. package/dist/styles/validation-message.styles.js +24 -0
  57. package/dist/styles/validation-message.styles.js.map +1 -0
  58. package/package.json +16 -1
  59. package/dist/context/qti-assessment-item.context.d.ts +0 -4
  60. package/dist/context/qti-assessment-item.context.js +0 -3
  61. package/dist/context/qti-assessment-item.context.js.map +0 -1
@@ -1,15 +1,42 @@
1
1
  import { LitElement } from 'lit';
2
- import type { ConfigContext } from '../context/config.context';
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
- import type { IInteraction } from '../lib/interaction.interface';
5
- export declare enum Correctness {
6
- Correct = "correct",
7
- PartiallyCorrect = "partially-correct",
8
- Incorrect = "incorrect"
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
- protected configContext: ConfigContext;
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
- * The correct response for this interaction. Can be set as a comma-separated
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 { property, state } from 'lit/decorators.js';
7
+ import { consume, provide } from '@lit/context';
8
8
  import { LitElement } from 'lit';
9
- import { consume } from '@lit/context';
10
- import { watch } from '@qti-components/utilities';
9
+ import { property } from 'lit/decorators.js';
11
10
  import { configContext } from '../context/config.context';
12
- import { itemContext } from '../context/qti-assessment-item.context';
13
- export var Correctness;
14
- (function (Correctness) {
15
- Correctness["Correct"] = "correct";
16
- Correctness["PartiallyCorrect"] = "partially-correct";
17
- Correctness["Incorrect"] = "incorrect";
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
- // CORRECT RESPONSE - Standalone mode attributes
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
- toggleCorrectResponse(show) {
144
- const correctResponseMode = this?.configContext?.correctResponseMode || 'internal';
145
- if (correctResponseMode === 'full') {
146
- this.toggleFullCorrectResponse(show);
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.toggleInternalCorrectResponse(show);
54
+ this.clearInlineValidationMessage();
55
+ }
56
+ if (mode === 'none') {
57
+ this.clearInlineValidationMessage();
150
58
  }
59
+ return isValid;
151
60
  }
152
- async toggleFullCorrectResponse(show) {
153
- const nextSibling = this.nextSibling;
154
- const nextSiblingIsFullCorrectResponse = nextSibling instanceof HTMLDivElement && nextSibling?.classList.contains('full-correct-response');
155
- // Get correct response from either responseVariable (item context) or local property (standalone)
156
- const correctResponse = this.correctResponse;
157
- if (!correctResponse) {
158
- return;
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
- const showFullCorrectResponse = show &&
161
- (this?.configContext?.fullCorrectResponseOnlyWhenIncorrect === false || this.correctness !== Correctness.Correct);
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
- if (nextSiblingIsFullCorrectResponse) {
171
- return; // Already exists
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
- // Add a clone of interaction with the correct response
174
- const clone = this.cloneNode(true);
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
- else {
185
- containerDiv.classList.add('full-correct-response-block');
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
- containerDiv.role = 'full-correct-response';
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
- toggleInternalCorrectResponse(show) {
195
- // This method is meant to be overridden by mixins like ChoicesMixin
196
- // In the base class, we just update internal state for CSS states
197
- this._internals.states.delete('show-correct-response');
198
- if (show && this.correctResponse) {
199
- this._internals.states.add('show-correct-response');
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
- toggleCandidateCorrection(show) {
203
- this._internals.states.delete('candidate-correct');
204
- this._internals.states.delete('candidate-partially-correct');
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
- // correctness getter now works in both standalone and item context modes
210
- if (this.correctness === Correctness.Correct) {
211
- this._internals.states.add('candidate-correct');
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
- constructor() {
221
- super();
222
- this.disabled = false;
223
- this.readonly = false;
224
- /**
225
- * Shows which choices are correct with inline indicators.
226
- * Adds `correct-response` / `incorrect-response` states to choices.
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
- reportValidity() {
259
- return this._internals.reportValidity();
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.isFullCorrectResponse) {
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