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