@mathscan/math-exercise-engine 1.2.5 → 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.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
- answer
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
- answer
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
- validationSection = { validation: transformValidation(ast.validation, answers, widgets) };
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
- const validVariableNames = Object.keys(widgets);
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
- const validVariableNames = Object.keys(widgets);
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)) {