@mathscan/math-exercise-engine 1.2.6 → 1.2.7
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/index.d.ts +686 -2
- package/dist/index.js +100 -16
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +100 -16
- package/dist/index.mjs.map +1 -1
- package/dist/style.css +19 -0
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1886,6 +1886,7 @@ const InlineInputWidgetSchema = z.discriminatedUnion("inputType", [
|
|
|
1886
1886
|
inputType: z.literal(INPUT_TYPES.STRING),
|
|
1887
1887
|
length: z.number().int().positive(),
|
|
1888
1888
|
placeholder: z.string().max(32).optional(),
|
|
1889
|
+
disabled: z.boolean().optional(),
|
|
1889
1890
|
answer: z.union([z.string(), z.number()]).optional()
|
|
1890
1891
|
}),
|
|
1891
1892
|
z.object({
|
|
@@ -1894,6 +1895,7 @@ const InlineInputWidgetSchema = z.discriminatedUnion("inputType", [
|
|
|
1894
1895
|
inputType: z.literal(INPUT_TYPES.NUMERIC),
|
|
1895
1896
|
digits: z.number().int().positive(),
|
|
1896
1897
|
placeholder: z.string().max(32).optional(),
|
|
1898
|
+
disabled: z.boolean().optional(),
|
|
1897
1899
|
answer: z.union([z.string(), z.number()]).optional()
|
|
1898
1900
|
})
|
|
1899
1901
|
]);
|
|
@@ -2049,7 +2051,7 @@ function createBoldText(value, highlight) {
|
|
|
2049
2051
|
highlight
|
|
2050
2052
|
});
|
|
2051
2053
|
}
|
|
2052
|
-
function createStringInput(name, length, answer, placeholder) {
|
|
2054
|
+
function createStringInput(name, length, answer, placeholder, disabled) {
|
|
2053
2055
|
return parseOrThrow(
|
|
2054
2056
|
InlineInputWidgetSchema,
|
|
2055
2057
|
{
|
|
@@ -2058,12 +2060,14 @@ function createStringInput(name, length, answer, placeholder) {
|
|
|
2058
2060
|
inputType: INPUT_TYPES.STRING,
|
|
2059
2061
|
length,
|
|
2060
2062
|
placeholder,
|
|
2061
|
-
|
|
2063
|
+
disabled,
|
|
2064
|
+
// A disabled input is never graded, so its answer is dropped.
|
|
2065
|
+
answer: disabled ? void 0 : answer
|
|
2062
2066
|
},
|
|
2063
2067
|
"STRING_INPUT"
|
|
2064
2068
|
);
|
|
2065
2069
|
}
|
|
2066
|
-
function createNumericInput(name, digits, answer, placeholder) {
|
|
2070
|
+
function createNumericInput(name, digits, answer, placeholder, disabled) {
|
|
2067
2071
|
return parseOrThrow(
|
|
2068
2072
|
InlineInputWidgetSchema,
|
|
2069
2073
|
{
|
|
@@ -2072,7 +2076,9 @@ function createNumericInput(name, digits, answer, placeholder) {
|
|
|
2072
2076
|
inputType: INPUT_TYPES.NUMERIC,
|
|
2073
2077
|
digits,
|
|
2074
2078
|
placeholder,
|
|
2075
|
-
|
|
2079
|
+
disabled,
|
|
2080
|
+
// A disabled input is never graded, so its answer is dropped.
|
|
2081
|
+
answer: disabled ? void 0 : answer
|
|
2076
2082
|
},
|
|
2077
2083
|
"NUMERIC_INPUT"
|
|
2078
2084
|
);
|
|
@@ -3685,6 +3691,9 @@ class NaturalDSLVisitor extends BaseCstVisitor {
|
|
|
3685
3691
|
if (!name) {
|
|
3686
3692
|
throw new Error("INPUT inside VERTICAL_OPERATION requires parameter: name");
|
|
3687
3693
|
}
|
|
3694
|
+
if (params.disabled !== void 0) {
|
|
3695
|
+
throw new Error("INPUT inside VERTICAL_OPERATION does not support parameter: disabled");
|
|
3696
|
+
}
|
|
3688
3697
|
const digits = params.digits !== void 0 ? Number(params.digits) : 1;
|
|
3689
3698
|
const answer = params.answer;
|
|
3690
3699
|
return createVopInputCell(name, digits, answer);
|
|
@@ -3930,12 +3939,20 @@ class NaturalDSLVisitor extends BaseCstVisitor {
|
|
|
3930
3939
|
const name = params.name;
|
|
3931
3940
|
const answer = params.answer;
|
|
3932
3941
|
const placeholder = params.placeholder;
|
|
3942
|
+
let disabled;
|
|
3943
|
+
if (params.disabled === true || params.disabled === "true") disabled = true;
|
|
3944
|
+
else if (params.disabled === false || params.disabled === "false") disabled = false;
|
|
3945
|
+
else if (params.disabled !== void 0) {
|
|
3946
|
+
throw new Error(
|
|
3947
|
+
`${ctx.stringInput ? "STRING_INPUT" : "NUMERIC_INPUT"} "${name}": disabled must be true or false, got ${JSON.stringify(params.disabled)}`
|
|
3948
|
+
);
|
|
3949
|
+
}
|
|
3933
3950
|
if (ctx.stringInput) {
|
|
3934
3951
|
const length = params.length;
|
|
3935
|
-
return createStringInput(name, length, answer, placeholder);
|
|
3952
|
+
return createStringInput(name, length, answer, placeholder, disabled);
|
|
3936
3953
|
}
|
|
3937
3954
|
const digits = params.digits;
|
|
3938
|
-
return createNumericInput(name, digits, answer, placeholder);
|
|
3955
|
+
return createNumericInput(name, digits, answer, placeholder, disabled);
|
|
3939
3956
|
}
|
|
3940
3957
|
inlineDragOption(ctx) {
|
|
3941
3958
|
const valueToken = ctx.StringLiteral[0];
|
|
@@ -4531,13 +4548,17 @@ const StringInputJSONSchema = z.object({
|
|
|
4531
4548
|
type: z.literal(JSON_TYPES.INLINE_INPUT),
|
|
4532
4549
|
inputType: z.literal(INPUT_TYPES.STRING),
|
|
4533
4550
|
length: z.number().int().positive(),
|
|
4534
|
-
placeholder: z.string().max(32).optional()
|
|
4551
|
+
placeholder: z.string().max(32).optional(),
|
|
4552
|
+
/** Gray, non-interactive, never graded (any answer for it is ignored). */
|
|
4553
|
+
disabled: z.boolean().optional()
|
|
4535
4554
|
});
|
|
4536
4555
|
const NumericInputJSONSchema = z.object({
|
|
4537
4556
|
type: z.literal(JSON_TYPES.INLINE_INPUT),
|
|
4538
4557
|
inputType: z.literal(INPUT_TYPES.NUMERIC),
|
|
4539
4558
|
digits: z.number().int().positive(),
|
|
4540
|
-
placeholder: z.string().max(32).optional()
|
|
4559
|
+
placeholder: z.string().max(32).optional(),
|
|
4560
|
+
/** Gray, non-interactive, never graded (any answer for it is ignored). */
|
|
4561
|
+
disabled: z.boolean().optional()
|
|
4541
4562
|
});
|
|
4542
4563
|
const InlineInputJSONSchema = z.discriminatedUnion("inputType", [
|
|
4543
4564
|
StringInputJSONSchema,
|
|
@@ -4765,6 +4786,9 @@ function isChartItem(item) {
|
|
|
4765
4786
|
function isInlineInputJSON(widget) {
|
|
4766
4787
|
return widget.type === JSON_TYPES.INLINE_INPUT;
|
|
4767
4788
|
}
|
|
4789
|
+
function isDisabledInputJSON(widget) {
|
|
4790
|
+
return isInlineInputJSON(widget) && widget.disabled === true;
|
|
4791
|
+
}
|
|
4768
4792
|
function isDragOptionsJSON(widget) {
|
|
4769
4793
|
return widget.type === JSON_TYPES.DRAG_OPTIONS;
|
|
4770
4794
|
}
|
|
@@ -10411,14 +10435,16 @@ function toJSON(ast) {
|
|
|
10411
10435
|
type: JSON_TYPES.INLINE_INPUT,
|
|
10412
10436
|
inputType: INPUT_TYPES.STRING,
|
|
10413
10437
|
length: inline.length,
|
|
10414
|
-
placeholder: inline.placeholder
|
|
10438
|
+
placeholder: inline.placeholder,
|
|
10439
|
+
...inline.disabled && { disabled: true }
|
|
10415
10440
|
};
|
|
10416
10441
|
} else {
|
|
10417
10442
|
widgets[inline.name] = {
|
|
10418
10443
|
type: JSON_TYPES.INLINE_INPUT,
|
|
10419
10444
|
inputType: INPUT_TYPES.NUMERIC,
|
|
10420
10445
|
digits: inline.digits,
|
|
10421
|
-
placeholder: inline.placeholder
|
|
10446
|
+
placeholder: inline.placeholder,
|
|
10447
|
+
...inline.disabled && { disabled: true }
|
|
10422
10448
|
};
|
|
10423
10449
|
}
|
|
10424
10450
|
if (inline.answer !== void 0) {
|
|
@@ -10538,7 +10564,9 @@ function toJSON(ast) {
|
|
|
10538
10564
|
}
|
|
10539
10565
|
let validationSection = {};
|
|
10540
10566
|
if (ast.validation) {
|
|
10541
|
-
|
|
10567
|
+
const validation = transformValidation(ast.validation, answers, widgets);
|
|
10568
|
+
const onlyDisabledLeft = validation.mode === VALIDATION_MODES.SIMPLE && Object.keys(answers).length === 0 && Object.values(widgets).some(isDisabledInputJSON);
|
|
10569
|
+
if (!onlyDisabledLeft) validationSection = { validation };
|
|
10542
10570
|
} else if (Object.keys(answers).length > 0) {
|
|
10543
10571
|
validationSection = { validation: { mode: VALIDATION_MODES.SIMPLE, answers } };
|
|
10544
10572
|
}
|
|
@@ -10994,7 +11022,8 @@ function transformValidation(validation, answers, widgets) {
|
|
|
10994
11022
|
answers
|
|
10995
11023
|
};
|
|
10996
11024
|
} else if (validation.type === AST_TYPES.EXPRESSION_VALIDATION) {
|
|
10997
|
-
|
|
11025
|
+
assertNoDisabledInExpression(validation.expression, widgets);
|
|
11026
|
+
const validVariableNames = getEnabledWidgetNames(widgets);
|
|
10998
11027
|
const variables = extractVariables$1(validation.expression, validVariableNames);
|
|
10999
11028
|
return {
|
|
11000
11029
|
mode: VALIDATION_MODES.EXPRESSION,
|
|
@@ -11002,13 +11031,15 @@ function transformValidation(validation, answers, widgets) {
|
|
|
11002
11031
|
variables
|
|
11003
11032
|
};
|
|
11004
11033
|
} else if (validation.type === AST_TYPES.MIXED_VALIDATION) {
|
|
11005
|
-
|
|
11034
|
+
assertNoDisabledInExpression(validation.expression, widgets);
|
|
11035
|
+
const validVariableNames = getEnabledWidgetNames(widgets);
|
|
11006
11036
|
const variables = extractVariables$1(validation.expression, validVariableNames);
|
|
11007
11037
|
const inExpression = new Set(variables);
|
|
11008
11038
|
for (const [name, widget] of Object.entries(widgets)) {
|
|
11009
11039
|
if (name.startsWith("_")) continue;
|
|
11010
11040
|
if (name in answers) continue;
|
|
11011
11041
|
if (inExpression.has(name)) continue;
|
|
11042
|
+
if (isDisabledInputJSON(widget)) continue;
|
|
11012
11043
|
if (widget.type === JSON_TYPES.FRACTION) {
|
|
11013
11044
|
const w = widget;
|
|
11014
11045
|
if (w.numerator !== null && w.denominator !== null) continue;
|
|
@@ -11030,6 +11061,17 @@ function transformValidation(validation, answers, widgets) {
|
|
|
11030
11061
|
}
|
|
11031
11062
|
throw new Error(`Unknown validation type: ${validation.type}`);
|
|
11032
11063
|
}
|
|
11064
|
+
function assertNoDisabledInExpression(expression, widgets) {
|
|
11065
|
+
const disabled = extractVariables$1(expression, Object.keys(widgets)).filter((name) => widgets[name] && isDisabledInputJSON(widgets[name]));
|
|
11066
|
+
if (disabled.length > 0) {
|
|
11067
|
+
throw new Error(
|
|
11068
|
+
`WIDGET_ANSWER: expression references disabled input(s) ${disabled.map((n) => `"${n}"`).join(", ")} — a disabled input is never filled, so the expression could never be true`
|
|
11069
|
+
);
|
|
11070
|
+
}
|
|
11071
|
+
}
|
|
11072
|
+
function getEnabledWidgetNames(widgets) {
|
|
11073
|
+
return Object.entries(widgets).filter(([, widget]) => !isDisabledInputJSON(widget)).map(([name]) => name);
|
|
11074
|
+
}
|
|
11033
11075
|
function extractVariables$1(expression, validVariables) {
|
|
11034
11076
|
const matches = expression.match(/\b[a-zA-Z_][a-zA-Z0-9_]*\b/g) || [];
|
|
11035
11077
|
const variables = matches.filter(
|
|
@@ -11545,7 +11587,43 @@ function preprocessFractionValues(exercise, userInputs) {
|
|
|
11545
11587
|
}
|
|
11546
11588
|
return result;
|
|
11547
11589
|
}
|
|
11590
|
+
function withoutDisabledWidgets(exercise) {
|
|
11591
|
+
const validation = exercise.validation;
|
|
11592
|
+
if (!validation) return exercise;
|
|
11593
|
+
const disabled = new Set(
|
|
11594
|
+
Object.entries(exercise.widgets).filter(([, widget]) => isDisabledInputJSON(widget)).map(([id]) => id)
|
|
11595
|
+
);
|
|
11596
|
+
if (disabled.size === 0) return exercise;
|
|
11597
|
+
return {
|
|
11598
|
+
...exercise,
|
|
11599
|
+
validation: {
|
|
11600
|
+
...validation,
|
|
11601
|
+
...validation.answers && {
|
|
11602
|
+
answers: Object.fromEntries(
|
|
11603
|
+
Object.entries(validation.answers).filter(([id]) => !disabled.has(id))
|
|
11604
|
+
)
|
|
11605
|
+
},
|
|
11606
|
+
...validation.variables && {
|
|
11607
|
+
variables: validation.variables.filter((v) => !disabled.has(v))
|
|
11608
|
+
}
|
|
11609
|
+
}
|
|
11610
|
+
};
|
|
11611
|
+
}
|
|
11612
|
+
function findDisabledInExpression(exercise) {
|
|
11613
|
+
const expression = exercise.validation?.expression;
|
|
11614
|
+
if (!expression) return [];
|
|
11615
|
+
return extractVariables(expression, Object.keys(exercise.widgets)).filter((name) => exercise.widgets[name] && isDisabledInputJSON(exercise.widgets[name]));
|
|
11616
|
+
}
|
|
11548
11617
|
function validate(exercise, userInputs) {
|
|
11618
|
+
const disabledInExpression = findDisabledInExpression(exercise);
|
|
11619
|
+
if (disabledInExpression.length > 0) {
|
|
11620
|
+
return {
|
|
11621
|
+
isCorrect: false,
|
|
11622
|
+
details: {},
|
|
11623
|
+
error: `Expression references disabled input(s) ${disabledInExpression.map((n) => `"${n}"`).join(", ")}`
|
|
11624
|
+
};
|
|
11625
|
+
}
|
|
11626
|
+
exercise = withoutDisabledWidgets(exercise);
|
|
11549
11627
|
if (!exercise.validation) {
|
|
11550
11628
|
return {
|
|
11551
11629
|
isCorrect: true,
|
|
@@ -11575,6 +11653,7 @@ function validate(exercise, userInputs) {
|
|
|
11575
11653
|
}
|
|
11576
11654
|
}
|
|
11577
11655
|
function getAnswerableInputs(exercise) {
|
|
11656
|
+
exercise = withoutDisabledWidgets(exercise);
|
|
11578
11657
|
if (!exercise.validation) return [];
|
|
11579
11658
|
const keys = /* @__PURE__ */ new Set();
|
|
11580
11659
|
if (exercise.validation.mode === VALIDATION_MODES.SIMPLE) {
|
|
@@ -11589,6 +11668,7 @@ function getAnswerableInputs(exercise) {
|
|
|
11589
11668
|
}
|
|
11590
11669
|
function getMissingInputs(exercise, userInputs) {
|
|
11591
11670
|
const missing = [];
|
|
11671
|
+
exercise = withoutDisabledWidgets(exercise);
|
|
11592
11672
|
if (!exercise.validation) {
|
|
11593
11673
|
return missing;
|
|
11594
11674
|
}
|
|
@@ -16326,6 +16406,7 @@ const InlineInput = ({
|
|
|
16326
16406
|
length,
|
|
16327
16407
|
digits,
|
|
16328
16408
|
placeholder,
|
|
16409
|
+
disabled = false,
|
|
16329
16410
|
onChange,
|
|
16330
16411
|
onFocus
|
|
16331
16412
|
}) => {
|
|
@@ -16340,13 +16421,14 @@ const InlineInput = ({
|
|
|
16340
16421
|
const expectedLength = inputType === "numeric" ? digits : length;
|
|
16341
16422
|
const dynamicWidth = `${(expectedLength || 5) * 0.8 + 1}em`;
|
|
16342
16423
|
const statusClass = validationStatus ? ` widget--${validationStatus}` : "";
|
|
16424
|
+
const disabledClass = disabled ? " inline-input--disabled" : "";
|
|
16343
16425
|
return /* @__PURE__ */ jsx(
|
|
16344
16426
|
Droppable,
|
|
16345
16427
|
{
|
|
16346
16428
|
id: widgetId,
|
|
16347
16429
|
accepts: ["drag-option", "placed-chip"],
|
|
16348
16430
|
onDrop: handleDrop,
|
|
16349
|
-
disabled: locked,
|
|
16431
|
+
disabled: locked || disabled,
|
|
16350
16432
|
className: "inline-widget inline-input-droppable",
|
|
16351
16433
|
children: isDropped && value !== "" ? (
|
|
16352
16434
|
// Show as chip only when value came from drag-drop
|
|
@@ -16366,7 +16448,7 @@ const InlineInput = ({
|
|
|
16366
16448
|
{
|
|
16367
16449
|
id: widgetId + "-input",
|
|
16368
16450
|
type: "text",
|
|
16369
|
-
className: `inline-input${statusClass}`,
|
|
16451
|
+
className: `inline-input${statusClass}${disabledClass}`,
|
|
16370
16452
|
value,
|
|
16371
16453
|
onChange: (e) => handleChange(e.target.value, false),
|
|
16372
16454
|
onMouseDown: () => {
|
|
@@ -16374,6 +16456,7 @@ const InlineInput = ({
|
|
|
16374
16456
|
},
|
|
16375
16457
|
onFocus: () => onFocus?.(widgetId),
|
|
16376
16458
|
readOnly: locked,
|
|
16459
|
+
disabled,
|
|
16377
16460
|
maxLength: expectedLength,
|
|
16378
16461
|
style: { width: dynamicWidth },
|
|
16379
16462
|
placeholder
|
|
@@ -18622,6 +18705,7 @@ const WidgetRenderer = ({
|
|
|
18622
18705
|
length: widget.inputType === "string" ? widget.length : void 0,
|
|
18623
18706
|
digits: widget.inputType === "numeric" ? widget.digits : void 0,
|
|
18624
18707
|
placeholder: widget.placeholder,
|
|
18708
|
+
disabled: widget.disabled,
|
|
18625
18709
|
onChange,
|
|
18626
18710
|
onFocus: onInputFocus
|
|
18627
18711
|
}
|
|
@@ -20879,7 +20963,7 @@ function buildInitialValuesSignature(json) {
|
|
|
20879
20963
|
}
|
|
20880
20964
|
function buildRevealedValues(json) {
|
|
20881
20965
|
const revealed = {};
|
|
20882
|
-
const validation = json.validation;
|
|
20966
|
+
const validation = withoutDisabledWidgets(json).validation;
|
|
20883
20967
|
if (!validation) return revealed;
|
|
20884
20968
|
const answers = validation.answers ?? {};
|
|
20885
20969
|
for (const [widgetId, expected] of Object.entries(answers)) {
|