@qti-components/base 1.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/LICENSE.md +674 -0
- package/dist/abstract/interaction.d.ts +87 -0
- package/dist/abstract/interaction.js +371 -0
- package/dist/abstract/interaction.js.map +1 -0
- package/dist/abstract/qti-condition-expression.d.ts +5 -0
- package/dist/abstract/qti-condition-expression.js +11 -0
- package/dist/abstract/qti-condition-expression.js.map +1 -0
- package/dist/abstract/qti-expression.d.ts +17 -0
- package/dist/abstract/qti-expression.js +141 -0
- package/dist/abstract/qti-expression.js.map +1 -0
- package/dist/abstract/qti-feedback.d.ts +11 -0
- package/dist/abstract/qti-feedback.js +58 -0
- package/dist/abstract/qti-feedback.js.map +1 -0
- package/dist/abstract/qti-variable-declaration.d.ts +6 -0
- package/dist/abstract/qti-variable-declaration.js +18 -0
- package/dist/abstract/qti-variable-declaration.js.map +1 -0
- package/dist/context/computed-item.context.d.ts +19 -0
- package/dist/context/computed-item.context.js +3 -0
- package/dist/context/computed-item.context.js.map +1 -0
- package/dist/context/computed.context.d.ts +29 -0
- package/dist/context/computed.context.js +3 -0
- package/dist/context/computed.context.js.map +1 -0
- package/dist/context/config.context.d.ts +12 -0
- package/dist/context/config.context.js +3 -0
- package/dist/context/config.context.js.map +1 -0
- package/dist/context/item.context.d.ts +12 -0
- package/dist/context/item.context.js +17 -0
- package/dist/context/item.context.js.map +1 -0
- package/dist/context/qti-assessment-item.context.d.ts +4 -0
- package/dist/context/qti-assessment-item.context.js +3 -0
- package/dist/context/qti-assessment-item.context.js.map +1 -0
- package/dist/context/qti.context.d.ts +12 -0
- package/dist/context/qti.context.js +3 -0
- package/dist/context/qti.context.js.map +1 -0
- package/dist/context/session.context.d.ts +13 -0
- package/dist/context/session.context.js +4 -0
- package/dist/context/session.context.js.map +1 -0
- package/dist/context/test.context.d.ts +12 -0
- package/dist/context/test.context.js +4 -0
- package/dist/context/test.context.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/expression-result.d.ts +18 -0
- package/dist/lib/expression-result.js +2 -0
- package/dist/lib/expression-result.js.map +1 -0
- package/dist/lib/interaction.interface.d.ts +20 -0
- package/dist/lib/interaction.interface.js +2 -0
- package/dist/lib/interaction.interface.js.map +1 -0
- package/dist/lib/qti-rule-base.d.ts +13 -0
- package/dist/lib/qti-rule-base.js +7 -0
- package/dist/lib/qti-rule-base.js.map +1 -0
- package/dist/lib/variables.d.ts +50 -0
- package/dist/lib/variables.js +2 -0
- package/dist/lib/variables.js.map +1 -0
- package/dist/utils/scoring-helper.d.ts +25 -0
- package/dist/utils/scoring-helper.js +129 -0
- package/dist/utils/scoring-helper.js.map +1 -0
- package/dist/utils/utils.d.ts +4 -0
- package/dist/utils/utils.js +28 -0
- package/dist/utils/utils.js.map +1 -0
- package/package.json +38 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"qti-rule-base.js","sourceRoot":"","sources":["../../src/lib/qti-rule-base.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,KAAK,CAAC;AASjC;;GAEG;AACH,MAAM,OAAgB,cAAe,SAAQ,UAAU;CAEtD","sourcesContent":["import { LitElement } from 'lit';\n\n/**\n * Interface for any object that can process QTI rules\n */\nexport interface QtiRuleBase {\n process(): any;\n}\n\n/**\n * Abstract base class for QTI rule DOM elements\n */\nexport abstract class QtiRuleElement extends LitElement implements QtiRuleBase {\n abstract process(): any;\n}\n"]}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { BaseType, Cardinality } from './expression-result';
|
|
2
|
+
export type QtiAreaMapping = {
|
|
3
|
+
defaultValue: number;
|
|
4
|
+
lowerBound?: number;
|
|
5
|
+
upperBound?: number;
|
|
6
|
+
areaMapEntries: QtiAreaMapEntry[];
|
|
7
|
+
};
|
|
8
|
+
export type QtiAreaMapEntry = {
|
|
9
|
+
shape: areaShape;
|
|
10
|
+
coords: string;
|
|
11
|
+
mappedValue: number;
|
|
12
|
+
defaultValue: number;
|
|
13
|
+
};
|
|
14
|
+
export type QtiMapping = {
|
|
15
|
+
defaultValue: number;
|
|
16
|
+
lowerBound?: number;
|
|
17
|
+
upperBound?: number;
|
|
18
|
+
mapEntries: QtiMapEntry[];
|
|
19
|
+
};
|
|
20
|
+
export type QtiMapEntry = {
|
|
21
|
+
mapKey: string;
|
|
22
|
+
mappedValue: number;
|
|
23
|
+
caseSensitive: boolean;
|
|
24
|
+
};
|
|
25
|
+
export type areaShape = 'default' | 'circle' | 'rect' | 'ellipse' | 'poly';
|
|
26
|
+
export interface VariableValue<T> {
|
|
27
|
+
identifier: string;
|
|
28
|
+
value: Readonly<T>;
|
|
29
|
+
defaultValue?: T | null;
|
|
30
|
+
type: 'outcome' | 'response' | 'context' | 'template';
|
|
31
|
+
}
|
|
32
|
+
export interface VariableDeclaration<T> extends VariableValue<T> {
|
|
33
|
+
cardinality?: Cardinality;
|
|
34
|
+
baseType?: BaseType;
|
|
35
|
+
}
|
|
36
|
+
export interface OutcomeVariable extends VariableDeclaration<string | string[] | null> {
|
|
37
|
+
interpolationTable?: Map<number, number>;
|
|
38
|
+
externalScored?: 'human' | 'externalMachine' | null;
|
|
39
|
+
}
|
|
40
|
+
export interface ResponseVariable extends VariableDeclaration<string | string[] | null> {
|
|
41
|
+
candidateResponse?: string | string[] | null;
|
|
42
|
+
mapping?: QtiMapping;
|
|
43
|
+
areaMapping?: QtiAreaMapping;
|
|
44
|
+
correctResponse?: string | string[] | null;
|
|
45
|
+
}
|
|
46
|
+
export interface TemplateVariable extends VariableDeclaration<string | string[] | number | boolean | null> {
|
|
47
|
+
type: 'template';
|
|
48
|
+
mathVariable?: boolean;
|
|
49
|
+
paramVariable?: boolean;
|
|
50
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"variables.js","sourceRoot":"","sources":["../../src/lib/variables.ts"],"names":[],"mappings":"","sourcesContent":["import type { BaseType, Cardinality } from './expression-result';\n\nexport type QtiAreaMapping = {\n defaultValue: number;\n lowerBound?: number;\n upperBound?: number;\n areaMapEntries: QtiAreaMapEntry[];\n};\n\nexport type QtiAreaMapEntry = {\n shape: areaShape;\n coords: string;\n mappedValue: number;\n defaultValue: number;\n};\n\nexport type QtiMapping = {\n defaultValue: number;\n lowerBound?: number;\n upperBound?: number;\n mapEntries: QtiMapEntry[];\n};\n\nexport type QtiMapEntry = { mapKey: string; mappedValue: number; caseSensitive: boolean };\n\nexport type areaShape = 'default' | 'circle' | 'rect' | 'ellipse' | 'poly';\nexport interface VariableValue<T> {\n identifier: string;\n value: Readonly<T>;\n defaultValue?: T | null;\n type: 'outcome' | 'response' | 'context' | 'template';\n}\n\nexport interface VariableDeclaration<T> extends VariableValue<T> {\n cardinality?: Cardinality;\n baseType?: BaseType;\n}\n\nexport interface OutcomeVariable extends VariableDeclaration<string | string[] | null> {\n // specific to outcome variables\n interpolationTable?: Map<number, number>;\n externalScored?: 'human' | 'externalMachine' | null;\n}\n\nexport interface ResponseVariable extends VariableDeclaration<string | string[] | null> {\n // specific to response variables\n candidateResponse?: string | string[] | null;\n mapping?: QtiMapping;\n areaMapping?: QtiAreaMapping; // Optional property for area mappings\n correctResponse?: string | string[] | null;\n}\n\nexport interface TemplateVariable extends VariableDeclaration<string | string[] | number | boolean | null> {\n type: 'template';\n mathVariable?: boolean;\n paramVariable?: boolean;\n}\n"]}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { BaseType } from '../lib/expression-result';
|
|
2
|
+
export declare class ScoringHelper {
|
|
3
|
+
/**
|
|
4
|
+
* Checks if a given point is within a specified area.
|
|
5
|
+
* @param point The point to test, represented as a string "x y" (e.g., "102 113").
|
|
6
|
+
* @param areaKey The area definition, including shape and coordinates (e.g., "circle,102,113,16").
|
|
7
|
+
* @param baseType The base type of the response, must be "point" for this method to proceed.
|
|
8
|
+
* @returns True if the point is within the area; false otherwise.
|
|
9
|
+
*/
|
|
10
|
+
static isPointInArea(point: string, areaKey: string, baseType: string): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Checks if a point is inside a polygon using the ray-casting algorithm.
|
|
13
|
+
* @param point The point to test.
|
|
14
|
+
* @param vertices The vertices of the polygon in order (array of {x, y} objects).
|
|
15
|
+
* @returns True if the point is inside the polygon; false otherwise.
|
|
16
|
+
*/
|
|
17
|
+
static isPointInPolygon(point: {
|
|
18
|
+
x: number;
|
|
19
|
+
y: number;
|
|
20
|
+
}, vertices: {
|
|
21
|
+
x: number;
|
|
22
|
+
y: number;
|
|
23
|
+
}[]): boolean;
|
|
24
|
+
static compareSingleValues(value1: Readonly<string>, value2: Readonly<string>, baseType: BaseType): boolean;
|
|
25
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
export class ScoringHelper {
|
|
2
|
+
/**
|
|
3
|
+
* Checks if a given point is within a specified area.
|
|
4
|
+
* @param point The point to test, represented as a string "x y" (e.g., "102 113").
|
|
5
|
+
* @param areaKey The area definition, including shape and coordinates (e.g., "circle,102,113,16").
|
|
6
|
+
* @param baseType The base type of the response, must be "point" for this method to proceed.
|
|
7
|
+
* @returns True if the point is within the area; false otherwise.
|
|
8
|
+
*/
|
|
9
|
+
static isPointInArea(point, areaKey, baseType) {
|
|
10
|
+
if (baseType !== 'point') {
|
|
11
|
+
console.warn(`Base type ${baseType} is not supported for point area mapping.`);
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
// Parse the point as x and y coordinates
|
|
15
|
+
const [px, py] = point.split(' ').map(Number);
|
|
16
|
+
// Parse the area definition
|
|
17
|
+
const [shape, ...coords] = areaKey.split(',');
|
|
18
|
+
const coordinates = coords.map(Number);
|
|
19
|
+
switch (shape.toLowerCase()) {
|
|
20
|
+
case 'circle':
|
|
21
|
+
case 'default': {
|
|
22
|
+
const [cx, cy, radius] = coordinates;
|
|
23
|
+
if (coordinates.length !== 3) {
|
|
24
|
+
console.warn(`Invalid circle definition: ${areaKey}`);
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
const distance = Math.sqrt((px - cx) ** 2 + (py - cy) ** 2);
|
|
28
|
+
return distance <= radius;
|
|
29
|
+
}
|
|
30
|
+
case 'rect': {
|
|
31
|
+
const [x1, y1, x2, y2] = coordinates;
|
|
32
|
+
if (coordinates.length !== 4) {
|
|
33
|
+
console.warn(`Invalid rectangle definition: ${areaKey}`);
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
return px >= x1 && px <= x2 && py >= y1 && py <= y2;
|
|
37
|
+
}
|
|
38
|
+
case 'ellipse': {
|
|
39
|
+
const [cx, cy, rx, ry] = coordinates;
|
|
40
|
+
if (coordinates.length !== 4) {
|
|
41
|
+
console.warn(`Invalid ellipse definition: ${areaKey}`);
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
// Ellipse equation: ((px - cx)² / rx²) + ((py - cy)² / ry²) <= 1
|
|
45
|
+
const normalizedX = (px - cx) ** 2 / rx ** 2;
|
|
46
|
+
const normalizedY = (py - cy) ** 2 / ry ** 2;
|
|
47
|
+
return normalizedX + normalizedY <= 1;
|
|
48
|
+
}
|
|
49
|
+
case 'poly': {
|
|
50
|
+
if (coordinates.length < 6 || coordinates.length % 2 !== 0) {
|
|
51
|
+
console.warn(`Invalid polygon definition: ${areaKey}`);
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
const vertices = [];
|
|
55
|
+
for (let i = 0; i < coordinates.length; i += 2) {
|
|
56
|
+
vertices.push({ x: coordinates[i], y: coordinates[i + 1] });
|
|
57
|
+
}
|
|
58
|
+
return this.isPointInPolygon({ x: px, y: py }, vertices);
|
|
59
|
+
}
|
|
60
|
+
default:
|
|
61
|
+
console.warn(`Unsupported shape type: ${shape}`);
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Checks if a point is inside a polygon using the ray-casting algorithm.
|
|
67
|
+
* @param point The point to test.
|
|
68
|
+
* @param vertices The vertices of the polygon in order (array of {x, y} objects).
|
|
69
|
+
* @returns True if the point is inside the polygon; false otherwise.
|
|
70
|
+
*/
|
|
71
|
+
static isPointInPolygon(point, vertices) {
|
|
72
|
+
let inside = false;
|
|
73
|
+
for (let i = 0, j = vertices.length - 1; i < vertices.length; j = i++) {
|
|
74
|
+
const xi = vertices[i].x, yi = vertices[i].y;
|
|
75
|
+
const xj = vertices[j].x, yj = vertices[j].y;
|
|
76
|
+
const intersect = yi > point.y !== yj > point.y && point.x < ((xj - xi) * (point.y - yi)) / (yj - yi) + xi;
|
|
77
|
+
if (intersect)
|
|
78
|
+
inside = !inside;
|
|
79
|
+
}
|
|
80
|
+
return inside;
|
|
81
|
+
}
|
|
82
|
+
static compareSingleValues(value1, value2, baseType) {
|
|
83
|
+
switch (baseType) {
|
|
84
|
+
case 'identifier':
|
|
85
|
+
case 'string':
|
|
86
|
+
return value1 === value2;
|
|
87
|
+
case 'integer': {
|
|
88
|
+
const int1 = parseInt(value1, 10);
|
|
89
|
+
const int2 = parseInt(value2, 10);
|
|
90
|
+
if (!isNaN(int1) && !isNaN(int2)) {
|
|
91
|
+
return int1 === int2;
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
console.error(`Cannot convert ${value1} and/or ${value2} to int.`);
|
|
95
|
+
}
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
case 'float': {
|
|
99
|
+
const float1 = parseFloat(value1);
|
|
100
|
+
const float2 = parseFloat(value2);
|
|
101
|
+
if (!isNaN(float1) && !isNaN(float2)) {
|
|
102
|
+
return float1 === float2;
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
console.error(`couldn't convert ${value1} and/or ${value2} to float.`);
|
|
106
|
+
}
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
case 'pair':
|
|
110
|
+
case 'directedPair': {
|
|
111
|
+
const pair1 = value1.split(' ').sort();
|
|
112
|
+
const pair2 = value2.split(' ').sort();
|
|
113
|
+
if (pair1.length === 2 && pair2.length === 2) {
|
|
114
|
+
if (baseType === 'pair') {
|
|
115
|
+
pair1.sort();
|
|
116
|
+
pair2.sort();
|
|
117
|
+
}
|
|
118
|
+
return pair1.join(' ') === pair2.join(' ');
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
console.error(`compared two pair but one of the values does not have 2 values: 1: ${value1} 2: ${value2}`);
|
|
122
|
+
}
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=scoring-helper.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scoring-helper.js","sourceRoot":"","sources":["../../src/utils/scoring-helper.ts"],"names":[],"mappings":"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","sourcesContent":["import type { BaseType } from '../lib/expression-result';\n\nexport class ScoringHelper {\n /**\n * Checks if a given point is within a specified area.\n * @param point The point to test, represented as a string \"x y\" (e.g., \"102 113\").\n * @param areaKey The area definition, including shape and coordinates (e.g., \"circle,102,113,16\").\n * @param baseType The base type of the response, must be \"point\" for this method to proceed.\n * @returns True if the point is within the area; false otherwise.\n */\n public static isPointInArea(point: string, areaKey: string, baseType: string): boolean {\n if (baseType !== 'point') {\n console.warn(`Base type ${baseType} is not supported for point area mapping.`);\n return false;\n }\n\n // Parse the point as x and y coordinates\n const [px, py] = point.split(' ').map(Number);\n\n // Parse the area definition\n const [shape, ...coords] = areaKey.split(',');\n const coordinates = coords.map(Number);\n\n switch (shape.toLowerCase()) {\n case 'circle':\n case 'default': {\n const [cx, cy, radius] = coordinates;\n if (coordinates.length !== 3) {\n console.warn(`Invalid circle definition: ${areaKey}`);\n return false;\n }\n const distance = Math.sqrt((px - cx) ** 2 + (py - cy) ** 2);\n return distance <= radius;\n }\n\n case 'rect': {\n const [x1, y1, x2, y2] = coordinates;\n if (coordinates.length !== 4) {\n console.warn(`Invalid rectangle definition: ${areaKey}`);\n return false;\n }\n return px >= x1 && px <= x2 && py >= y1 && py <= y2;\n }\n\n case 'ellipse': {\n const [cx, cy, rx, ry] = coordinates;\n if (coordinates.length !== 4) {\n console.warn(`Invalid ellipse definition: ${areaKey}`);\n return false;\n }\n // Ellipse equation: ((px - cx)² / rx²) + ((py - cy)² / ry²) <= 1\n const normalizedX = (px - cx) ** 2 / rx ** 2;\n const normalizedY = (py - cy) ** 2 / ry ** 2;\n return normalizedX + normalizedY <= 1;\n }\n\n case 'poly': {\n if (coordinates.length < 6 || coordinates.length % 2 !== 0) {\n console.warn(`Invalid polygon definition: ${areaKey}`);\n return false;\n }\n const vertices = [];\n for (let i = 0; i < coordinates.length; i += 2) {\n vertices.push({ x: coordinates[i], y: coordinates[i + 1] });\n }\n return this.isPointInPolygon({ x: px, y: py }, vertices);\n }\n\n default:\n console.warn(`Unsupported shape type: ${shape}`);\n return false;\n }\n }\n\n /**\n * Checks if a point is inside a polygon using the ray-casting algorithm.\n * @param point The point to test.\n * @param vertices The vertices of the polygon in order (array of {x, y} objects).\n * @returns True if the point is inside the polygon; false otherwise.\n */\n static isPointInPolygon(point: { x: number; y: number }, vertices: { x: number; y: number }[]): boolean {\n let inside = false;\n for (let i = 0, j = vertices.length - 1; i < vertices.length; j = i++) {\n const xi = vertices[i].x,\n yi = vertices[i].y;\n const xj = vertices[j].x,\n yj = vertices[j].y;\n\n const intersect = yi > point.y !== yj > point.y && point.x < ((xj - xi) * (point.y - yi)) / (yj - yi) + xi;\n\n if (intersect) inside = !inside;\n }\n return inside;\n }\n\n public static compareSingleValues(value1: Readonly<string>, value2: Readonly<string>, baseType: BaseType): boolean {\n switch (baseType) {\n case 'identifier':\n case 'string':\n return value1 === value2;\n case 'integer': {\n const int1 = parseInt(value1, 10);\n const int2 = parseInt(value2, 10);\n if (!isNaN(int1) && !isNaN(int2)) {\n return int1 === int2;\n } else {\n console.error(`Cannot convert ${value1} and/or ${value2} to int.`);\n }\n break;\n }\n case 'float': {\n const float1 = parseFloat(value1);\n const float2 = parseFloat(value2);\n if (!isNaN(float1) && !isNaN(float2)) {\n return float1 === float2;\n } else {\n console.error(`couldn't convert ${value1} and/or ${value2} to float.`);\n }\n break;\n }\n case 'pair':\n case 'directedPair': {\n const pair1 = value1.split(' ').sort();\n const pair2 = value2.split(' ').sort();\n if (pair1.length === 2 && pair2.length === 2) {\n if (baseType === 'pair') {\n pair1.sort();\n pair2.sort();\n }\n return pair1.join(' ') === pair2.join(' ');\n } else {\n console.error(`compared two pair but one of the values does not have 2 values: 1: ${value1} 2: ${value2}`);\n }\n break;\n }\n }\n\n return false;\n }\n}\n"]}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare const decimalSeparator: () => string;
|
|
2
|
+
export declare const convertNumberToUniversalFormat: (number: number | string) => string;
|
|
3
|
+
export declare function IsNullOrUndefined(value: unknown): boolean;
|
|
4
|
+
export declare function removeDoubleSlashes(str: string): string;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export const decimalSeparator = () => {
|
|
2
|
+
return new Intl.NumberFormat().format(0.1).replace(/\d/g, '');
|
|
3
|
+
};
|
|
4
|
+
export const convertNumberToUniversalFormat = (number) => {
|
|
5
|
+
// check if type is string
|
|
6
|
+
if (typeof number === 'string') {
|
|
7
|
+
return number;
|
|
8
|
+
}
|
|
9
|
+
const dSep = decimalSeparator();
|
|
10
|
+
if (dSep === '.') {
|
|
11
|
+
return number.toLocaleString();
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
return number.toString().replace('.', '').replace(dSep, '.');
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
export function IsNullOrUndefined(value) {
|
|
18
|
+
return value === null || value === undefined;
|
|
19
|
+
}
|
|
20
|
+
export function removeDoubleSlashes(str) {
|
|
21
|
+
const singleForwardSlashes = str
|
|
22
|
+
.replace(/([^:]\/)\/+/g, '$1')
|
|
23
|
+
.replace(/\/\//g, '/')
|
|
24
|
+
.replace('http:/', 'http://')
|
|
25
|
+
.replace('https:/', 'https://');
|
|
26
|
+
return singleForwardSlashes;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/utils/utils.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,gBAAgB,GAAG,GAAG,EAAE;IACnC,OAAO,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;AAChE,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,8BAA8B,GAAG,CAAC,MAAuB,EAAE,EAAE;IACxE,0BAA0B;IAC1B,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC/B,OAAO,MAAM,CAAC;IAChB,CAAC;IACD,MAAM,IAAI,GAAG,gBAAgB,EAAE,CAAC;IAChC,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;QACjB,OAAO,MAAM,CAAC,cAAc,EAAE,CAAC;IACjC,CAAC;SAAM,CAAC;QACN,OAAO,MAAM,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAC/D,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,UAAU,iBAAiB,CAAC,KAAc;IAC9C,OAAO,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,CAAC;AAC/C,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,GAAW;IAC7C,MAAM,oBAAoB,GAAG,GAAG;SAC7B,OAAO,CAAC,cAAc,EAAE,IAAI,CAAC;SAC7B,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC;SACrB,OAAO,CAAC,QAAQ,EAAE,SAAS,CAAC;SAC5B,OAAO,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;IAClC,OAAO,oBAAoB,CAAC;AAC9B,CAAC","sourcesContent":["export const decimalSeparator = () => {\n return new Intl.NumberFormat().format(0.1).replace(/\\d/g, '');\n};\n\nexport const convertNumberToUniversalFormat = (number: number | string) => {\n // check if type is string\n if (typeof number === 'string') {\n return number;\n }\n const dSep = decimalSeparator();\n if (dSep === '.') {\n return number.toLocaleString();\n } else {\n return number.toString().replace('.', '').replace(dSep, '.');\n }\n};\n\nexport function IsNullOrUndefined(value: unknown) {\n return value === null || value === undefined;\n}\n\nexport function removeDoubleSlashes(str: string) {\n const singleForwardSlashes = str\n .replace(/([^:]\\/)\\/+/g, '$1')\n .replace(/\\/\\//g, '/')\n .replace('http:/', 'http://')\n .replace('https:/', 'https://');\n return singleForwardSlashes;\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@qti-components/base",
|
|
3
|
+
"description": "QTI shared utilities",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"author": "",
|
|
6
|
+
"dependencies": {
|
|
7
|
+
"@qti-components/utilities": "^1.0.0"
|
|
8
|
+
},
|
|
9
|
+
"devDependencies": {
|
|
10
|
+
"@lit/context": "^1.1.6",
|
|
11
|
+
"lit": "^3.3.1"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"qti",
|
|
15
|
+
"shared"
|
|
16
|
+
],
|
|
17
|
+
"license": "GPL-3.0-only",
|
|
18
|
+
"main": "dist/index.js",
|
|
19
|
+
"peerDependencies": {
|
|
20
|
+
"@lit/context": "^1.1.6",
|
|
21
|
+
"lit": "^3.3.1"
|
|
22
|
+
},
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public"
|
|
25
|
+
},
|
|
26
|
+
"type": "module",
|
|
27
|
+
"types": "dist/index.d.ts",
|
|
28
|
+
"files": [
|
|
29
|
+
"dist"
|
|
30
|
+
],
|
|
31
|
+
"scripts": {
|
|
32
|
+
"attw": "attw --profile esm-only --pack",
|
|
33
|
+
"build": "tsc --paths null",
|
|
34
|
+
"clean": "rm -rf dist",
|
|
35
|
+
"yalc": "yalc publish",
|
|
36
|
+
"yalc:push": "yalc push"
|
|
37
|
+
}
|
|
38
|
+
}
|