@qti-editor/core 0.1.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.
@@ -0,0 +1,40 @@
1
+ /**
2
+ * QTI Composer - Core Logic
3
+ *
4
+ * Pure functions for building QTI XML from editor context.
5
+ * No Lit/UI dependencies - these can be used in any environment.
6
+ */
7
+ import type { ResponseProcessingKind } from '@qti-editor/interfaces';
8
+ export interface ComposerItemContext {
9
+ identifier?: string;
10
+ title?: string;
11
+ itemBody?: Document;
12
+ }
13
+ export interface ResponseDeclaration {
14
+ identifier: string;
15
+ cardinality: 'single' | 'multiple';
16
+ baseType: 'identifier' | 'point' | 'string';
17
+ correctResponse?: string;
18
+ stringMapping?: {
19
+ defaultValue: number;
20
+ entries: Array<{
21
+ mapKey: string;
22
+ mappedValue: number;
23
+ caseSensitive: boolean;
24
+ }>;
25
+ };
26
+ responseProcessingKind?: ResponseProcessingKind;
27
+ areaMapping?: {
28
+ defaultValue: number;
29
+ entries: Array<{
30
+ shape: 'circle' | 'rect';
31
+ coords: string;
32
+ mappedValue: number;
33
+ }>;
34
+ };
35
+ sourceTag: string;
36
+ }
37
+ export declare function extractResponseDeclarations(itemBodyRoot?: Element | null): ResponseDeclaration[];
38
+ export declare function buildAssessmentItemXml(itemContext?: ComposerItemContext): string;
39
+ export declare function formatXml(xml: string): string;
40
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/composer/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAErE,MAAM,WAAW,mBAAmB;IAClC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACrB;AAED,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,QAAQ,GAAG,UAAU,CAAC;IACnC,QAAQ,EAAE,YAAY,GAAG,OAAO,GAAG,QAAQ,CAAC;IAC5C,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,aAAa,CAAC,EAAE;QACd,YAAY,EAAE,MAAM,CAAC;QACrB,OAAO,EAAE,KAAK,CAAC;YACb,MAAM,EAAE,MAAM,CAAC;YACf,WAAW,EAAE,MAAM,CAAC;YACpB,aAAa,EAAE,OAAO,CAAC;SACxB,CAAC,CAAC;KACJ,CAAC;IACF,sBAAsB,CAAC,EAAE,sBAAsB,CAAC;IAChD,WAAW,CAAC,EAAE;QACZ,YAAY,EAAE,MAAM,CAAC;QACrB,OAAO,EAAE,KAAK,CAAC;YACb,KAAK,EAAE,QAAQ,GAAG,MAAM,CAAC;YACzB,MAAM,EAAE,MAAM,CAAC;YACf,WAAW,EAAE,MAAM,CAAC;SACrB,CAAC,CAAC;KACJ,CAAC;IACF,SAAS,EAAE,MAAM,CAAC;CACnB;AAUD,wBAAgB,2BAA2B,CAAC,YAAY,CAAC,EAAE,OAAO,GAAG,IAAI,GAAG,mBAAmB,EAAE,CAOhG;AAED,wBAAgB,sBAAsB,CAAC,WAAW,CAAC,EAAE,mBAAmB,GAAG,MAAM,CAuHhF;AA6KD,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CA4B7C"}
@@ -0,0 +1,276 @@
1
+ /**
2
+ * QTI Composer - Core Logic
3
+ *
4
+ * Pure functions for building QTI XML from editor context.
5
+ * No Lit/UI dependencies - these can be used in any environment.
6
+ */
7
+ import { getInteractionComposerHandler } from '../interactions/composer.js';
8
+ const QTI_NS = 'http://www.imsglobal.org/xsd/imsqtiasi_v3p0';
9
+ const XSI_NS = 'http://www.w3.org/2001/XMLSchema-instance';
10
+ const XML_NS = 'http://www.w3.org/XML/1998/namespace';
11
+ const SCHEMA_LOCATION = 'http://www.imsglobal.org/xsd/imsqtiasi_v3p0 https://purl.imsglobal.org/spec/qti/v3p0/schema/xsd/imsqti_asiv3p0p1_v1p0.xsd';
12
+ const MATCH_CORRECT_TEMPLATE = 'https://purl.imsglobal.org/spec/qti/v3p0/rptemplates/match_correct';
13
+ export function extractResponseDeclarations(itemBodyRoot) {
14
+ if (!itemBodyRoot)
15
+ return [];
16
+ const tempDoc = document.implementation.createDocument(QTI_NS, 'qti-item-body', null);
17
+ const tempRoot = tempDoc.importNode(itemBodyRoot, true);
18
+ const { declarations } = composeAndNormalizeItemBody(tempRoot, tempDoc);
19
+ return declarations;
20
+ }
21
+ export function buildAssessmentItemXml(itemContext) {
22
+ if (!itemContext)
23
+ return '';
24
+ const xmlDoc = document.implementation.createDocument(QTI_NS, 'qti-assessment-item', null);
25
+ const root = xmlDoc.documentElement;
26
+ root.setAttribute('xmlns', QTI_NS);
27
+ root.setAttributeNS(XSI_NS, 'xsi:schemaLocation', SCHEMA_LOCATION);
28
+ root.setAttribute('identifier', itemContext.identifier?.trim() || 'item-1');
29
+ root.setAttribute('title', itemContext.title?.trim() || 'Untitled Item');
30
+ root.setAttribute('adaptive', 'false');
31
+ root.setAttribute('time-dependent', 'false');
32
+ root.setAttributeNS(XML_NS, 'xml:lang', 'en');
33
+ const sourceBodyDoc = itemContext.itemBody;
34
+ const sourceBodyRoot = sourceBodyDoc?.querySelector('qti-item-body') ??
35
+ (sourceBodyDoc?.documentElement?.tagName.toLowerCase() === 'qti-item-body'
36
+ ? sourceBodyDoc.documentElement
37
+ : null);
38
+ const composedItemBody = sourceBodyRoot != null
39
+ ? xmlDoc.importNode(sourceBodyRoot, true)
40
+ : xmlDoc.createElementNS(QTI_NS, 'qti-item-body');
41
+ const { declarations, responseTemplate, maxScore } = composeAndNormalizeItemBody(composedItemBody, xmlDoc);
42
+ declarations.forEach(declaration => {
43
+ const responseDeclaration = xmlDoc.createElementNS(QTI_NS, 'qti-response-declaration');
44
+ responseDeclaration.setAttribute('identifier', declaration.identifier);
45
+ responseDeclaration.setAttribute('cardinality', declaration.cardinality);
46
+ responseDeclaration.setAttribute('base-type', declaration.baseType);
47
+ if (declaration.correctResponse) {
48
+ const correctResponse = xmlDoc.createElementNS(QTI_NS, 'qti-correct-response');
49
+ const values = declaration.cardinality === 'multiple'
50
+ ? declaration.correctResponse.split(',').map(v => v.trim()).filter(Boolean)
51
+ : [declaration.correctResponse];
52
+ values.forEach(v => {
53
+ const value = xmlDoc.createElementNS(QTI_NS, 'qti-value');
54
+ value.textContent = v;
55
+ correctResponse.appendChild(value);
56
+ });
57
+ responseDeclaration.appendChild(correctResponse);
58
+ }
59
+ if (declaration.stringMapping?.entries.length) {
60
+ const mapping = xmlDoc.createElementNS(QTI_NS, 'qti-mapping');
61
+ mapping.setAttribute('default-value', String(declaration.stringMapping.defaultValue));
62
+ declaration.stringMapping.entries.forEach(entry => {
63
+ const mapEntry = xmlDoc.createElementNS(QTI_NS, 'qti-map-entry');
64
+ mapEntry.setAttribute('map-key', entry.mapKey);
65
+ mapEntry.setAttribute('mapped-value', String(entry.mappedValue));
66
+ mapEntry.setAttribute('case-sensitive', String(entry.caseSensitive));
67
+ mapping.appendChild(mapEntry);
68
+ });
69
+ responseDeclaration.appendChild(mapping);
70
+ }
71
+ if (declaration.areaMapping) {
72
+ const areaMapping = xmlDoc.createElementNS(QTI_NS, 'qti-area-mapping');
73
+ areaMapping.setAttribute('default-value', String(declaration.areaMapping.defaultValue));
74
+ declaration.areaMapping.entries.forEach(entry => {
75
+ const areaMapEntry = xmlDoc.createElementNS(QTI_NS, 'qti-area-map-entry');
76
+ areaMapEntry.setAttribute('shape', entry.shape);
77
+ areaMapEntry.setAttribute('coords', entry.coords);
78
+ areaMapEntry.setAttribute('mapped-value', String(entry.mappedValue));
79
+ areaMapping.appendChild(areaMapEntry);
80
+ });
81
+ responseDeclaration.appendChild(areaMapping);
82
+ }
83
+ root.appendChild(responseDeclaration);
84
+ });
85
+ if (maxScore > 0) {
86
+ const outcomeDeclaration = xmlDoc.createElementNS(QTI_NS, 'qti-outcome-declaration');
87
+ outcomeDeclaration.setAttribute('identifier', 'SCORE');
88
+ outcomeDeclaration.setAttribute('cardinality', 'single');
89
+ outcomeDeclaration.setAttribute('base-type', 'float');
90
+ const scoreDefaultValue = xmlDoc.createElementNS(QTI_NS, 'qti-default-value');
91
+ const scoreValue = xmlDoc.createElementNS(QTI_NS, 'qti-value');
92
+ scoreValue.textContent = '0';
93
+ scoreDefaultValue.appendChild(scoreValue);
94
+ outcomeDeclaration.appendChild(scoreDefaultValue);
95
+ root.appendChild(outcomeDeclaration);
96
+ const maxScoreOutcomeDeclaration = xmlDoc.createElementNS(QTI_NS, 'qti-outcome-declaration');
97
+ maxScoreOutcomeDeclaration.setAttribute('identifier', 'MAX_SCORE');
98
+ maxScoreOutcomeDeclaration.setAttribute('cardinality', 'single');
99
+ maxScoreOutcomeDeclaration.setAttribute('base-type', 'float');
100
+ const maxScoreDefaultValue = xmlDoc.createElementNS(QTI_NS, 'qti-default-value');
101
+ const maxScoreValue = xmlDoc.createElementNS(QTI_NS, 'qti-value');
102
+ maxScoreValue.textContent = String(maxScore);
103
+ maxScoreDefaultValue.appendChild(maxScoreValue);
104
+ maxScoreOutcomeDeclaration.appendChild(maxScoreDefaultValue);
105
+ root.appendChild(maxScoreOutcomeDeclaration);
106
+ }
107
+ root.appendChild(composedItemBody);
108
+ if (maxScore > 0) {
109
+ if (declarations.length === 1) {
110
+ const responseProcessing = xmlDoc.createElementNS(QTI_NS, 'qti-response-processing');
111
+ responseProcessing.setAttribute('template', responseTemplate);
112
+ root.appendChild(responseProcessing);
113
+ }
114
+ else if (declarations.length > 1) {
115
+ root.appendChild(buildMultiInteractionResponseProcessing(xmlDoc, declarations));
116
+ }
117
+ }
118
+ return new XMLSerializer().serializeToString(xmlDoc);
119
+ }
120
+ function composeAndNormalizeItemBody(itemBody, xmlDoc) {
121
+ const declarations = [];
122
+ const seenIdentifiers = new Set();
123
+ const templateCandidates = new Set();
124
+ let maxScore = 0;
125
+ const elements = Array.from(itemBody.querySelectorAll('*'));
126
+ elements.forEach(element => {
127
+ const tagName = element.tagName.toLowerCase();
128
+ const handler = getInteractionComposerHandler(tagName);
129
+ if (handler) {
130
+ const composeResult = handler.compose(element, xmlDoc);
131
+ composeResult.warnings.forEach(warning => {
132
+ console.warn(`[QTI Composer] ${warning.message}`);
133
+ });
134
+ const parent = element.parentNode;
135
+ if (parent) {
136
+ parent.replaceChild(composeResult.normalizedElement, element);
137
+ }
138
+ if (composeResult.responseDeclaration) {
139
+ maxScore += 1;
140
+ }
141
+ if (composeResult.responseDeclaration && !seenIdentifiers.has(composeResult.responseDeclaration.identifier)) {
142
+ const responseProcessingKind = composeResult
143
+ .responseProcessingKind;
144
+ declarations.push({
145
+ ...composeResult.responseDeclaration,
146
+ responseProcessingKind,
147
+ });
148
+ seenIdentifiers.add(composeResult.responseDeclaration.identifier);
149
+ }
150
+ if (composeResult.responseProcessingTemplate && composeResult.responseDeclaration) {
151
+ templateCandidates.add(composeResult.responseProcessingTemplate);
152
+ }
153
+ return;
154
+ }
155
+ console.warn(`[QTI Composer] Missing interaction composer handler for ${tagName}; keeping element as-is during compose.`);
156
+ });
157
+ itemBody.querySelectorAll('[correct-response]').forEach(interaction => {
158
+ interaction.removeAttribute('correct-response');
159
+ });
160
+ normalizeResponseIdentifiers(itemBody, declarations);
161
+ if (declarations.length === 1 && templateCandidates.size === 1) {
162
+ return { declarations, responseTemplate: Array.from(templateCandidates)[0], maxScore };
163
+ }
164
+ return { declarations, responseTemplate: MATCH_CORRECT_TEMPLATE, maxScore };
165
+ }
166
+ function buildMultiInteractionResponseProcessing(xmlDoc, declarations) {
167
+ const responseProcessing = xmlDoc.createElementNS(QTI_NS, 'qti-response-processing');
168
+ declarations.forEach(declaration => {
169
+ const kind = declaration.responseProcessingKind ?? 'match_correct';
170
+ if (kind === 'match_correct') {
171
+ responseProcessing.appendChild(createMatchCorrectContribution(xmlDoc, declaration.identifier));
172
+ return;
173
+ }
174
+ if (kind === 'map_response') {
175
+ responseProcessing.appendChild(createMapResponseContribution(xmlDoc, declaration.identifier));
176
+ return;
177
+ }
178
+ responseProcessing.appendChild(createMapResponsePointContribution(xmlDoc, declaration.identifier));
179
+ });
180
+ return responseProcessing;
181
+ }
182
+ function createMatchCorrectContribution(xmlDoc, responseIdentifier) {
183
+ const responseCondition = xmlDoc.createElementNS(QTI_NS, 'qti-response-condition');
184
+ const responseIf = xmlDoc.createElementNS(QTI_NS, 'qti-response-if');
185
+ const match = xmlDoc.createElementNS(QTI_NS, 'qti-match');
186
+ const variable = xmlDoc.createElementNS(QTI_NS, 'qti-variable');
187
+ variable.setAttribute('identifier', responseIdentifier);
188
+ match.appendChild(variable);
189
+ const correct = xmlDoc.createElementNS(QTI_NS, 'qti-correct');
190
+ correct.setAttribute('identifier', responseIdentifier);
191
+ match.appendChild(correct);
192
+ responseIf.appendChild(match);
193
+ const incrementValue = xmlDoc.createElementNS(QTI_NS, 'qti-base-value');
194
+ incrementValue.setAttribute('base-type', 'float');
195
+ incrementValue.textContent = '1';
196
+ responseIf.appendChild(createScoreIncrement(xmlDoc, incrementValue));
197
+ responseCondition.appendChild(responseIf);
198
+ return responseCondition;
199
+ }
200
+ function createMapResponseContribution(xmlDoc, responseIdentifier) {
201
+ const mapResponse = xmlDoc.createElementNS(QTI_NS, 'qti-map-response');
202
+ const variable = xmlDoc.createElementNS(QTI_NS, 'qti-variable');
203
+ variable.setAttribute('identifier', responseIdentifier);
204
+ mapResponse.appendChild(variable);
205
+ return createScoreIncrement(xmlDoc, mapResponse);
206
+ }
207
+ function createMapResponsePointContribution(xmlDoc, responseIdentifier) {
208
+ const mapResponsePoint = xmlDoc.createElementNS(QTI_NS, 'qti-map-response-point');
209
+ const variable = xmlDoc.createElementNS(QTI_NS, 'qti-variable');
210
+ variable.setAttribute('identifier', responseIdentifier);
211
+ mapResponsePoint.appendChild(variable);
212
+ return createScoreIncrement(xmlDoc, mapResponsePoint);
213
+ }
214
+ function createScoreIncrement(xmlDoc, contribution) {
215
+ const setOutcomeValue = xmlDoc.createElementNS(QTI_NS, 'qti-set-outcome-value');
216
+ setOutcomeValue.setAttribute('identifier', 'SCORE');
217
+ const sum = xmlDoc.createElementNS(QTI_NS, 'qti-sum');
218
+ const currentScore = xmlDoc.createElementNS(QTI_NS, 'qti-variable');
219
+ currentScore.setAttribute('identifier', 'SCORE');
220
+ sum.appendChild(currentScore);
221
+ sum.appendChild(contribution);
222
+ setOutcomeValue.appendChild(sum);
223
+ return setOutcomeValue;
224
+ }
225
+ function normalizeResponseIdentifiers(itemBody, declarations) {
226
+ if (declarations.length === 0)
227
+ return;
228
+ const identifierMap = new Map();
229
+ if (declarations.length === 1) {
230
+ identifierMap.set(declarations[0].identifier, 'RESPONSE');
231
+ }
232
+ else {
233
+ declarations.forEach((declaration, index) => {
234
+ identifierMap.set(declaration.identifier, `RESPONSE${index + 1}`);
235
+ });
236
+ }
237
+ itemBody.querySelectorAll('[response-identifier]').forEach(interaction => {
238
+ const currentIdentifier = interaction.getAttribute('response-identifier')?.trim();
239
+ if (!currentIdentifier)
240
+ return;
241
+ const mappedIdentifier = identifierMap.get(currentIdentifier);
242
+ if (mappedIdentifier) {
243
+ interaction.setAttribute('response-identifier', mappedIdentifier);
244
+ }
245
+ });
246
+ declarations.forEach(declaration => {
247
+ const mappedIdentifier = identifierMap.get(declaration.identifier);
248
+ if (mappedIdentifier) {
249
+ declaration.identifier = mappedIdentifier;
250
+ }
251
+ });
252
+ }
253
+ export function formatXml(xml) {
254
+ const PADDING = ' ';
255
+ const reg = /(>)(<)(\/*)/g;
256
+ let pad = 0;
257
+ xml = xml.replace(reg, '$1\n$2$3');
258
+ return xml
259
+ .split('\n')
260
+ .map(node => {
261
+ const trimmed = node.trim();
262
+ const isClosingTag = /^<\//.test(trimmed);
263
+ const isSelfClosingTag = /\/>$/.test(trimmed);
264
+ const isDeclarationOrDirective = /^<\?/.test(trimmed) || /^<!/.test(trimmed);
265
+ const isOpeningTag = /^<[^!?/][^>]*>$/.test(trimmed) && !isSelfClosingTag;
266
+ if (isClosingTag) {
267
+ pad = Math.max(pad - 1, 0);
268
+ }
269
+ const indent = Math.max(pad, 0);
270
+ if (isOpeningTag && !isDeclarationOrDirective) {
271
+ pad += 1;
272
+ }
273
+ return PADDING.repeat(indent) + node;
274
+ })
275
+ .join('\n');
276
+ }
@@ -0,0 +1,3 @@
1
+ export * from './composer/index.js';
2
+ export * from './interactions/composer.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,cAAc,qBAAqB,CAAC;AACpC,cAAc,4BAA4B,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ /*
2
+ * QTI Core
3
+ *
4
+ * Canonical home for QTI composition, serialization, and metadata orchestration.
5
+ */
6
+ export * from './composer/index.js';
7
+ export * from './interactions/composer.js';
@@ -0,0 +1,8 @@
1
+ import type { InteractionComposerHandler, InteractionComposerMetadata, InteractionDescriptor, NodeAttributePanelMetadata } from '@qti-editor/interfaces';
2
+ export declare function getInteractionComposerMetadata(tagName: string): InteractionComposerMetadata | undefined;
3
+ export declare function getInteractionComposerMetadataByNodeTypeName(nodeTypeName: string): InteractionComposerMetadata | undefined;
4
+ export declare function getNodeAttributePanelMetadataByNodeTypeName(nodeTypeName: string): NodeAttributePanelMetadata | undefined;
5
+ export declare function getInteractionComposerHandler(tagName: string): InteractionComposerHandler | undefined;
6
+ export declare function listInteractionComposerHandlers(): ReadonlyArray<InteractionComposerHandler>;
7
+ export declare function listInteractionDescriptors(): ReadonlyArray<InteractionDescriptor>;
8
+ //# sourceMappingURL=composer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"composer.d.ts","sourceRoot":"","sources":["../../src/interactions/composer.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EACV,0BAA0B,EAC1B,2BAA2B,EAC3B,qBAAqB,EACrB,0BAA0B,EAC3B,MAAM,wBAAwB,CAAC;AA+BhC,wBAAgB,8BAA8B,CAAC,OAAO,EAAE,MAAM,GAAG,2BAA2B,GAAG,SAAS,CAEvG;AAED,wBAAgB,4CAA4C,CAC1D,YAAY,EAAE,MAAM,GACnB,2BAA2B,GAAG,SAAS,CAEzC;AAED,wBAAgB,2CAA2C,CACzD,YAAY,EAAE,MAAM,GACnB,0BAA0B,GAAG,SAAS,CAExC;AAED,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,MAAM,GAAG,0BAA0B,GAAG,SAAS,CAErG;AAED,wBAAgB,+BAA+B,IAAI,aAAa,CAAC,0BAA0B,CAAC,CAE3F;AAED,wBAAgB,0BAA0B,IAAI,aAAa,CAAC,qBAAqB,CAAC,CAEjF"}
@@ -0,0 +1,38 @@
1
+ import { choiceInteractionDescriptor } from '@qti-editor/interaction-choice';
2
+ import { extendedTextInteractionDescriptor } from '@qti-editor/interaction-extended-text';
3
+ import { inlineChoiceInteractionDescriptor } from '@qti-editor/interaction-inline-choice';
4
+ import { matchInteractionDescriptor } from '@qti-editor/interaction-match';
5
+ import { selectPointInteractionDescriptor } from '@qti-editor/interaction-select-point';
6
+ import { textEntryInteractionDescriptor } from '@qti-editor/interaction-text-entry';
7
+ const registeredDescriptors = [
8
+ choiceInteractionDescriptor,
9
+ extendedTextInteractionDescriptor,
10
+ inlineChoiceInteractionDescriptor,
11
+ matchInteractionDescriptor,
12
+ selectPointInteractionDescriptor,
13
+ textEntryInteractionDescriptor,
14
+ ];
15
+ const metadataByTagName = new Map(registeredDescriptors.map(d => [d.tagName, d.composerMetadata]));
16
+ const metadataByNodeTypeName = new Map(registeredDescriptors.map(d => [d.nodeTypeName.toLowerCase(), d.composerMetadata]));
17
+ const handlersByTagName = new Map(registeredDescriptors
18
+ .filter(d => d.composerHandler != null)
19
+ .map(d => [d.tagName, d.composerHandler]));
20
+ const panelMetadataByNodeTypeName = new Map(registeredDescriptors.flatMap(d => Object.entries(d.attributePanelMetadata ?? {})));
21
+ export function getInteractionComposerMetadata(tagName) {
22
+ return metadataByTagName.get(tagName.toLowerCase());
23
+ }
24
+ export function getInteractionComposerMetadataByNodeTypeName(nodeTypeName) {
25
+ return metadataByNodeTypeName.get(nodeTypeName.toLowerCase());
26
+ }
27
+ export function getNodeAttributePanelMetadataByNodeTypeName(nodeTypeName) {
28
+ return panelMetadataByNodeTypeName.get(nodeTypeName.toLowerCase());
29
+ }
30
+ export function getInteractionComposerHandler(tagName) {
31
+ return handlersByTagName.get(tagName.toLowerCase());
32
+ }
33
+ export function listInteractionComposerHandlers() {
34
+ return Array.from(handlersByTagName.values());
35
+ }
36
+ export function listInteractionDescriptors() {
37
+ return registeredDescriptors;
38
+ }
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@qti-editor/core",
3
+ "version": "0.1.0",
4
+ "description": "QTI semantics, composer registry, and XML export orchestration for QTI Editor",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "default": "./dist/index.js"
11
+ },
12
+ "./composer": {
13
+ "types": "./dist/composer/index.d.ts",
14
+ "default": "./dist/composer/index.js"
15
+ },
16
+ "./interactions/composer": {
17
+ "types": "./dist/interactions/composer.d.ts",
18
+ "default": "./dist/interactions/composer.js"
19
+ }
20
+ },
21
+ "dependencies": {
22
+ "@qti-editor/interfaces": "0.1.0",
23
+ "@qti-editor/interaction-choice": "0.1.0",
24
+ "@qti-editor/interaction-extended-text": "0.1.0",
25
+ "@qti-editor/interaction-inline-choice": "0.1.0",
26
+ "@qti-editor/interaction-match": "0.1.0",
27
+ "@qti-editor/interaction-select-point": "0.1.0",
28
+ "@qti-editor/interaction-text-entry": "0.1.0",
29
+ "@qti-editor/interaction-shared": "0.1.0"
30
+ },
31
+ "devDependencies": {
32
+ "@types/node": "^20.0.0",
33
+ "typescript": "^5.0.0"
34
+ },
35
+ "types": "./dist/index.d.ts",
36
+ "files": [
37
+ "dist"
38
+ ],
39
+ "publishConfig": {
40
+ "access": "public"
41
+ },
42
+ "scripts": {
43
+ "build": "tsc",
44
+ "typecheck": "tsc --noEmit",
45
+ "clean": "rm -rf dist"
46
+ }
47
+ }