@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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"test.context.js","sourceRoot":"","sources":["../../src/context/test.context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAU7C,MAAM,CAAC,MAAM,oBAAoB,GAA0B,EAAE,KAAK,EAAE,EAAE,EAAE,oBAAoB,EAAE,EAAE,EAAE,CAAC;AAEnG,MAAM,CAAC,MAAM,WAAW,GAAG,aAAa,CAAwB,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC","sourcesContent":["import { createContext } from '@lit/context';\n\nimport type { ItemContext } from './item.
|
|
1
|
+
{"version":3,"file":"test.context.js","sourceRoot":"","sources":["../../src/context/test.context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAU7C,MAAM,CAAC,MAAM,oBAAoB,GAA0B,EAAE,KAAK,EAAE,EAAE,EAAE,oBAAoB,EAAE,EAAE,EAAE,CAAC;AAEnG,MAAM,CAAC,MAAM,WAAW,GAAG,aAAa,CAAwB,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC","sourcesContent":["import { createContext } from '@lit/context';\n\nimport type { ItemContext } from './types/item.types';\nimport type { VariableDeclaration } from '../lib/variables';\n\nexport interface TestContext {\n items: (ItemContext & { category?: string })[];\n testOutcomeVariables?: VariableDeclaration<string | string[] | null>[];\n}\n\nexport const INITIAL_TEST_CONTEXT: Readonly<TestContext> = { items: [], testOutcomeVariables: [] };\n\nexport const testContext = createContext<Readonly<TestContext>>(Symbol('testContext'));\n"]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { VariableDeclaration } from '../../lib/variables';
|
|
2
|
+
export type ComputedItemContext = {
|
|
3
|
+
identifier: string;
|
|
4
|
+
href?: string;
|
|
5
|
+
correct?: boolean;
|
|
6
|
+
incorrect?: boolean;
|
|
7
|
+
completed?: boolean;
|
|
8
|
+
adaptive?: boolean;
|
|
9
|
+
timeDependent?: boolean;
|
|
10
|
+
title?: string;
|
|
11
|
+
label?: string;
|
|
12
|
+
score?: number;
|
|
13
|
+
maxScore?: number;
|
|
14
|
+
completionStatus?: string;
|
|
15
|
+
variables: ReadonlyArray<VariableDeclaration<string | string[] | null>>;
|
|
16
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"computed-item.types.js","sourceRoot":"","sources":["../../../src/context/types/computed-item.types.ts"],"names":[],"mappings":"","sourcesContent":["import type { VariableDeclaration } from '../../lib/variables';\n\nexport type ComputedItemContext = {\n identifier: string;\n href?: string;\n correct?: boolean;\n incorrect?: boolean;\n completed?: boolean;\n adaptive?: boolean;\n timeDependent?: boolean;\n title?: string;\n label?: string;\n score?: number;\n maxScore?: number;\n completionStatus?: string;\n variables: ReadonlyArray<VariableDeclaration<string | string[] | null>>;\n};\n"]}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { View } from '../session.context';
|
|
2
|
+
import type { ComputedItemContext } from './computed-item.types';
|
|
3
|
+
export type ComputedItem = ComputedItemContext & {
|
|
4
|
+
categories?: string[];
|
|
5
|
+
type?: 'info' | 'regular';
|
|
6
|
+
index?: number;
|
|
7
|
+
active?: boolean;
|
|
8
|
+
};
|
|
9
|
+
export type ComputedContext = {
|
|
10
|
+
view: View;
|
|
11
|
+
identifier: string;
|
|
12
|
+
title: string;
|
|
13
|
+
testParts: {
|
|
14
|
+
active?: boolean;
|
|
15
|
+
identifier: string;
|
|
16
|
+
navigationMode: 'linear' | 'nonlinear';
|
|
17
|
+
submissionMode: 'individual' | 'simultaneous';
|
|
18
|
+
sections: {
|
|
19
|
+
active?: boolean;
|
|
20
|
+
identifier: string;
|
|
21
|
+
title: string;
|
|
22
|
+
completed?: boolean;
|
|
23
|
+
items: ComputedItem[];
|
|
24
|
+
}[];
|
|
25
|
+
}[];
|
|
26
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"computed.types.js","sourceRoot":"","sources":["../../../src/context/types/computed.types.ts"],"names":[],"mappings":"","sourcesContent":["import type { View } from '../session.context';\nimport type { ComputedItemContext } from './computed-item.types';\n\nexport type ComputedItem = ComputedItemContext & {\n categories?: string[]; // not necessary for outside world\n type?: 'info' | 'regular';\n index?: number;\n active?: boolean;\n};\n\nexport type ComputedContext = {\n view: View;\n identifier: string;\n title: string;\n testParts: {\n active?: boolean;\n identifier: string;\n navigationMode: 'linear' | 'nonlinear';\n submissionMode: 'individual' | 'simultaneous';\n sections: {\n active?: boolean;\n identifier: string;\n title: string;\n completed?: boolean;\n items: ComputedItem[];\n }[];\n }[];\n};\n"]}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { VariableDeclaration } from '../../lib/variables';
|
|
2
|
+
export interface ItemContext {
|
|
3
|
+
identifier?: string;
|
|
4
|
+
href?: string;
|
|
5
|
+
variables?: ReadonlyArray<VariableDeclaration<string | string[] | null>>;
|
|
6
|
+
/**
|
|
7
|
+
* Optional per-interaction opaque state, keyed by responseIdentifier.
|
|
8
|
+
* Used for interactions that support state save/restore (e.g. PCI).
|
|
9
|
+
*/
|
|
10
|
+
state?: Record<string, string | null>;
|
|
11
|
+
}
|
|
12
|
+
export declare const itemContextVariables: VariableDeclaration<string | string[]>[];
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export const itemContextVariables = [
|
|
2
|
+
{
|
|
3
|
+
identifier: 'completionStatus',
|
|
4
|
+
cardinality: 'single',
|
|
5
|
+
baseType: 'string',
|
|
6
|
+
value: 'unknown',
|
|
7
|
+
type: 'outcome'
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
identifier: 'numAttempts',
|
|
11
|
+
cardinality: 'single',
|
|
12
|
+
baseType: 'integer',
|
|
13
|
+
value: '0',
|
|
14
|
+
type: 'response'
|
|
15
|
+
}
|
|
16
|
+
];
|
|
17
|
+
//# sourceMappingURL=item.types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"item.types.js","sourceRoot":"","sources":["../../../src/context/types/item.types.ts"],"names":[],"mappings":"AAaA,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAClC;QACE,UAAU,EAAE,kBAAkB;QAC9B,WAAW,EAAE,QAAQ;QACrB,QAAQ,EAAE,QAAQ;QAClB,KAAK,EAAE,SAAS;QAChB,IAAI,EAAE,SAAS;KAChB;IACD;QACE,UAAU,EAAE,aAAa;QACzB,WAAW,EAAE,QAAQ;QACrB,QAAQ,EAAE,SAAS;QACnB,KAAK,EAAE,GAAG;QACV,IAAI,EAAE,UAAU;KACjB;CAC0C,CAAC","sourcesContent":["import type { VariableDeclaration } from '../../lib/variables';\n\nexport interface ItemContext {\n identifier?: string;\n href?: string;\n variables?: ReadonlyArray<VariableDeclaration<string | string[] | null>>;\n /**\n * Optional per-interaction opaque state, keyed by responseIdentifier.\n * Used for interactions that support state save/restore (e.g. PCI).\n */\n state?: Record<string, string | null>;\n}\n\nexport const itemContextVariables = [\n {\n identifier: 'completionStatus',\n cardinality: 'single',\n baseType: 'string',\n value: 'unknown',\n type: 'outcome'\n },\n {\n identifier: 'numAttempts',\n cardinality: 'single',\n baseType: 'integer',\n value: '0',\n type: 'response'\n }\n] as VariableDeclaration<string | string[]>[];\n"]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom elements this package registers into an application's registry.
|
|
3
|
+
*
|
|
4
|
+
* Empty, deliberately. `qti-base` is abstractions and context — it contributes no elements to a
|
|
5
|
+
* delivery application.
|
|
6
|
+
*
|
|
7
|
+
* The one entry that used to be here, `qti-config-test-provider`, is gone entirely. It was a
|
|
8
|
+
* TEST-ONLY wrapper that every consumer of `import '@qti-components/base'` registered transitively,
|
|
9
|
+
* and it carried `@customElement(...)` on top of this list — an UNGUARDED `customElements.define()`
|
|
10
|
+
* at module-evaluation time. A module graph holding two copies of this package (a workspace build
|
|
11
|
+
* alongside a published dist, which is what the editor resolves) evaluated it twice and the second
|
|
12
|
+
* threw `NotSupportedError: ... already been used with this registry`, aborting module evaluation
|
|
13
|
+
* and taking down every test file that imported it — 34 suites in the editor, none of which used
|
|
14
|
+
* the element. `register.ts` had the guard right all along; the decorator bypassed it.
|
|
15
|
+
*
|
|
16
|
+
* Config for a single interaction is a property binding now — see config.context.ts.
|
|
17
|
+
*
|
|
18
|
+
* The array and `register.ts` stay so the elements + guarded-register convention the other 30
|
|
19
|
+
* packages follow is already in place the day this package does own an element.
|
|
20
|
+
*/
|
|
21
|
+
export declare const qtiBaseElements: readonly {
|
|
22
|
+
tag: string;
|
|
23
|
+
ctor: CustomElementConstructor;
|
|
24
|
+
}[];
|
package/dist/elements.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom elements this package registers into an application's registry.
|
|
3
|
+
*
|
|
4
|
+
* Empty, deliberately. `qti-base` is abstractions and context — it contributes no elements to a
|
|
5
|
+
* delivery application.
|
|
6
|
+
*
|
|
7
|
+
* The one entry that used to be here, `qti-config-test-provider`, is gone entirely. It was a
|
|
8
|
+
* TEST-ONLY wrapper that every consumer of `import '@qti-components/base'` registered transitively,
|
|
9
|
+
* and it carried `@customElement(...)` on top of this list — an UNGUARDED `customElements.define()`
|
|
10
|
+
* at module-evaluation time. A module graph holding two copies of this package (a workspace build
|
|
11
|
+
* alongside a published dist, which is what the editor resolves) evaluated it twice and the second
|
|
12
|
+
* threw `NotSupportedError: ... already been used with this registry`, aborting module evaluation
|
|
13
|
+
* and taking down every test file that imported it — 34 suites in the editor, none of which used
|
|
14
|
+
* the element. `register.ts` had the guard right all along; the decorator bypassed it.
|
|
15
|
+
*
|
|
16
|
+
* Config for a single interaction is a property binding now — see config.context.ts.
|
|
17
|
+
*
|
|
18
|
+
* The array and `register.ts` stay so the elements + guarded-register convention the other 30
|
|
19
|
+
* packages follow is already in place the day this package does own an element.
|
|
20
|
+
*/
|
|
21
|
+
export const qtiBaseElements = [];
|
|
22
|
+
//# sourceMappingURL=elements.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"elements.js","sourceRoot":"","sources":["../src/elements.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,CAAC,MAAM,eAAe,GAA+D,EAAE,CAAC","sourcesContent":["/**\n * Custom elements this package registers into an application's registry.\n *\n * Empty, deliberately. `qti-base` is abstractions and context — it contributes no elements to a\n * delivery application.\n *\n * The one entry that used to be here, `qti-config-test-provider`, is gone entirely. It was a\n * TEST-ONLY wrapper that every consumer of `import '@qti-components/base'` registered transitively,\n * and it carried `@customElement(...)` on top of this list — an UNGUARDED `customElements.define()`\n * at module-evaluation time. A module graph holding two copies of this package (a workspace build\n * alongside a published dist, which is what the editor resolves) evaluated it twice and the second\n * threw `NotSupportedError: ... already been used with this registry`, aborting module evaluation\n * and taking down every test file that imported it — 34 suites in the editor, none of which used\n * the element. `register.ts` had the guard right all along; the decorator bypassed it.\n *\n * Config for a single interaction is a property binding now — see config.context.ts.\n *\n * The array and `register.ts` stay so the elements + guarded-register convention the other 30\n * packages follow is already in place the day this package does own an element.\n */\nexport const qtiBaseElements: readonly { tag: string; ctor: CustomElementConstructor }[] = [];\n"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,16 +1,24 @@
|
|
|
1
|
+
import './register';
|
|
1
2
|
export * from './context/computed-item.context';
|
|
3
|
+
export * from './context/types/computed-item.types';
|
|
2
4
|
export * from './context/computed.context';
|
|
5
|
+
export * from './context/types/computed.types';
|
|
3
6
|
export * from './context/config.context';
|
|
7
|
+
export * from './context/interaction.context';
|
|
4
8
|
export * from './context/item.context';
|
|
5
|
-
export * from './context/
|
|
9
|
+
export * from './context/types/item.types';
|
|
6
10
|
export * from './context/qti.context';
|
|
7
11
|
export * from './context/session.context';
|
|
8
12
|
export * from './context/test.context';
|
|
13
|
+
export * from './styles/box-sizing.styles';
|
|
14
|
+
export * from './styles/drop-region.styles';
|
|
15
|
+
export * from './styles/validation-message.styles';
|
|
9
16
|
export * from './abstract/interaction';
|
|
10
17
|
export * from './abstract/qti-variable-declaration';
|
|
11
18
|
export * from './abstract/qti-condition-expression';
|
|
12
19
|
export * from './abstract/qti-feedback';
|
|
13
20
|
export * from './abstract/qti-expression';
|
|
21
|
+
export * from './lib/response';
|
|
14
22
|
export * from './lib/expression-result';
|
|
15
23
|
export * from './lib/interaction.interface';
|
|
16
24
|
export * from './lib/variables';
|
package/dist/index.js
CHANGED
|
@@ -1,16 +1,24 @@
|
|
|
1
|
+
import './register';
|
|
1
2
|
export * from './context/computed-item.context';
|
|
3
|
+
export * from './context/types/computed-item.types';
|
|
2
4
|
export * from './context/computed.context';
|
|
5
|
+
export * from './context/types/computed.types';
|
|
3
6
|
export * from './context/config.context';
|
|
7
|
+
export * from './context/interaction.context';
|
|
4
8
|
export * from './context/item.context';
|
|
5
|
-
export * from './context/
|
|
9
|
+
export * from './context/types/item.types';
|
|
6
10
|
export * from './context/qti.context';
|
|
7
11
|
export * from './context/session.context';
|
|
8
12
|
export * from './context/test.context';
|
|
13
|
+
export * from './styles/box-sizing.styles';
|
|
14
|
+
export * from './styles/drop-region.styles';
|
|
15
|
+
export * from './styles/validation-message.styles';
|
|
9
16
|
export * from './abstract/interaction';
|
|
10
17
|
export * from './abstract/qti-variable-declaration';
|
|
11
18
|
export * from './abstract/qti-condition-expression';
|
|
12
19
|
export * from './abstract/qti-feedback';
|
|
13
20
|
export * from './abstract/qti-expression';
|
|
21
|
+
export * from './lib/response';
|
|
14
22
|
export * from './lib/expression-result';
|
|
15
23
|
export * from './lib/interaction.interface';
|
|
16
24
|
export * from './lib/variables';
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,iCAAiC,CAAC;AAChD,cAAc,4BAA4B,CAAC;AAC3C,cAAc,0BAA0B,CAAC;AACzC,cAAc,wBAAwB,CAAC;AACvC,cAAc,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,CAAC;AAEpB,cAAc,iCAAiC,CAAC;AAChD,cAAc,qCAAqC,CAAC;AACpD,cAAc,4BAA4B,CAAC;AAC3C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,0BAA0B,CAAC;AACzC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,wBAAwB,CAAC;AACvC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,uBAAuB,CAAC;AACtC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,wBAAwB,CAAC;AAEvC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,oCAAoC,CAAC;AAEnD,cAAc,wBAAwB,CAAC;AACvC,cAAc,qCAAqC,CAAC;AACpD,cAAc,qCAAqC,CAAC;AACpD,cAAc,yBAAyB,CAAC;AACxC,cAAc,2BAA2B,CAAC;AAE1C,cAAc,gBAAgB,CAAC;AAC/B,cAAc,yBAAyB,CAAC;AACxC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC;AAEpC,cAAc,wBAAwB,CAAC;AACvC,cAAc,eAAe,CAAC","sourcesContent":["import './register';\n\nexport * from './context/computed-item.context';\nexport * from './context/types/computed-item.types';\nexport * from './context/computed.context';\nexport * from './context/types/computed.types';\nexport * from './context/config.context';\nexport * from './context/interaction.context';\nexport * from './context/item.context';\nexport * from './context/types/item.types';\nexport * from './context/qti.context';\nexport * from './context/session.context';\nexport * from './context/test.context';\n\nexport * from './styles/box-sizing.styles';\nexport * from './styles/drop-region.styles';\nexport * from './styles/validation-message.styles';\n\nexport * from './abstract/interaction';\nexport * from './abstract/qti-variable-declaration';\nexport * from './abstract/qti-condition-expression';\nexport * from './abstract/qti-feedback';\nexport * from './abstract/qti-expression';\n\nexport * from './lib/response';\nexport * from './lib/expression-result';\nexport * from './lib/interaction.interface';\nexport * from './lib/variables';\nexport * from './lib/qti-rule-base';\n\nexport * from './utils/scoring-helper';\nexport * from './utils/utils';\n"]}
|
|
@@ -1,20 +1,44 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
showCandidateCorrection: boolean;
|
|
10
|
-
response: string | string[];
|
|
11
|
-
responseIdentifier: string;
|
|
1
|
+
/**
|
|
2
|
+
* Smallest shared contract for any form-associated custom element.
|
|
3
|
+
*
|
|
4
|
+
* Keep this intentionally tiny and platform-aligned. QTI-specific concerns
|
|
5
|
+
* such as response identifiers, readonly handling, and validation
|
|
6
|
+
* should be layered via separate capability interfaces below.
|
|
7
|
+
*/
|
|
8
|
+
export interface FormAssociatedInteraction extends HTMLElement {
|
|
12
9
|
disabled: boolean;
|
|
10
|
+
readonly internals: ElementInternals;
|
|
11
|
+
formResetCallback(): void;
|
|
12
|
+
}
|
|
13
|
+
/** QTI response identity capability. */
|
|
14
|
+
export interface ResponseIdentifiedInteraction {
|
|
15
|
+
responseIdentifier: string;
|
|
16
|
+
}
|
|
17
|
+
/** Optional readonly capability used by many QTI interactions. */
|
|
18
|
+
export interface ReadonlyInteraction {
|
|
13
19
|
readonly: boolean;
|
|
20
|
+
}
|
|
21
|
+
/** Optional reset capability beyond the platform form reset callback. */
|
|
22
|
+
export interface ResettableInteraction {
|
|
23
|
+
reset(): void;
|
|
24
|
+
}
|
|
25
|
+
/** Interaction that publishes and stores a response value. */
|
|
26
|
+
export interface ResponseInteractionElement extends FormAssociatedInteraction {
|
|
27
|
+
responseIdentifier: string;
|
|
28
|
+
response: string | string[] | null;
|
|
29
|
+
saveResponse(value: string | string[], state?: string | null): void;
|
|
30
|
+
}
|
|
31
|
+
/** Interaction that participates in validity reporting. */
|
|
32
|
+
export interface ValidatableInteraction extends FormAssociatedInteraction {
|
|
14
33
|
validate(): boolean;
|
|
15
34
|
reportValidity(): boolean;
|
|
16
|
-
reset(): void;
|
|
17
|
-
saveResponse(value: string | string[]): void;
|
|
18
|
-
toggleCorrectResponse(show: boolean): void;
|
|
19
|
-
toggleCandidateCorrection(show: boolean): void;
|
|
20
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* Common contract expected by item containers when tracking registered interactions.
|
|
38
|
+
*/
|
|
39
|
+
export interface RegisteredInteraction extends ResponseInteractionElement, ValidatableInteraction, ReadonlyInteraction, ResettableInteraction {
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* @deprecated Prefer `RegisteredInteraction` for new code.
|
|
43
|
+
*/
|
|
44
|
+
export type IInteraction = RegisteredInteraction;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"interaction.interface.js","sourceRoot":"","sources":["../../src/lib/interaction.interface.ts"],"names":[],"mappings":"","sourcesContent":["
|
|
1
|
+
{"version":3,"file":"interaction.interface.js","sourceRoot":"","sources":["../../src/lib/interaction.interface.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * Smallest shared contract for any form-associated custom element.\n *\n * Keep this intentionally tiny and platform-aligned. QTI-specific concerns\n * such as response identifiers, readonly handling, and validation\n * should be layered via separate capability interfaces below.\n */\nexport interface FormAssociatedInteraction extends HTMLElement {\n disabled: boolean;\n readonly internals: ElementInternals;\n formResetCallback(): void;\n}\n\n/** QTI response identity capability. */\nexport interface ResponseIdentifiedInteraction {\n responseIdentifier: string;\n}\n\n/** Optional readonly capability used by many QTI interactions. */\nexport interface ReadonlyInteraction {\n readonly: boolean;\n}\n\n/** Optional reset capability beyond the platform form reset callback. */\nexport interface ResettableInteraction {\n reset(): void;\n}\n\n/** Interaction that publishes and stores a response value. */\nexport interface ResponseInteractionElement extends FormAssociatedInteraction {\n responseIdentifier: string;\n response: string | string[] | null;\n saveResponse(value: string | string[], state?: string | null): void;\n}\n\n/** Interaction that participates in validity reporting. */\nexport interface ValidatableInteraction extends FormAssociatedInteraction {\n validate(): boolean;\n reportValidity(): boolean;\n}\n\n/**\n * Common contract expected by item containers when tracking registered interactions.\n */\nexport interface RegisteredInteraction\n extends ResponseInteractionElement,\n ValidatableInteraction,\n ReadonlyInteraction,\n ResettableInteraction {}\n\n/**\n * @deprecated Prefer `RegisteredInteraction` for new code.\n */\nexport type IInteraction = RegisteredInteraction;\n"]}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical `response="..."` / `correct-response="..."` attribute codec.
|
|
3
|
+
*
|
|
4
|
+
* Authors put a single string on an interaction element. This module is the
|
|
5
|
+
* single source of truth for how that string parses into the runtime's
|
|
6
|
+
* internal value AND how an editor serializes the internal value back to the
|
|
7
|
+
* attribute. Both `@qti-components/*` interactions and `@citolab/prose-qti`
|
|
8
|
+
* (the editor) import from here — no per-codebase parsers, no drift.
|
|
9
|
+
*
|
|
10
|
+
* The two attributes share the same grammar; the codec is agnostic about
|
|
11
|
+
* which of them is being parsed.
|
|
12
|
+
*
|
|
13
|
+
* ---
|
|
14
|
+
*
|
|
15
|
+
* Format reference (per interaction):
|
|
16
|
+
*
|
|
17
|
+
* | Interaction | base-type | cardinality | attribute value |
|
|
18
|
+
* |----------------------|---------------|----------------------|---------------------------|
|
|
19
|
+
* | choice | identifier | single / multiple | "A" / "A,B,C" |
|
|
20
|
+
* | inline-choice | identifier | single | "Y" |
|
|
21
|
+
* | hottext | identifier | single / multiple | "h1" / "h1,h2" |
|
|
22
|
+
* | hotspot | identifier | single / multiple | "spot1" / "spot1,spot2" |
|
|
23
|
+
* | order | identifier | ordered | "a,b,c" (order matters) |
|
|
24
|
+
* | graphic-order | identifier | ordered | "spot1,spot2,spot3" |
|
|
25
|
+
* | match | directedPair | multiple | "src1 tgt1,src2 tgt2" |
|
|
26
|
+
* | associate | directedPair | multiple | "A B,C D" |
|
|
27
|
+
* | gap-match | directedPair | multiple | "gtext1 gap1,..." |
|
|
28
|
+
* | graphic-associate | directedPair | multiple | "spot1 spot2,..." |
|
|
29
|
+
* | graphic-gap-match | directedPair | multiple | "gimg1 spot1,..." |
|
|
30
|
+
* | select-point | point | single / multiple | "100 150" / "100 150,…" |
|
|
31
|
+
* | slider | integer | single | "50" |
|
|
32
|
+
* | text-entry | string | single | "paris" |
|
|
33
|
+
*
|
|
34
|
+
* Rules:
|
|
35
|
+
* - Comma `,` separates VALUES (different identifiers / pairs / points).
|
|
36
|
+
* - Space ` ` separates PARTS within one value (pair members or point coords).
|
|
37
|
+
* - Each value is trimmed on parse.
|
|
38
|
+
* - No escape mechanism. Identifiers that contain commas cannot round-trip.
|
|
39
|
+
*/
|
|
40
|
+
/** Canonical parsed value of a `response` / `correct-response` attribute. */
|
|
41
|
+
export type ResponseValue = string | string[] | null;
|
|
42
|
+
/** Identifier literal — opaque QTI identifier (e.g. `"choice1"`). */
|
|
43
|
+
export type Identifier = string;
|
|
44
|
+
/** Directed pair literal — `"sourceId targetId"`. Used by match / associate / gap-match. */
|
|
45
|
+
export type DirectedPair = `${string} ${string}`;
|
|
46
|
+
/** Point literal — `"x y"` integer coordinates. Used by select-point / graphic-*. */
|
|
47
|
+
export type Point = `${number} ${number}`;
|
|
48
|
+
/**
|
|
49
|
+
* Parse a raw `response` / `correct-response` attribute string into the
|
|
50
|
+
* canonical value.
|
|
51
|
+
*
|
|
52
|
+
* - `null` / `undefined` / empty → `null`
|
|
53
|
+
* - no comma → trimmed string
|
|
54
|
+
* - one or more commas → trimmed array (single-element collapses to a string)
|
|
55
|
+
*/
|
|
56
|
+
export declare function parseResponseAttribute(raw: string | null | undefined): ResponseValue;
|
|
57
|
+
/**
|
|
58
|
+
* Serialize a canonical value back to the attribute string. Returns `null` for
|
|
59
|
+
* empty inputs so a Lit `@property({ reflect: true })` drops the attribute.
|
|
60
|
+
*/
|
|
61
|
+
export declare function serializeResponseAttribute(value: ResponseValue): string | null;
|
|
62
|
+
/**
|
|
63
|
+
* Iterate over the comma-separated entries in a response value, regardless of
|
|
64
|
+
* whether the caller has the raw string, the parsed string, or a string[].
|
|
65
|
+
* Yields trimmed non-empty entries.
|
|
66
|
+
*/
|
|
67
|
+
export declare function iterResponseValues(raw: ResponseValue): Generator<string>;
|
|
68
|
+
/** Parse a directed-pair value into `{ source, target }`. Throws on malformed input. */
|
|
69
|
+
export declare function parsePair(value: string): {
|
|
70
|
+
source: string;
|
|
71
|
+
target: string;
|
|
72
|
+
};
|
|
73
|
+
/** Serialize a directed pair to canonical `"source target"` form. */
|
|
74
|
+
export declare function serializePair(source: string, target: string): DirectedPair;
|
|
75
|
+
/** Parse a point value into `{ x, y }`. Throws on malformed input. */
|
|
76
|
+
export declare function parsePoint(value: string): {
|
|
77
|
+
x: number;
|
|
78
|
+
y: number;
|
|
79
|
+
};
|
|
80
|
+
/** Serialize a point to canonical `"x y"` form. */
|
|
81
|
+
export declare function serializePoint(x: number, y: number): Point;
|
|
82
|
+
/**
|
|
83
|
+
* Ready-to-use Lit `converter` for `response` / `correct-response` attributes.
|
|
84
|
+
* Assign to the `converter` option:
|
|
85
|
+
*
|
|
86
|
+
* @property({ attribute: 'response', converter: responseAttributeConverter({ emptyAs: [] }) })
|
|
87
|
+
*
|
|
88
|
+
* `emptyAs` picks the sentinel returned when the attribute is absent / blank.
|
|
89
|
+
* Three sentinels exist across the codebase — match the one your call site
|
|
90
|
+
* already uses. Future work may normalize to a single sentinel.
|
|
91
|
+
*
|
|
92
|
+
* - '' (empty string) — ChoicesMixin convention
|
|
93
|
+
* - [] — DragDropSlottedMixin / graphic-associate convention
|
|
94
|
+
* - null — base interaction / select-point convention
|
|
95
|
+
*
|
|
96
|
+
* On the serialize side, the `emptyAs` value roundtrips back to `null` so a
|
|
97
|
+
* `reflect: true` property drops the attribute rather than emitting `""`.
|
|
98
|
+
*/
|
|
99
|
+
export declare function responseAttributeConverter<E extends '' | null | readonly unknown[]>(opts: {
|
|
100
|
+
emptyAs: E;
|
|
101
|
+
}): {
|
|
102
|
+
fromAttribute: (value: string | null) => ResponseValue | E;
|
|
103
|
+
toAttribute: (value: ResponseValue | E) => string | null;
|
|
104
|
+
};
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical `response="..."` / `correct-response="..."` attribute codec.
|
|
3
|
+
*
|
|
4
|
+
* Authors put a single string on an interaction element. This module is the
|
|
5
|
+
* single source of truth for how that string parses into the runtime's
|
|
6
|
+
* internal value AND how an editor serializes the internal value back to the
|
|
7
|
+
* attribute. Both `@qti-components/*` interactions and `@citolab/prose-qti`
|
|
8
|
+
* (the editor) import from here — no per-codebase parsers, no drift.
|
|
9
|
+
*
|
|
10
|
+
* The two attributes share the same grammar; the codec is agnostic about
|
|
11
|
+
* which of them is being parsed.
|
|
12
|
+
*
|
|
13
|
+
* ---
|
|
14
|
+
*
|
|
15
|
+
* Format reference (per interaction):
|
|
16
|
+
*
|
|
17
|
+
* | Interaction | base-type | cardinality | attribute value |
|
|
18
|
+
* |----------------------|---------------|----------------------|---------------------------|
|
|
19
|
+
* | choice | identifier | single / multiple | "A" / "A,B,C" |
|
|
20
|
+
* | inline-choice | identifier | single | "Y" |
|
|
21
|
+
* | hottext | identifier | single / multiple | "h1" / "h1,h2" |
|
|
22
|
+
* | hotspot | identifier | single / multiple | "spot1" / "spot1,spot2" |
|
|
23
|
+
* | order | identifier | ordered | "a,b,c" (order matters) |
|
|
24
|
+
* | graphic-order | identifier | ordered | "spot1,spot2,spot3" |
|
|
25
|
+
* | match | directedPair | multiple | "src1 tgt1,src2 tgt2" |
|
|
26
|
+
* | associate | directedPair | multiple | "A B,C D" |
|
|
27
|
+
* | gap-match | directedPair | multiple | "gtext1 gap1,..." |
|
|
28
|
+
* | graphic-associate | directedPair | multiple | "spot1 spot2,..." |
|
|
29
|
+
* | graphic-gap-match | directedPair | multiple | "gimg1 spot1,..." |
|
|
30
|
+
* | select-point | point | single / multiple | "100 150" / "100 150,…" |
|
|
31
|
+
* | slider | integer | single | "50" |
|
|
32
|
+
* | text-entry | string | single | "paris" |
|
|
33
|
+
*
|
|
34
|
+
* Rules:
|
|
35
|
+
* - Comma `,` separates VALUES (different identifiers / pairs / points).
|
|
36
|
+
* - Space ` ` separates PARTS within one value (pair members or point coords).
|
|
37
|
+
* - Each value is trimmed on parse.
|
|
38
|
+
* - No escape mechanism. Identifiers that contain commas cannot round-trip.
|
|
39
|
+
*/
|
|
40
|
+
// ─── attribute-level parser / serializer ─────────────────────────────────────
|
|
41
|
+
/**
|
|
42
|
+
* Parse a raw `response` / `correct-response` attribute string into the
|
|
43
|
+
* canonical value.
|
|
44
|
+
*
|
|
45
|
+
* - `null` / `undefined` / empty → `null`
|
|
46
|
+
* - no comma → trimmed string
|
|
47
|
+
* - one or more commas → trimmed array (single-element collapses to a string)
|
|
48
|
+
*/
|
|
49
|
+
export function parseResponseAttribute(raw) {
|
|
50
|
+
if (raw == null)
|
|
51
|
+
return null;
|
|
52
|
+
if (!raw.includes(',')) {
|
|
53
|
+
const trimmed = raw.trim();
|
|
54
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
55
|
+
}
|
|
56
|
+
const tokens = raw
|
|
57
|
+
.split(',')
|
|
58
|
+
.map(token => token.trim())
|
|
59
|
+
.filter(token => token.length > 0);
|
|
60
|
+
if (tokens.length === 0)
|
|
61
|
+
return null;
|
|
62
|
+
if (tokens.length === 1)
|
|
63
|
+
return tokens[0];
|
|
64
|
+
return tokens;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Serialize a canonical value back to the attribute string. Returns `null` for
|
|
68
|
+
* empty inputs so a Lit `@property({ reflect: true })` drops the attribute.
|
|
69
|
+
*/
|
|
70
|
+
export function serializeResponseAttribute(value) {
|
|
71
|
+
if (value == null)
|
|
72
|
+
return null;
|
|
73
|
+
if (Array.isArray(value)) {
|
|
74
|
+
const tokens = value.map(token => token.trim()).filter(token => token.length > 0);
|
|
75
|
+
return tokens.length > 0 ? tokens.join(',') : null;
|
|
76
|
+
}
|
|
77
|
+
const trimmed = value.trim();
|
|
78
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
79
|
+
}
|
|
80
|
+
// ─── value-shape helpers (per interaction type) ──────────────────────────────
|
|
81
|
+
/**
|
|
82
|
+
* Iterate over the comma-separated entries in a response value, regardless of
|
|
83
|
+
* whether the caller has the raw string, the parsed string, or a string[].
|
|
84
|
+
* Yields trimmed non-empty entries.
|
|
85
|
+
*/
|
|
86
|
+
export function* iterResponseValues(raw) {
|
|
87
|
+
if (raw == null)
|
|
88
|
+
return;
|
|
89
|
+
if (Array.isArray(raw)) {
|
|
90
|
+
for (const v of raw) {
|
|
91
|
+
const t = v.trim();
|
|
92
|
+
if (t)
|
|
93
|
+
yield t;
|
|
94
|
+
}
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
if (!raw.includes(',')) {
|
|
98
|
+
const t = raw.trim();
|
|
99
|
+
if (t)
|
|
100
|
+
yield t;
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
for (const v of raw.split(',')) {
|
|
104
|
+
const t = v.trim();
|
|
105
|
+
if (t)
|
|
106
|
+
yield t;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/** Parse a directed-pair value into `{ source, target }`. Throws on malformed input. */
|
|
110
|
+
export function parsePair(value) {
|
|
111
|
+
const [source, target, ...rest] = value.trim().split(/\s+/);
|
|
112
|
+
if (!source || !target || rest.length > 0) {
|
|
113
|
+
throw new Error(`malformed directed-pair value: "${value}" (expected "source target")`);
|
|
114
|
+
}
|
|
115
|
+
return { source, target };
|
|
116
|
+
}
|
|
117
|
+
/** Serialize a directed pair to canonical `"source target"` form. */
|
|
118
|
+
export function serializePair(source, target) {
|
|
119
|
+
return `${source} ${target}`;
|
|
120
|
+
}
|
|
121
|
+
/** Parse a point value into `{ x, y }`. Throws on malformed input. */
|
|
122
|
+
export function parsePoint(value) {
|
|
123
|
+
const [xStr, yStr, ...rest] = value.trim().split(/\s+/);
|
|
124
|
+
const x = Number(xStr);
|
|
125
|
+
const y = Number(yStr);
|
|
126
|
+
if (!Number.isFinite(x) || !Number.isFinite(y) || rest.length > 0) {
|
|
127
|
+
throw new Error(`malformed point value: "${value}" (expected "x y" numeric coords)`);
|
|
128
|
+
}
|
|
129
|
+
return { x, y };
|
|
130
|
+
}
|
|
131
|
+
/** Serialize a point to canonical `"x y"` form. */
|
|
132
|
+
export function serializePoint(x, y) {
|
|
133
|
+
return `${x} ${y}`;
|
|
134
|
+
}
|
|
135
|
+
// ─── Lit converter factory ───────────────────────────────────────────────────
|
|
136
|
+
/**
|
|
137
|
+
* Ready-to-use Lit `converter` for `response` / `correct-response` attributes.
|
|
138
|
+
* Assign to the `converter` option:
|
|
139
|
+
*
|
|
140
|
+
* @property({ attribute: 'response', converter: responseAttributeConverter({ emptyAs: [] }) })
|
|
141
|
+
*
|
|
142
|
+
* `emptyAs` picks the sentinel returned when the attribute is absent / blank.
|
|
143
|
+
* Three sentinels exist across the codebase — match the one your call site
|
|
144
|
+
* already uses. Future work may normalize to a single sentinel.
|
|
145
|
+
*
|
|
146
|
+
* - '' (empty string) — ChoicesMixin convention
|
|
147
|
+
* - [] — DragDropSlottedMixin / graphic-associate convention
|
|
148
|
+
* - null — base interaction / select-point convention
|
|
149
|
+
*
|
|
150
|
+
* On the serialize side, the `emptyAs` value roundtrips back to `null` so a
|
|
151
|
+
* `reflect: true` property drops the attribute rather than emitting `""`.
|
|
152
|
+
*/
|
|
153
|
+
export function responseAttributeConverter(opts) {
|
|
154
|
+
const { emptyAs } = opts;
|
|
155
|
+
const isEmptySentinel = (value) => value === emptyAs || value == null || value === '' || (Array.isArray(value) && value.length === 0);
|
|
156
|
+
return {
|
|
157
|
+
fromAttribute: (value) => {
|
|
158
|
+
const parsed = parseResponseAttribute(value);
|
|
159
|
+
return parsed === null ? emptyAs : parsed;
|
|
160
|
+
},
|
|
161
|
+
toAttribute: (value) => {
|
|
162
|
+
if (isEmptySentinel(value))
|
|
163
|
+
return null;
|
|
164
|
+
return serializeResponseAttribute(value);
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
//# sourceMappingURL=response.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"response.js","sourceRoot":"","sources":["../../src/lib/response.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAkBH,gFAAgF;AAEhF;;;;;;;GAOG;AACH,MAAM,UAAU,sBAAsB,CAAC,GAA8B;IACnE,IAAI,GAAG,IAAI,IAAI;QAAE,OAAO,IAAI,CAAC;IAC7B,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACvB,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;QAC3B,OAAO,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;IAC7C,CAAC;IACD,MAAM,MAAM,GAAG,GAAG;SACf,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;SAC1B,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACrC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACrC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC;IAC1C,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,0BAA0B,CAAC,KAAoB;IAC7D,IAAI,KAAK,IAAI,IAAI;QAAE,OAAO,IAAI,CAAC;IAC/B,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,MAAM,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QAClF,OAAO,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACrD,CAAC;IACD,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAC7B,OAAO,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;AAC7C,CAAC;AAED,gFAAgF;AAEhF;;;;GAIG;AACH,MAAM,SAAS,CAAC,CAAC,kBAAkB,CAAC,GAAkB;IACpD,IAAI,GAAG,IAAI,IAAI;QAAE,OAAO;IACxB,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QACvB,KAAK,MAAM,CAAC,IAAI,GAAG,EAAE,CAAC;YACpB,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;YACnB,IAAI,CAAC;gBAAE,MAAM,CAAC,CAAC;QACjB,CAAC;QACD,OAAO;IACT,CAAC;IACD,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACvB,MAAM,CAAC,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;QACrB,IAAI,CAAC;YAAE,MAAM,CAAC,CAAC;QACf,OAAO;IACT,CAAC;IACD,KAAK,MAAM,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QAC/B,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;QACnB,IAAI,CAAC;YAAE,MAAM,CAAC,CAAC;IACjB,CAAC;AACH,CAAC;AAED,wFAAwF;AACxF,MAAM,UAAU,SAAS,CAAC,KAAa;IACrC,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC5D,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1C,MAAM,IAAI,KAAK,CAAC,mCAAmC,KAAK,8BAA8B,CAAC,CAAC;IAC1F,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;AAC5B,CAAC;AAED,qEAAqE;AACrE,MAAM,UAAU,aAAa,CAAC,MAAc,EAAE,MAAc;IAC1D,OAAO,GAAG,MAAM,IAAI,MAAM,EAAE,CAAC;AAC/B,CAAC;AAED,sEAAsE;AACtE,MAAM,UAAU,UAAU,CAAC,KAAa;IACtC,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,IAAI,CAAC,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACxD,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;IACvB,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;IACvB,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAClE,MAAM,IAAI,KAAK,CAAC,2BAA2B,KAAK,mCAAmC,CAAC,CAAC;IACvF,CAAC;IACD,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC;AAClB,CAAC;AAED,mDAAmD;AACnD,MAAM,UAAU,cAAc,CAAC,CAAS,EAAE,CAAS;IACjD,OAAO,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;AACrB,CAAC;AAED,gFAAgF;AAEhF;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,0BAA0B,CAA2C,IAEpF;IAIC,MAAM,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IACzB,MAAM,eAAe,GAAG,CAAC,KAAc,EAAW,EAAE,CAClD,KAAK,KAAK,OAAO,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC;IAErG,OAAO;QACL,aAAa,EAAE,CAAC,KAAoB,EAAqB,EAAE;YACzD,MAAM,MAAM,GAAG,sBAAsB,CAAC,KAAK,CAAC,CAAC;YAC7C,OAAO,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC;QAC5C,CAAC;QACD,WAAW,EAAE,CAAC,KAAwB,EAAiB,EAAE;YACvD,IAAI,eAAe,CAAC,KAAK,CAAC;gBAAE,OAAO,IAAI,CAAC;YACxC,OAAO,0BAA0B,CAAC,KAAsB,CAAC,CAAC;QAC5D,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["/**\n * Canonical `response=\"...\"` / `correct-response=\"...\"` attribute codec.\n *\n * Authors put a single string on an interaction element. This module is the\n * single source of truth for how that string parses into the runtime's\n * internal value AND how an editor serializes the internal value back to the\n * attribute. Both `@qti-components/*` interactions and `@citolab/prose-qti`\n * (the editor) import from here — no per-codebase parsers, no drift.\n *\n * The two attributes share the same grammar; the codec is agnostic about\n * which of them is being parsed.\n *\n * ---\n *\n * Format reference (per interaction):\n *\n * | Interaction | base-type | cardinality | attribute value |\n * |----------------------|---------------|----------------------|---------------------------|\n * | choice | identifier | single / multiple | \"A\" / \"A,B,C\" |\n * | inline-choice | identifier | single | \"Y\" |\n * | hottext | identifier | single / multiple | \"h1\" / \"h1,h2\" |\n * | hotspot | identifier | single / multiple | \"spot1\" / \"spot1,spot2\" |\n * | order | identifier | ordered | \"a,b,c\" (order matters) |\n * | graphic-order | identifier | ordered | \"spot1,spot2,spot3\" |\n * | match | directedPair | multiple | \"src1 tgt1,src2 tgt2\" |\n * | associate | directedPair | multiple | \"A B,C D\" |\n * | gap-match | directedPair | multiple | \"gtext1 gap1,...\" |\n * | graphic-associate | directedPair | multiple | \"spot1 spot2,...\" |\n * | graphic-gap-match | directedPair | multiple | \"gimg1 spot1,...\" |\n * | select-point | point | single / multiple | \"100 150\" / \"100 150,…\" |\n * | slider | integer | single | \"50\" |\n * | text-entry | string | single | \"paris\" |\n *\n * Rules:\n * - Comma `,` separates VALUES (different identifiers / pairs / points).\n * - Space ` ` separates PARTS within one value (pair members or point coords).\n * - Each value is trimmed on parse.\n * - No escape mechanism. Identifiers that contain commas cannot round-trip.\n */\n\n// ─── parsed shape ────────────────────────────────────────────────────────────\n\n/** Canonical parsed value of a `response` / `correct-response` attribute. */\nexport type ResponseValue = string | string[] | null;\n\n// ─── per-shape branded types (advisory at compile time) ──────────────────────\n\n/** Identifier literal — opaque QTI identifier (e.g. `\"choice1\"`). */\nexport type Identifier = string;\n\n/** Directed pair literal — `\"sourceId targetId\"`. Used by match / associate / gap-match. */\nexport type DirectedPair = `${string} ${string}`;\n\n/** Point literal — `\"x y\"` integer coordinates. Used by select-point / graphic-*. */\nexport type Point = `${number} ${number}`;\n\n// ─── attribute-level parser / serializer ─────────────────────────────────────\n\n/**\n * Parse a raw `response` / `correct-response` attribute string into the\n * canonical value.\n *\n * - `null` / `undefined` / empty → `null`\n * - no comma → trimmed string\n * - one or more commas → trimmed array (single-element collapses to a string)\n */\nexport function parseResponseAttribute(raw: string | null | undefined): ResponseValue {\n if (raw == null) return null;\n if (!raw.includes(',')) {\n const trimmed = raw.trim();\n return trimmed.length > 0 ? trimmed : null;\n }\n const tokens = raw\n .split(',')\n .map(token => token.trim())\n .filter(token => token.length > 0);\n if (tokens.length === 0) return null;\n if (tokens.length === 1) return tokens[0];\n return tokens;\n}\n\n/**\n * Serialize a canonical value back to the attribute string. Returns `null` for\n * empty inputs so a Lit `@property({ reflect: true })` drops the attribute.\n */\nexport function serializeResponseAttribute(value: ResponseValue): string | null {\n if (value == null) return null;\n if (Array.isArray(value)) {\n const tokens = value.map(token => token.trim()).filter(token => token.length > 0);\n return tokens.length > 0 ? tokens.join(',') : null;\n }\n const trimmed = value.trim();\n return trimmed.length > 0 ? trimmed : null;\n}\n\n// ─── value-shape helpers (per interaction type) ──────────────────────────────\n\n/**\n * Iterate over the comma-separated entries in a response value, regardless of\n * whether the caller has the raw string, the parsed string, or a string[].\n * Yields trimmed non-empty entries.\n */\nexport function* iterResponseValues(raw: ResponseValue): Generator<string> {\n if (raw == null) return;\n if (Array.isArray(raw)) {\n for (const v of raw) {\n const t = v.trim();\n if (t) yield t;\n }\n return;\n }\n if (!raw.includes(',')) {\n const t = raw.trim();\n if (t) yield t;\n return;\n }\n for (const v of raw.split(',')) {\n const t = v.trim();\n if (t) yield t;\n }\n}\n\n/** Parse a directed-pair value into `{ source, target }`. Throws on malformed input. */\nexport function parsePair(value: string): { source: string; target: string } {\n const [source, target, ...rest] = value.trim().split(/\\s+/);\n if (!source || !target || rest.length > 0) {\n throw new Error(`malformed directed-pair value: \"${value}\" (expected \"source target\")`);\n }\n return { source, target };\n}\n\n/** Serialize a directed pair to canonical `\"source target\"` form. */\nexport function serializePair(source: string, target: string): DirectedPair {\n return `${source} ${target}`;\n}\n\n/** Parse a point value into `{ x, y }`. Throws on malformed input. */\nexport function parsePoint(value: string): { x: number; y: number } {\n const [xStr, yStr, ...rest] = value.trim().split(/\\s+/);\n const x = Number(xStr);\n const y = Number(yStr);\n if (!Number.isFinite(x) || !Number.isFinite(y) || rest.length > 0) {\n throw new Error(`malformed point value: \"${value}\" (expected \"x y\" numeric coords)`);\n }\n return { x, y };\n}\n\n/** Serialize a point to canonical `\"x y\"` form. */\nexport function serializePoint(x: number, y: number): Point {\n return `${x} ${y}`;\n}\n\n// ─── Lit converter factory ───────────────────────────────────────────────────\n\n/**\n * Ready-to-use Lit `converter` for `response` / `correct-response` attributes.\n * Assign to the `converter` option:\n *\n * @property({ attribute: 'response', converter: responseAttributeConverter({ emptyAs: [] }) })\n *\n * `emptyAs` picks the sentinel returned when the attribute is absent / blank.\n * Three sentinels exist across the codebase — match the one your call site\n * already uses. Future work may normalize to a single sentinel.\n *\n * - '' (empty string) — ChoicesMixin convention\n * - [] — DragDropSlottedMixin / graphic-associate convention\n * - null — base interaction / select-point convention\n *\n * On the serialize side, the `emptyAs` value roundtrips back to `null` so a\n * `reflect: true` property drops the attribute rather than emitting `\"\"`.\n */\nexport function responseAttributeConverter<E extends '' | null | readonly unknown[]>(opts: {\n emptyAs: E;\n}): {\n fromAttribute: (value: string | null) => ResponseValue | E;\n toAttribute: (value: ResponseValue | E) => string | null;\n} {\n const { emptyAs } = opts;\n const isEmptySentinel = (value: unknown): boolean =>\n value === emptyAs || value == null || value === '' || (Array.isArray(value) && value.length === 0);\n\n return {\n fromAttribute: (value: string | null): ResponseValue | E => {\n const parsed = parseResponseAttribute(value);\n return parsed === null ? emptyAs : parsed;\n },\n toAttribute: (value: ResponseValue | E): string | null => {\n if (isEmptySentinel(value)) return null;\n return serializeResponseAttribute(value as ResponseValue);\n }\n };\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/register.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"register.js","sourceRoot":"","sources":["../src/register.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE7C,KAAK,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,eAAe,EAAE,CAAC;IAC5C,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;QAC7B,cAAc,CAAC,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IACnC,CAAC;AACH,CAAC","sourcesContent":["import { qtiBaseElements } from './elements';\n\nfor (const { tag, ctor } of qtiBaseElements) {\n if (!customElements.get(tag)) {\n customElements.define(tag, ctor);\n }\n}\n"]}
|