@mathscan/math-exercise-engine 1.2.12 → 1.2.14

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.
@@ -2092,6 +2092,8 @@ const InlineInputWidgetSchema = zod.z.discriminatedUnion("inputType", [
2092
2092
  width: InlineInputWidthSchema,
2093
2093
  placeholder: zod.z.string().max(32).optional(),
2094
2094
  disabled: zod.z.boolean().optional(),
2095
+ /** Starting value the student can edit (shown read-only when disabled). */
2096
+ prefill: zod.z.string().optional(),
2095
2097
  answer: zod.z.union([zod.z.string(), zod.z.number()]).optional()
2096
2098
  }),
2097
2099
  zod.z.object({
@@ -2102,6 +2104,8 @@ const InlineInputWidgetSchema = zod.z.discriminatedUnion("inputType", [
2102
2104
  width: InlineInputWidthSchema,
2103
2105
  placeholder: zod.z.string().max(32).optional(),
2104
2106
  disabled: zod.z.boolean().optional(),
2107
+ /** Starting value the student can edit (shown read-only when disabled). */
2108
+ prefill: zod.z.string().optional(),
2105
2109
  answer: zod.z.union([zod.z.string(), zod.z.number()]).optional()
2106
2110
  })
2107
2111
  ]);
@@ -2257,7 +2261,7 @@ function createBoldText(value, highlight) {
2257
2261
  highlight
2258
2262
  });
2259
2263
  }
2260
- function createStringInput(name, length, answer, placeholder, disabled, width) {
2264
+ function createStringInput(name, length, answer, placeholder, disabled, width, prefill) {
2261
2265
  return parseOrThrow(
2262
2266
  InlineInputWidgetSchema,
2263
2267
  {
@@ -2268,13 +2272,14 @@ function createStringInput(name, length, answer, placeholder, disabled, width) {
2268
2272
  width,
2269
2273
  placeholder,
2270
2274
  disabled,
2275
+ prefill,
2271
2276
  // A disabled input is never graded, so its answer is dropped.
2272
2277
  answer: disabled ? void 0 : answer
2273
2278
  },
2274
2279
  "STRING_INPUT"
2275
2280
  );
2276
2281
  }
2277
- function createNumericInput(name, digits, answer, placeholder, disabled, width) {
2282
+ function createNumericInput(name, digits, answer, placeholder, disabled, width, prefill) {
2278
2283
  return parseOrThrow(
2279
2284
  InlineInputWidgetSchema,
2280
2285
  {
@@ -2285,6 +2290,7 @@ function createNumericInput(name, digits, answer, placeholder, disabled, width)
2285
2290
  width,
2286
2291
  placeholder,
2287
2292
  disabled,
2293
+ prefill,
2288
2294
  // A disabled input is never graded, so its answer is dropped.
2289
2295
  answer: disabled ? void 0 : answer
2290
2296
  },
@@ -4324,6 +4330,7 @@ class NaturalDSLVisitor extends BaseCstVisitor {
4324
4330
  const name = params.name;
4325
4331
  const answer = params.answer;
4326
4332
  const placeholder = params.placeholder;
4333
+ const prefill = params.prefill === void 0 ? void 0 : String(params.prefill);
4327
4334
  let disabled;
4328
4335
  if (params.disabled === true || params.disabled === "true") disabled = true;
4329
4336
  else if (params.disabled === false || params.disabled === "false") disabled = false;
@@ -4341,10 +4348,10 @@ class NaturalDSLVisitor extends BaseCstVisitor {
4341
4348
  }
4342
4349
  if (ctx.stringInput) {
4343
4350
  const length = params.length;
4344
- return createStringInput(name, length, answer, placeholder, disabled, width);
4351
+ return createStringInput(name, length, answer, placeholder, disabled, width, prefill);
4345
4352
  }
4346
4353
  const digits = params.digits;
4347
- return createNumericInput(name, digits, answer, placeholder, disabled, width);
4354
+ return createNumericInput(name, digits, answer, placeholder, disabled, width, prefill);
4348
4355
  }
4349
4356
  inlineDragOption(ctx) {
4350
4357
  const valueToken = ctx.StringLiteral[0];
@@ -5088,7 +5095,9 @@ const StringInputJSONSchema = zod.z.object({
5088
5095
  width: InlineInputWidthSchema,
5089
5096
  placeholder: zod.z.string().max(32).optional(),
5090
5097
  /** Gray, non-interactive, never graded (any answer for it is ignored). */
5091
- disabled: zod.z.boolean().optional()
5098
+ disabled: zod.z.boolean().optional(),
5099
+ /** Starting value the student can edit (shown read-only when disabled). */
5100
+ prefill: zod.z.string().optional()
5092
5101
  });
5093
5102
  const NumericInputJSONSchema = zod.z.object({
5094
5103
  type: zod.z.literal(JSON_TYPES.INLINE_INPUT),
@@ -5098,7 +5107,9 @@ const NumericInputJSONSchema = zod.z.object({
5098
5107
  width: InlineInputWidthSchema,
5099
5108
  placeholder: zod.z.string().max(32).optional(),
5100
5109
  /** Gray, non-interactive, never graded (any answer for it is ignored). */
5101
- disabled: zod.z.boolean().optional()
5110
+ disabled: zod.z.boolean().optional(),
5111
+ /** Starting value the student can edit (shown read-only when disabled). */
5112
+ prefill: zod.z.string().optional()
5102
5113
  });
5103
5114
  const InlineInputJSONSchema = zod.z.discriminatedUnion("inputType", [
5104
5115
  StringInputJSONSchema,
@@ -10993,8 +11004,10 @@ function toJSON(ast) {
10993
11004
  layout.push(...lineItems);
10994
11005
  }
10995
11006
  }
11007
+ const hasDragBank = ast.tools?.some((tool) => tool.type === AST_TYPES.DRAG_OPTIONS) ?? false;
10996
11008
  for (const inline of allInlines) {
10997
11009
  if (inline.type === AST_TYPES.INLINE_INPUT) {
11010
+ const disabled = inline.disabled || hasDragBank && inline.prefill !== void 0;
10998
11011
  if (inline.inputType === INPUT_TYPES.STRING) {
10999
11012
  widgets[inline.name] = {
11000
11013
  type: JSON_TYPES.INLINE_INPUT,
@@ -11002,7 +11015,8 @@ function toJSON(ast) {
11002
11015
  length: inline.length,
11003
11016
  ...inline.width !== void 0 && { width: inline.width },
11004
11017
  placeholder: inline.placeholder,
11005
- ...inline.disabled && { disabled: true }
11018
+ ...disabled && { disabled: true },
11019
+ ...inline.prefill !== void 0 && { prefill: inline.prefill }
11006
11020
  };
11007
11021
  } else {
11008
11022
  widgets[inline.name] = {
@@ -11011,10 +11025,11 @@ function toJSON(ast) {
11011
11025
  digits: inline.digits,
11012
11026
  ...inline.width !== void 0 && { width: inline.width },
11013
11027
  placeholder: inline.placeholder,
11014
- ...inline.disabled && { disabled: true }
11028
+ ...disabled && { disabled: true },
11029
+ ...inline.prefill !== void 0 && { prefill: inline.prefill }
11015
11030
  };
11016
11031
  }
11017
- if (inline.answer !== void 0) {
11032
+ if (inline.answer !== void 0 && !disabled) {
11018
11033
  answers[inline.name] = inline.answer;
11019
11034
  }
11020
11035
  } else if (inline.type === AST_TYPES.CLOCK && inline.mode === "interactive") {
@@ -12240,7 +12255,20 @@ function revealGeometryWidget(json) {
12240
12255
  if (!host.revealAnswer(store, loaded.data).success) return null;
12241
12256
  return validation.serializeGeometryState(store.getState().geometry);
12242
12257
  }
12258
+ function withDisabledPrefilledInputs(exercise) {
12259
+ if (exercise.widgets[DRAG_OPTIONS_WIDGET_ID]?.type !== JSON_TYPES.DRAG_OPTIONS) return exercise;
12260
+ const widgets = { ...exercise.widgets };
12261
+ let changed = false;
12262
+ for (const [id, widget] of Object.entries(widgets)) {
12263
+ if (widget.type === JSON_TYPES.INLINE_INPUT && widget.prefill !== void 0 && !widget.disabled) {
12264
+ widgets[id] = { ...widget, disabled: true };
12265
+ changed = true;
12266
+ }
12267
+ }
12268
+ return changed ? { ...exercise, widgets } : exercise;
12269
+ }
12243
12270
  function withoutDisabledWidgets(exercise) {
12271
+ exercise = withDisabledPrefilledInputs(exercise);
12244
12272
  const validation2 = exercise.validation;
12245
12273
  if (!validation2) return exercise;
12246
12274
  const disabled = new Set(
@@ -12263,6 +12291,7 @@ function withoutDisabledWidgets(exercise) {
12263
12291
  };
12264
12292
  }
12265
12293
  function findDisabledInExpression(exercise) {
12294
+ exercise = withDisabledPrefilledInputs(exercise);
12266
12295
  const expression = exercise.validation?.expression;
12267
12296
  if (!expression) return [];
12268
12297
  return extractVariables(expression, Object.keys(exercise.widgets)).filter((name) => exercise.widgets[name] && isDisabledInputJSON(exercise.widgets[name]));
@@ -16343,7 +16372,6 @@ const Draggable = ({
16343
16372
  attributes,
16344
16373
  listeners,
16345
16374
  setNodeRef,
16346
- transform,
16347
16375
  isDragging
16348
16376
  } = useDraggable({
16349
16377
  id,
@@ -16351,7 +16379,6 @@ const Draggable = ({
16351
16379
  disabled
16352
16380
  });
16353
16381
  const style = {
16354
- transform: CSS$1.Translate.toString(transform),
16355
16382
  cursor: disabled ? "default" : "grab",
16356
16383
  touchAction: "none"
16357
16384
  };
@@ -17102,6 +17129,16 @@ const SlotSizer = ({
17102
17129
  style: { ...style, visibility: "hidden" }
17103
17130
  }
17104
17131
  );
17132
+ const FixedInputChip = ({ value }) => /* @__PURE__ */ jsxRuntime.jsx(
17133
+ "button",
17134
+ {
17135
+ type: "button",
17136
+ className: "inline-input-chip",
17137
+ disabled: true,
17138
+ style: OVERLAY_STYLE,
17139
+ children: value
17140
+ }
17141
+ );
17105
17142
  const PlacedInputChip = ({
17106
17143
  widgetId,
17107
17144
  value,
@@ -17160,12 +17197,19 @@ function getTextInputs(container) {
17160
17197
  function canTakeInput(el) {
17161
17198
  return !el.readOnly && !el.disabled;
17162
17199
  }
17200
+ function canAdvanceTo(el) {
17201
+ return !el.disabled && el.tabIndex >= 0;
17202
+ }
17163
17203
  function findNextInput(container, currentKey) {
17164
17204
  const inputs = getTextInputs(container);
17165
17205
  const currentIndex = inputs.findIndex((el) => el.id === getInputElementId(currentKey));
17166
17206
  if (currentIndex === -1) return null;
17167
- return inputs.slice(currentIndex + 1).find(canTakeInput) ?? null;
17207
+ return inputs.slice(currentIndex + 1).find(canAdvanceTo) ?? null;
17168
17208
  }
17209
+ const KeypadContext = React.createContext(false);
17210
+ const useHasKeypad = () => React.useContext(KeypadContext);
17211
+ const DragBankContext = React.createContext(false);
17212
+ const useHasDragBank = () => React.useContext(DragBankContext);
17169
17213
  const InlineInput = ({
17170
17214
  widgetId,
17171
17215
  value,
@@ -17182,6 +17226,8 @@ const InlineInput = ({
17182
17226
  }) => {
17183
17227
  const playSound = useSound();
17184
17228
  const locked = useExerciseLocked();
17229
+ const hasKeypad = useHasKeypad();
17230
+ const hasDragBank = useHasDragBank();
17185
17231
  const handleChange = React.useCallback((newValue, fromDrop = false, sourceOptionId) => {
17186
17232
  onChange(widgetId, newValue, fromDrop, sourceOptionId);
17187
17233
  }, [onChange, widgetId]);
@@ -17192,6 +17238,7 @@ const InlineInput = ({
17192
17238
  const dynamicWidth = `${(width ?? expectedLength ?? 5) * 0.8 + 1}em`;
17193
17239
  const statusClass = validationStatus ? ` widget--${validationStatus}` : "";
17194
17240
  const disabledClass = disabled ? " inline-input--disabled" : "";
17241
+ const isFixedChip = hasDragBank && disabled && value !== "";
17195
17242
  return /* @__PURE__ */ jsxRuntime.jsx(
17196
17243
  Droppable,
17197
17244
  {
@@ -17200,7 +17247,10 @@ const InlineInput = ({
17200
17247
  onDrop: handleDrop,
17201
17248
  disabled: locked || disabled,
17202
17249
  className: "inline-widget inline-input-droppable",
17203
- children: isDropped && value !== "" ? (
17250
+ children: isFixedChip ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
17251
+ /* @__PURE__ */ jsxRuntime.jsx(SlotSizer, { className: "inline-input", style: { width: dynamicWidth }, placeholder }),
17252
+ /* @__PURE__ */ jsxRuntime.jsx(FixedInputChip, { value })
17253
+ ] }) : isDropped && value !== "" ? (
17204
17254
  // Show as chip only when value came from drag-drop
17205
17255
  /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
17206
17256
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -17236,6 +17286,7 @@ const InlineInput = ({
17236
17286
  },
17237
17287
  onFocus: () => onFocus?.(widgetId),
17238
17288
  readOnly: locked,
17289
+ inputMode: hasKeypad ? "none" : void 0,
17239
17290
  disabled,
17240
17291
  maxLength: expectedLength,
17241
17292
  style: { width: dynamicWidth },
@@ -17256,11 +17307,13 @@ const DragOptionChip = ({
17256
17307
  onOptionClick,
17257
17308
  className = ""
17258
17309
  }) => {
17259
- const { usedOptionIds, hasDraggedOption, playHintSound } = useExerciseDnd();
17310
+ const { activeItem, usedOptionIds, hasDraggedOption, playHintSound } = useExerciseDnd();
17260
17311
  const locked = useExerciseLocked();
17261
17312
  const isUsed = usedOptionIds.has(optionId);
17313
+ const isPickedUp = activeItem?.type === "drag-option" && activeItem.id === optionId;
17262
17314
  const { jiggling: hinting, onAnimationEnd: onHintEnd } = useJiggle(hasDraggedOption);
17263
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `drag-option-slot ${className}`.trim(), children: isUsed ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "drag-option-empty", "aria-hidden": "true" }) : /* @__PURE__ */ jsxRuntime.jsx(
17315
+ const emptySlot = /* @__PURE__ */ jsxRuntime.jsx("div", { className: "drag-option-empty", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx("button", { className: "drag-option", disabled: true, children: value }) });
17316
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `drag-option-slot ${className}`.trim(), children: isUsed ? emptySlot : /* @__PURE__ */ jsxRuntime.jsx(
17264
17317
  Draggable,
17265
17318
  {
17266
17319
  id: optionId,
@@ -17268,7 +17321,7 @@ const DragOptionChip = ({
17268
17321
  data: { value, label: value },
17269
17322
  disabled: locked,
17270
17323
  className: "drag-option-wrapper",
17271
- children: /* @__PURE__ */ jsxRuntime.jsx(
17324
+ children: isPickedUp ? emptySlot : /* @__PURE__ */ jsxRuntime.jsx(
17272
17325
  "button",
17273
17326
  {
17274
17327
  className: hinting && !locked ? "drag-option drag-option--hint" : "drag-option",
@@ -17438,6 +17491,7 @@ const FractionInput = ({
17438
17491
  }) => {
17439
17492
  const playSound = useSound();
17440
17493
  const locked = useExerciseLocked();
17494
+ const hasKeypad = useHasKeypad();
17441
17495
  const numKey = getFractionKey(widgetId, "num");
17442
17496
  const denKey = getFractionKey(widgetId, "den");
17443
17497
  const numValue = values[numKey] ?? "";
@@ -17491,6 +17545,7 @@ const FractionInput = ({
17491
17545
  },
17492
17546
  onFocus: () => onFocus?.(subKey),
17493
17547
  readOnly: locked,
17548
+ inputMode: hasKeypad ? "none" : void 0,
17494
17549
  maxLength: placeholder ? Math.max(20, placeholder.length) : 4,
17495
17550
  size: placeholder ? placeholder.length : void 0,
17496
17551
  "aria-label": label,
@@ -19701,7 +19756,7 @@ function isGeometryWidget(widget) {
19701
19756
  return widget.type === JSON_TYPES.GEOMETRY;
19702
19757
  }
19703
19758
  const GeometryWidget = React.lazy(async () => ({
19704
- default: (await Promise.resolve().then(() => require("./GeometryWidget-BMOiQMvj.js"))).GeometryWidget
19759
+ default: (await Promise.resolve().then(() => require("./GeometryWidget-D6_DlxhZ.js"))).GeometryWidget
19705
19760
  }));
19706
19761
  const WidgetRenderer = ({
19707
19762
  widget,
@@ -22003,7 +22058,7 @@ const LayoutRenderer = ({
22003
22058
  return null;
22004
22059
  }
22005
22060
  };
22006
- function LionHand({ className }) {
22061
+ function ChameleonHand({ className }) {
22007
22062
  const id = React.useId();
22008
22063
  const shadowId = `${id}-shadow`;
22009
22064
  const maskId = `${id}-mask`;
@@ -22030,26 +22085,90 @@ function LionHand({ className }) {
22030
22085
  y: "19",
22031
22086
  width: "49",
22032
22087
  height: "42",
22033
- children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M49.7484 33.9668C58.158 45.6281 37.9572 61.4399 28.1745 60.1051C17.1397 58.5995 5.03802 42.2474 10.4349 38.3224C15.8318 34.3974 18.9715 32.4031 23.1181 30.1278C24.1486 29.5624 25.1742 29.0263 26.1947 28.5358C33.7314 24.764 52.8138 16.0308 57.0969 21.97C59.9201 25.8848 54.8651 28.9522 48.1097 31.8806C48.658 32.518 49.2042 33.2123 49.7484 33.9668Z", fill: "#FCD592" })
22088
+ children: /* @__PURE__ */ jsxRuntime.jsx(
22089
+ "path",
22090
+ {
22091
+ d: "M49.7484 33.9668C58.158 45.6281 37.9572 61.4399 28.1745 60.1051C17.1397 58.5995 5.03802 42.2474 10.4349 38.3224C15.8318 34.3974 18.9715 32.4031 23.1181 30.1278C24.1486 29.5624 25.1742 29.0263 26.1947 28.5358C33.7314 24.764 52.8138 16.0308 57.0969 21.97C59.9201 25.8848 54.8651 28.9522 48.1097 31.8806C48.658 32.518 49.2042 33.2123 49.7484 33.9668Z",
22092
+ fill: "#B7DB4E"
22093
+ }
22094
+ )
22034
22095
  }
22035
22096
  ),
22036
22097
  /* @__PURE__ */ jsxRuntime.jsxs("g", { mask: `url(#${maskId})`, children: [
22037
- /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M49.7484 33.9672C58.158 45.6285 38.3118 60.3151 32.1037 64.7332C22.6672 71.4486 2.68204 43.9969 12.2424 37.1926C15.1884 35.0959 18.9716 32.4035 23.1182 30.1282C24.7308 29.2433 26.3314 28.4303 27.9195 27.7514C36.611 23.6398 54.4269 16.1501 58.4471 21.7248C61.34 25.7362 55.5686 28.9294 48.2024 31.9893C48.7196 32.5968 49.2349 33.2552 49.7484 33.9672Z", fill: "#FCD592" }),
22038
- /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12.2426 37.1924C2.68223 43.9967 22.6674 71.4485 32.1039 64.733C31.3434 63.6785 26.4317 61.1668 25.3134 59.6161C24.1951 58.0654 22.794 50.5211 22.123 49.5907C21.4521 48.6603 16.3219 45.7409 15.5637 44.4032C14.8055 43.0655 13.8979 39.4877 14.7258 35.4253L12.2426 37.1924Z", fill: "#6B291E" }),
22039
- /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M50.4102 36.1737C50.6823 37.524 52.0035 38.4016 53.3614 38.1339C54.7192 37.8662 55.5994 36.5546 55.3274 35.2043L52.8688 35.689L50.4102 36.1737ZM38.8014 26.9523C37.4241 27.0934 36.425 28.3178 36.5697 29.6872C36.7144 31.0565 37.9482 32.0522 39.3255 31.9111L39.0634 29.4317L38.8014 26.9523ZM52.8688 35.689C55.3274 35.2043 55.327 35.2023 55.3266 35.2004C55.3265 35.1997 55.326 35.1977 55.3258 35.1963C55.3252 35.1935 55.3246 35.1906 55.324 35.1875C55.3227 35.1815 55.3213 35.1749 55.3198 35.1679C55.3168 35.1539 55.3133 35.1379 55.3092 35.1201C55.3011 35.0845 55.2907 35.0415 55.2779 34.9916C55.2522 34.8918 55.2163 34.764 55.1676 34.6127C55.0703 34.3106 54.9205 33.911 54.6961 33.4507C54.247 32.5296 53.4925 31.3535 52.2552 30.241C49.7078 27.9508 45.5021 26.2658 38.8014 26.9523L39.0634 29.4317L39.3255 31.9111C44.9159 31.3384 47.6128 32.782 48.8954 33.9352C49.5731 34.5445 49.9653 35.1687 50.1842 35.6177C50.2938 35.8424 50.3584 36.0201 50.3919 36.1242C50.4086 36.1759 50.4172 36.2084 50.4197 36.2181C50.4209 36.223 50.4206 36.222 50.419 36.2148C50.4181 36.2111 50.417 36.2059 50.4155 36.1991C50.4148 36.1957 50.414 36.1919 50.4131 36.1876C50.4127 36.1855 50.4122 36.1833 50.4117 36.181C50.4115 36.1798 50.4111 36.178 50.411 36.1774C50.4106 36.1755 50.4102 36.1737 52.8688 35.689Z", fill: "#A2634A" }),
22040
- /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M54.7526 47.3947C55.5602 48.5146 57.1269 48.7734 58.2518 47.9729C59.3767 47.1723 59.634 45.6155 58.8264 44.4956L56.7895 45.9452L54.7526 47.3947ZM38.1539 36.5617C36.7805 36.7357 35.8113 37.9837 35.9891 39.3493C36.1669 40.7148 37.4244 41.6808 38.7978 41.5068L38.4758 39.0342L38.1539 36.5617ZM56.7895 45.9452L58.8264 44.4956C57.4277 42.5562 55.3182 40.1317 51.9378 38.4204C48.5385 36.6995 44.0772 35.8112 38.1539 36.5617L38.4758 39.0342L38.7978 41.5068C43.8796 40.863 47.3003 41.6624 49.6691 42.8616C52.0569 44.0704 53.6015 45.7985 54.7526 47.3947L56.7895 45.9452Z", fill: "#A2634A" })
22098
+ /* @__PURE__ */ jsxRuntime.jsx(
22099
+ "path",
22100
+ {
22101
+ d: "M49.7484 33.9672C58.158 45.6285 38.3118 60.3151 32.1037 64.7332C22.6672 71.4486 2.68204 43.9969 12.2424 37.1926C15.1884 35.0959 18.9716 32.4035 23.1182 30.1282C24.7308 29.2433 26.3314 28.4303 27.9195 27.7514C36.611 23.6398 54.4269 16.1501 58.4471 21.7248C61.34 25.7362 55.5686 28.9294 48.2024 31.9893C48.7196 32.5968 49.2349 33.2552 49.7484 33.9672Z",
22102
+ fill: "#B7DB4E"
22103
+ }
22104
+ ),
22105
+ /* @__PURE__ */ jsxRuntime.jsx(
22106
+ "path",
22107
+ {
22108
+ d: "M50.4102 36.1737C50.6823 37.524 52.0035 38.4016 53.3614 38.1339C54.7192 37.8662 55.5994 36.5546 55.3274 35.2043L52.8688 35.689L50.4102 36.1737ZM38.8014 26.9523C37.4241 27.0934 36.425 28.3178 36.5697 29.6872C36.7144 31.0565 37.9482 32.0522 39.3255 31.9111L39.0634 29.4317L38.8014 26.9523ZM52.8688 35.689C55.3274 35.2043 55.327 35.2023 55.3266 35.2004C55.3265 35.1997 55.326 35.1977 55.3258 35.1963C55.3252 35.1935 55.3246 35.1906 55.324 35.1875C55.3227 35.1815 55.3213 35.1749 55.3198 35.1679C55.3168 35.1539 55.3133 35.1379 55.3092 35.1201C55.3011 35.0845 55.2907 35.0415 55.2779 34.9916C55.2522 34.8918 55.2163 34.764 55.1676 34.6127C55.0703 34.3106 54.9205 33.911 54.6961 33.4507C54.247 32.5296 53.4925 31.3535 52.2552 30.241C49.7078 27.9508 45.5021 26.2658 38.8014 26.9523L39.0634 29.4317L39.3255 31.9111C44.9159 31.3384 47.6128 32.782 48.8954 33.9352C49.5731 34.5445 49.9653 35.1687 50.1842 35.6177C50.2938 35.8424 50.3584 36.0201 50.3919 36.1242C50.4086 36.1759 50.4172 36.2084 50.4197 36.2181C50.4209 36.223 50.4206 36.222 50.419 36.2148C50.4181 36.2111 50.417 36.2059 50.4155 36.1991C50.4148 36.1957 50.414 36.1919 50.4131 36.1876C50.4127 36.1855 50.4122 36.1833 50.4117 36.181C50.4115 36.1798 50.4111 36.178 50.411 36.1774C50.4106 36.1755 50.4102 36.1737 52.8688 35.689Z",
22109
+ fill: "#9CAF63"
22110
+ }
22111
+ ),
22112
+ /* @__PURE__ */ jsxRuntime.jsx(
22113
+ "path",
22114
+ {
22115
+ d: "M54.7526 47.3947C55.5602 48.5146 57.1269 48.7734 58.2518 47.9729C59.3767 47.1723 59.634 45.6155 58.8264 44.4956L56.7895 45.9452L54.7526 47.3947ZM38.1539 36.5617C36.7805 36.7357 35.8113 37.9837 35.9891 39.3493C36.1669 40.7148 37.4244 41.6808 38.7978 41.5068L38.4758 39.0342L38.1539 36.5617ZM56.7895 45.9452L58.8264 44.4956C57.4277 42.5562 55.3182 40.1317 51.9378 38.4204C48.5385 36.6995 44.0772 35.8112 38.1539 36.5617L38.4758 39.0342L38.7978 41.5068C43.8796 40.863 47.3003 41.6624 49.6691 42.8616C52.0569 44.0704 53.6015 45.7985 54.7526 47.3947L56.7895 45.9452Z",
22116
+ fill: "#9CAF63"
22117
+ }
22118
+ )
22041
22119
  ] })
22042
22120
  ] }),
22043
- /* @__PURE__ */ jsxRuntime.jsx("defs", { children: /* @__PURE__ */ jsxRuntime.jsxs("filter", { id: shadowId, x: "-1.99995", y: "0", width: "78.3658", height: "81.8518", filterUnits: "userSpaceOnUse", colorInterpolationFilters: "sRGB", children: [
22044
- /* @__PURE__ */ jsxRuntime.jsx("feFlood", { floodOpacity: "0", result: "BackgroundImageFix" }),
22045
- /* @__PURE__ */ jsxRuntime.jsx("feColorMatrix", { in: "SourceAlpha", type: "matrix", values: "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0", result: "hardAlpha" }),
22046
- /* @__PURE__ */ jsxRuntime.jsx("feOffset", { dy: "2" }),
22047
- /* @__PURE__ */ jsxRuntime.jsx("feGaussianBlur", { stdDeviation: "1" }),
22048
- /* @__PURE__ */ jsxRuntime.jsx("feComposite", { in2: "hardAlpha", operator: "out" }),
22049
- /* @__PURE__ */ jsxRuntime.jsx("feColorMatrix", { type: "matrix", values: "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0" }),
22050
- /* @__PURE__ */ jsxRuntime.jsx("feBlend", { mode: "normal", in2: "BackgroundImageFix", result: "effect1_dropShadow" }),
22051
- /* @__PURE__ */ jsxRuntime.jsx("feBlend", { mode: "normal", in: "SourceGraphic", in2: "effect1_dropShadow", result: "shape" })
22052
- ] }) })
22121
+ /* @__PURE__ */ jsxRuntime.jsx("defs", { children: /* @__PURE__ */ jsxRuntime.jsxs(
22122
+ "filter",
22123
+ {
22124
+ id: shadowId,
22125
+ x: "-1.99995",
22126
+ y: "0",
22127
+ width: "78.3658",
22128
+ height: "81.8518",
22129
+ filterUnits: "userSpaceOnUse",
22130
+ colorInterpolationFilters: "sRGB",
22131
+ children: [
22132
+ /* @__PURE__ */ jsxRuntime.jsx("feFlood", { floodOpacity: "0", result: "BackgroundImageFix" }),
22133
+ /* @__PURE__ */ jsxRuntime.jsx(
22134
+ "feColorMatrix",
22135
+ {
22136
+ in: "SourceAlpha",
22137
+ type: "matrix",
22138
+ values: "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0",
22139
+ result: "hardAlpha"
22140
+ }
22141
+ ),
22142
+ /* @__PURE__ */ jsxRuntime.jsx("feOffset", { dy: "2" }),
22143
+ /* @__PURE__ */ jsxRuntime.jsx("feGaussianBlur", { stdDeviation: "1" }),
22144
+ /* @__PURE__ */ jsxRuntime.jsx("feComposite", { in2: "hardAlpha", operator: "out" }),
22145
+ /* @__PURE__ */ jsxRuntime.jsx(
22146
+ "feColorMatrix",
22147
+ {
22148
+ type: "matrix",
22149
+ values: "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"
22150
+ }
22151
+ ),
22152
+ /* @__PURE__ */ jsxRuntime.jsx(
22153
+ "feBlend",
22154
+ {
22155
+ mode: "normal",
22156
+ in2: "BackgroundImageFix",
22157
+ result: "effect1_dropShadow"
22158
+ }
22159
+ ),
22160
+ /* @__PURE__ */ jsxRuntime.jsx(
22161
+ "feBlend",
22162
+ {
22163
+ mode: "normal",
22164
+ in: "SourceGraphic",
22165
+ in2: "effect1_dropShadow",
22166
+ result: "shape"
22167
+ }
22168
+ )
22169
+ ]
22170
+ }
22171
+ ) })
22053
22172
  ]
22054
22173
  }
22055
22174
  );
@@ -22111,6 +22230,8 @@ const TutorialOverlay = ({ tutorial, root, json, options, onClose }) => {
22111
22230
  tick();
22112
22231
  return () => cancelAnimationFrame(raf);
22113
22232
  }, [step]);
22233
+ const showBubble = options.showBubble !== false;
22234
+ const autoAdvance = options.autoAdvance === true || !showBubble;
22114
22235
  const [tapIndex, setTapIndex] = React.useState(0);
22115
22236
  const [tapCount, setTapCount] = React.useState(0);
22116
22237
  const [shownStep, setShownStep] = React.useState(stepIndex);
@@ -22120,11 +22241,13 @@ const TutorialOverlay = ({ tutorial, root, json, options, onClose }) => {
22120
22241
  setTapCount(0);
22121
22242
  }
22122
22243
  React.useEffect(() => {
22244
+ const targets = step.taps(ctxRef.current);
22245
+ if (autoAdvance && tapIndex >= targets.length) return;
22123
22246
  let release;
22124
22247
  const press = setTimeout(() => {
22125
22248
  setTapCount((n) => n + 1);
22126
- const targets = step.taps(ctxRef.current);
22127
- const el = targets[tapIndex % Math.max(1, targets.length)];
22249
+ const targets2 = step.taps(ctxRef.current);
22250
+ const el = targets2[tapIndex % Math.max(1, targets2.length)];
22128
22251
  if (el instanceof Element) {
22129
22252
  el.classList.add("met-tutorial-pressed");
22130
22253
  release = setTimeout(() => el.classList.remove("met-tutorial-pressed"), PRESS_HOLD_MS);
@@ -22137,23 +22260,44 @@ const TutorialOverlay = ({ tutorial, root, json, options, onClose }) => {
22137
22260
  if (release) clearTimeout(release);
22138
22261
  root.querySelectorAll(".met-tutorial-pressed").forEach((el) => el.classList.remove("met-tutorial-pressed"));
22139
22262
  };
22140
- }, [step, tapIndex, root]);
22263
+ }, [step, tapIndex, root, autoAdvance]);
22141
22264
  const text = options.texts?.[step.id] ?? step.text;
22142
22265
  const textRef = React.useRef(text);
22143
22266
  textRef.current = text;
22267
+ const onCloseRef = React.useRef(onClose);
22268
+ onCloseRef.current = onClose;
22269
+ const isLast = stepIndex === steps.length - 1;
22270
+ const [completedStep, setCompletedStep] = React.useState(null);
22144
22271
  React.useEffect(() => {
22145
22272
  optionsRef.current.onStep?.(stepIndex, step.id);
22146
- optionsRef.current.onSpeak?.(step.id, textRef.current);
22147
- }, [stepIndex, step]);
22148
- const isLast = stepIndex === steps.length - 1;
22273
+ const speech = Promise.resolve(optionsRef.current.onSpeak?.(step.id, textRef.current)).catch(() => {
22274
+ });
22275
+ if (!autoAdvance) return;
22276
+ const duration = Math.max(2, step.taps(ctxRef.current).length) * CYCLE_MS;
22277
+ let cancelled = false;
22278
+ let timer;
22279
+ const animation = new Promise((resolve2) => {
22280
+ timer = setTimeout(resolve2, duration);
22281
+ });
22282
+ void Promise.all([speech, animation]).then(() => {
22283
+ if (cancelled) return;
22284
+ if (isLast) {
22285
+ if (showBubble) setCompletedStep(stepIndex);
22286
+ else onCloseRef.current(true);
22287
+ } else setStepIndex((i) => i + 1);
22288
+ });
22289
+ return () => {
22290
+ cancelled = true;
22291
+ clearTimeout(timer);
22292
+ };
22293
+ }, [stepIndex, step, autoAdvance, isLast, showBubble]);
22294
+ const showNext = !autoAdvance || isLast && completedStep === stepIndex;
22149
22295
  const next = () => {
22150
22296
  if (isLast) onClose(true);
22151
22297
  else setStepIndex((i) => i + 1);
22152
22298
  };
22153
22299
  const overlayRef = React.useRef(null);
22154
22300
  const nextRef = React.useRef(null);
22155
- const onCloseRef = React.useRef(onClose);
22156
- onCloseRef.current = onClose;
22157
22301
  React.useEffect(() => {
22158
22302
  const previous = document.activeElement instanceof HTMLElement ? document.activeElement : null;
22159
22303
  previous?.blur();
@@ -22186,15 +22330,15 @@ const TutorialOverlay = ({ tutorial, root, json, options, onClose }) => {
22186
22330
  }, []);
22187
22331
  const bubbleShown = layout !== null;
22188
22332
  React.useEffect(() => {
22189
- nextRef.current?.focus({ focusVisible: false });
22190
- }, [stepIndex, bubbleShown]);
22333
+ (nextRef.current ?? overlayRef.current)?.focus({ focusVisible: false });
22334
+ }, [stepIndex, bubbleShown, showNext]);
22191
22335
  const maskId = `met-tutorial-mask-${React.useId()}`;
22192
22336
  if (typeof document === "undefined") return null;
22193
22337
  const holes = (layout?.spotlight ?? []).filter((r) => r !== null);
22194
- const tapTarget = layout?.taps.length ? layout.taps[tapIndex % layout.taps.length] ?? null : null;
22338
+ const tapTarget = layout?.taps.length && (!autoAdvance || tapIndex < layout.taps.length) ? layout.taps[tapIndex % layout.taps.length] ?? null : null;
22195
22339
  const Scene = step.Scene;
22196
22340
  return reactDom.createPortal(
22197
- /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: overlayRef, className: "met-tutorial", role: "dialog", "aria-modal": "true", "aria-label": text, children: [
22341
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: overlayRef, className: "met-tutorial", tabIndex: -1, role: "dialog", "aria-modal": "true", "aria-label": text, children: [
22198
22342
  /* @__PURE__ */ jsxRuntime.jsxs("svg", { className: "met-tutorial-dim", "aria-hidden": "true", children: [
22199
22343
  /* @__PURE__ */ jsxRuntime.jsx("defs", { children: /* @__PURE__ */ jsxRuntime.jsxs("mask", { id: maskId, children: [
22200
22344
  /* @__PURE__ */ jsxRuntime.jsx("rect", { width: "100%", height: "100%", fill: "white" }),
@@ -22229,7 +22373,7 @@ const TutorialOverlay = ({ tutorial, root, json, options, onClose }) => {
22229
22373
  )),
22230
22374
  Scene && layout && /* @__PURE__ */ jsxRuntime.jsx(Scene, { ctx, spotlight: layout.spotlight, taps: layout.taps, tapCount }),
22231
22375
  tapTarget && /* @__PURE__ */ jsxRuntime.jsx(Hand, { target: tapTarget, tapKey: `${stepIndex}-${tapIndex}` }),
22232
- layout && /* @__PURE__ */ jsxRuntime.jsx(
22376
+ layout && showBubble && /* @__PURE__ */ jsxRuntime.jsx(
22233
22377
  Bubble,
22234
22378
  {
22235
22379
  layout,
@@ -22237,7 +22381,8 @@ const TutorialOverlay = ({ tutorial, root, json, options, onClose }) => {
22237
22381
  stepIndex,
22238
22382
  stepCount: steps.length,
22239
22383
  nextLabel: options.nextLabel ?? DEFAULT_NEXT_LABEL,
22240
- onSpeak: options.onSpeak && (options.canSpeak?.(step.id) ?? true) ? () => optionsRef.current.onSpeak?.(step.id, textRef.current) : void 0,
22384
+ showNext,
22385
+ onSpeak: !autoAdvance && options.onSpeak && (options.canSpeak?.(step.id) ?? true) ? () => optionsRef.current.onSpeak?.(step.id, textRef.current) : void 0,
22241
22386
  onNext: next,
22242
22387
  nextRef
22243
22388
  }
@@ -22265,12 +22410,12 @@ const Hand = ({ target, tapKey }) => {
22265
22410
  style: { left: TUTORIAL_HAND_TIP.x * HAND_W, top: TUTORIAL_HAND_TIP.y * HAND_H }
22266
22411
  }
22267
22412
  ),
22268
- /* @__PURE__ */ jsxRuntime.jsx(LionHand, {})
22413
+ /* @__PURE__ */ jsxRuntime.jsx(ChameleonHand, {})
22269
22414
  ] }, tapKey)
22270
22415
  }
22271
22416
  );
22272
22417
  };
22273
- const Bubble = ({ layout, text, stepIndex, stepCount, nextLabel, onSpeak, onNext, nextRef }) => {
22418
+ const Bubble = ({ layout, text, stepIndex, stepCount, nextLabel, showNext, onSpeak, onNext, nextRef }) => {
22274
22419
  const { anchor, root, vw, vh } = layout;
22275
22420
  const clampLeft = (x) => Math.min(Math.max(EDGE, x), Math.max(EDGE, vw - BUBBLE_W - EDGE));
22276
22421
  const style = anchor ? { left: clampLeft(anchor.x), bottom: Math.max(EDGE, vh - anchor.y + BUBBLE_GAP), width: BUBBLE_W } : { left: clampLeft((root?.x ?? 0) + EDGE * 2), top: (root?.y ?? 0) + EDGE * 2, width: BUBBLE_W };
@@ -22281,7 +22426,7 @@ const Bubble = ({ layout, text, stepIndex, stepCount, nextLabel, onSpeak, onNext
22281
22426
  /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M4 9v6h4l5 4V5L8 9H4z", fill: "currentColor" }),
22282
22427
  /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M16 8.5a5 5 0 0 1 0 7M18.5 6a8.5 8.5 0 0 1 0 12", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" })
22283
22428
  ] }) }),
22284
- /* @__PURE__ */ jsxRuntime.jsx("button", { ref: nextRef, type: "button", className: "met-tutorial-next", onClick: onNext, children: nextLabel })
22429
+ showNext && /* @__PURE__ */ jsxRuntime.jsx("button", { ref: nextRef, type: "button", className: "met-tutorial-next", onClick: onNext, children: nextLabel })
22285
22430
  ] }),
22286
22431
  stepCount > 1 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "met-tutorial-dots", "aria-hidden": "true", children: Array.from({ length: stepCount }, (_, i) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: i === stepIndex ? "is-current" : i < stepIndex ? "is-done" : "" }, i)) })
22287
22432
  ] });
@@ -22301,7 +22446,16 @@ function answerBox(ctx) {
22301
22446
  const inputs = getTextInputs(ctx.root);
22302
22447
  return inputs.find(canTakeInput) ?? inputs[0] ?? null;
22303
22448
  }
22449
+ function answerDigits(ctx) {
22450
+ const id = answerBox(ctx)?.id.replace(/-input$/, "");
22451
+ const expected = id ? ctx.json.validation?.answers?.[id] : void 0;
22452
+ if (typeof expected !== "number" && typeof expected !== "string") return null;
22453
+ const value = String(expected);
22454
+ return /^\d+$/.test(value) ? [...value] : null;
22455
+ }
22304
22456
  function answerSlots(ctx) {
22457
+ const digits = answerDigits(ctx);
22458
+ if (digits) return digits.length;
22305
22459
  const id = answerBox(ctx)?.id.replace(/-input$/, "");
22306
22460
  const max = id ? getMaxInputLength(ctx.json, id) : void 0;
22307
22461
  return Math.min(MAX_SLOTS, Math.max(1, max ?? DEFAULT_SLOTS));
@@ -22317,13 +22471,14 @@ function checkTarget(ctx) {
22317
22471
  STAND_IN_CHECK_SIZE
22318
22472
  );
22319
22473
  }
22320
- const demoKeys = (ctx) => DEMO_KEYS.map((k) => key(ctx, k));
22321
- const StandInFill = ({ box, slots, filled }) => /* @__PURE__ */ jsxRuntime.jsx(
22474
+ const demoKeys = (ctx) => (answerDigits(ctx) ?? DEMO_KEYS).map((k) => key(ctx, k));
22475
+ const StandInFill = ({ box, slots, filled, digits }) => /* @__PURE__ */ jsxRuntime.jsx(
22322
22476
  "div",
22323
22477
  {
22324
22478
  className: "met-tutorial-fill",
22479
+ dir: "ltr",
22325
22480
  style: { left: box.x, top: box.y, width: box.width, height: box.height },
22326
- children: Array.from({ length: slots }, (_, i) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: i < filled ? "met-tutorial-fill-dot" : "met-tutorial-fill-slot", children: i < filled ? "●" : "_" }, i))
22481
+ children: Array.from({ length: slots }, (_, i) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: i < filled ? "met-tutorial-fill-dot" : "met-tutorial-fill-slot", children: i < filled ? digits?.[i] ?? "●" : "_" }, i))
22327
22482
  }
22328
22483
  );
22329
22484
  const center = (r) => ({ x: r.x + r.width / 2, y: r.y + r.height / 2 });
@@ -22331,7 +22486,8 @@ const HopScene = ({ ctx, spotlight, taps, tapCount }) => {
22331
22486
  const box = spotlight[1];
22332
22487
  if (!box) return null;
22333
22488
  const slots = answerSlots(ctx);
22334
- const filled = tapCount === 0 ? 0 : (tapCount - 1) % slots + 1;
22489
+ const digits = answerDigits(ctx);
22490
+ const filled = digits ? Math.min(tapCount, slots) : tapCount === 0 ? 0 : (tapCount - 1) % slots + 1;
22335
22491
  const from = tapCount > 0 ? taps[(tapCount - 1) % taps.length] : null;
22336
22492
  let arc = null;
22337
22493
  if (from) {
@@ -22342,15 +22498,23 @@ const HopScene = ({ ctx, spotlight, taps, tapCount }) => {
22342
22498
  }
22343
22499
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
22344
22500
  arc && /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "met-tutorial-arc", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: arc }) }),
22345
- arc && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "met-tutorial-hop", style: { offsetPath: `path('${arc}')` } }, tapCount),
22346
- /* @__PURE__ */ jsxRuntime.jsx(StandInFill, { box, slots, filled })
22501
+ arc && /* @__PURE__ */ jsxRuntime.jsx(
22502
+ "div",
22503
+ {
22504
+ className: `met-tutorial-hop${digits ? " met-tutorial-hop--digit" : ""}`,
22505
+ style: { offsetPath: `path('${arc}')` },
22506
+ children: digits?.[(tapCount - 1) % digits.length]
22507
+ },
22508
+ tapCount
22509
+ ),
22510
+ /* @__PURE__ */ jsxRuntime.jsx(StandInFill, { box, slots, filled, digits })
22347
22511
  ] });
22348
22512
  };
22349
22513
  const CheckScene = ({ ctx, spotlight }) => {
22350
22514
  const box = spotlight[0];
22351
22515
  const check = spotlight[1];
22352
22516
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
22353
- box && /* @__PURE__ */ jsxRuntime.jsx(StandInFill, { box, slots: answerSlots(ctx), filled: answerSlots(ctx) }),
22517
+ box && /* @__PURE__ */ jsxRuntime.jsx(StandInFill, { box, slots: answerSlots(ctx), filled: answerSlots(ctx), digits: answerDigits(ctx) }),
22354
22518
  check && !ctx.submitTarget && /* @__PURE__ */ jsxRuntime.jsx(
22355
22519
  "div",
22356
22520
  {
@@ -22391,6 +22555,33 @@ const WIDGET_TUTORIALS = [keypadTutorial];
22391
22555
  function findWidgetTutorial(json) {
22392
22556
  return WIDGET_TUTORIALS.find((t) => t.appliesTo(json)) ?? null;
22393
22557
  }
22558
+ function getAnswerLengthCompletion(json, answerable, values, touched) {
22559
+ const fields = [];
22560
+ for (const widgetId of answerable) {
22561
+ const widget = json.widgets[widgetId];
22562
+ const expected = json.validation?.answers?.[widgetId];
22563
+ if (widget?.type === JSON_TYPES.INLINE_INPUT && !widget.disabled) {
22564
+ fields.push({ id: widgetId, widgetId, expected });
22565
+ } else if (widget?.type === JSON_TYPES.FRACTION) {
22566
+ const parts = typeof expected === "string" ? expected.split("/") : [];
22567
+ const validParts = parts.length === 2;
22568
+ if (widget.numerator === null) {
22569
+ fields.push({ id: getFractionKey(widgetId, "num"), widgetId, expected: validParts ? parts[0] : void 0 });
22570
+ }
22571
+ if (widget.denominator === null) {
22572
+ fields.push({ id: getFractionKey(widgetId, "den"), widgetId, expected: validParts ? parts[1] : void 0 });
22573
+ }
22574
+ }
22575
+ }
22576
+ const answerLengthMatchedIds = fields.filter(({ id, widgetId, expected }) => {
22577
+ const value = values[id];
22578
+ return touched.has(widgetId) && (typeof expected === "string" || typeof expected === "number") && (typeof value === "string" || typeof value === "number") && String(value).length > 0 && String(value).length === String(expected).length;
22579
+ }).map(({ id }) => id);
22580
+ return {
22581
+ isAnswerLengthMatched: fields.length > 0 && answerLengthMatchedIds.length === fields.length,
22582
+ answerLengthMatchedIds
22583
+ };
22584
+ }
22394
22585
  const TOOL_ASPECT_RATIO = {
22395
22586
  ruler: RULER_ASPECT_RATIO,
22396
22587
  protractor: PROTRACTOR_ASPECT_RATIO,
@@ -22422,6 +22613,8 @@ function buildInitialValues(json) {
22422
22613
  initial[id] = String(widget.value);
22423
22614
  } else if (widget.type === JSON_TYPES.GEOMETRY) {
22424
22615
  initial[id] = validation.serializeGeometryState(validation.createEmptyGeometryAttempt());
22616
+ } else if (widget.type === JSON_TYPES.INLINE_INPUT && widget.prefill !== void 0) {
22617
+ initial[id] = widget.prefill;
22425
22618
  }
22426
22619
  }
22427
22620
  return initial;
@@ -22505,7 +22698,7 @@ function buildRevealedDropSources(json, revealedValues) {
22505
22698
  return sources;
22506
22699
  }
22507
22700
  const ExerciseRenderer = React.forwardRef(({
22508
- json,
22701
+ json: suppliedJson,
22509
22702
  onSubmit,
22510
22703
  onChange,
22511
22704
  showSubmitButton = false,
@@ -22514,6 +22707,7 @@ const ExerciseRenderer = React.forwardRef(({
22514
22707
  sounds,
22515
22708
  theme
22516
22709
  }, ref) => {
22710
+ const json = React.useMemo(() => withDisabledPrefilledInputs(suppliedJson), [suppliedJson]);
22517
22711
  const initialValuesSignature = React.useMemo(() => buildInitialValuesSignature(json), [json]);
22518
22712
  const [values, setValues] = React.useState(() => buildInitialValues(json));
22519
22713
  const valuesRef = React.useRef(values);
@@ -22599,7 +22793,8 @@ const ExerciseRenderer = React.forwardRef(({
22599
22793
  missing,
22600
22794
  filledIds,
22601
22795
  isComplete: answerable.length > 0 && missing.length === 0,
22602
- isAnyFilled: filledIds.length > 0
22796
+ isAnyFilled: filledIds.length > 0,
22797
+ ...getAnswerLengthCompletion(json, answerable, valuesRef.current, touchedRef.current)
22603
22798
  };
22604
22799
  },
22605
22800
  reset: () => {
@@ -22641,7 +22836,7 @@ const ExerciseRenderer = React.forwardRef(({
22641
22836
  const container = rootRef.current ?? document.querySelector(".math-exercise-renderer");
22642
22837
  if (!container) return;
22643
22838
  const candidates = getTextInputs(container);
22644
- const target = candidates.find(canTakeInput) ?? (locked ? void 0 : candidates[0]);
22839
+ const target = candidates.find(canTakeInput) ?? (locked ? void 0 : candidates.find(canAdvanceTo));
22645
22840
  target?.focus();
22646
22841
  },
22647
22842
  hasTutorial: () => tutorial !== null,
@@ -22813,7 +23008,7 @@ const ExerciseRenderer = React.forwardRef(({
22813
23008
  if (setSquareWidget) add2("set-square-1", "set-square", Math.round(getSize(setSquareWidget) * 160));
22814
23009
  return { tools: list, toolsMinHeight: maxH };
22815
23010
  }, [rulerWidget, protractorWidget, setSquareWidget]);
22816
- return /* @__PURE__ */ jsxRuntime.jsx(SoundPlayerProvider, { play: playSound, children: /* @__PURE__ */ jsxRuntime.jsx(ExerciseLockProvider, { locked, children: /* @__PURE__ */ jsxRuntime.jsx(ExerciseDndProvider, { droppedSources, exerciseKey: loadedValuesSignature, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: rootRef, className: `math-exercise-renderer ${revealed ? "revealed" : ""} ${locked ? "locked" : ""} ${className}`, style: themeStyle, children: [
23011
+ return /* @__PURE__ */ jsxRuntime.jsx(SoundPlayerProvider, { play: playSound, children: /* @__PURE__ */ jsxRuntime.jsx(ExerciseLockProvider, { locked, children: /* @__PURE__ */ jsxRuntime.jsx(ExerciseDndProvider, { droppedSources, exerciseKey: loadedValuesSignature, children: /* @__PURE__ */ jsxRuntime.jsx(KeypadContext.Provider, { value: Boolean(numericKeypadWidget), children: /* @__PURE__ */ jsxRuntime.jsx(DragBankContext.Provider, { value: json.widgets[DRAG_OPTIONS_WIDGET_ID] !== void 0, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: rootRef, className: `math-exercise-renderer ${revealed ? "revealed" : ""} ${locked ? "locked" : ""} ${className}`, style: themeStyle, children: [
22817
23012
  /* @__PURE__ */ jsxRuntime.jsxs(
22818
23013
  "div",
22819
23014
  {
@@ -22889,9 +23084,10 @@ const ExerciseRenderer = React.forwardRef(({
22889
23084
  onChange: () => {
22890
23085
  },
22891
23086
  onKeypadChange: (newValue) => {
22892
- if (focusedInputId) {
22893
- handleChange(focusedInputId, newValue, false);
22894
- }
23087
+ if (!focusedInputId) return;
23088
+ const maxLength = getMaxInputLength(json, focusedInputId);
23089
+ if (maxLength !== void 0 && newValue.length > maxLength) return;
23090
+ handleChange(focusedInputId, newValue, false);
22895
23091
  }
22896
23092
  }
22897
23093
  ) }),
@@ -22919,12 +23115,13 @@ const ExerciseRenderer = React.forwardRef(({
22919
23115
  options: tutorialRun.options,
22920
23116
  onClose: (finished) => {
22921
23117
  setTutorialRun(null);
23118
+ tutorialRun.options.onClose?.();
22922
23119
  if (finished) tutorialRun.options.onDone?.();
22923
23120
  }
22924
23121
  },
22925
23122
  tutorialRun.run
22926
23123
  )
22927
- ] }) }) }) });
23124
+ ] }) }) }) }) }) });
22928
23125
  });
22929
23126
  ExerciseRenderer.displayName = "ExerciseRenderer";
22930
23127
  function renderExercise(options) {
@@ -23268,4 +23465,4 @@ exports.validateUniqueIds = validateUniqueIds;
23268
23465
  exports.validateVisualFractionCapacity = validateVisualFractionCapacity;
23269
23466
  exports.validateWidgetJSON = validateWidgetJSON;
23270
23467
  exports.validateWidgetJSONSafe = validateWidgetJSONSafe;
23271
- //# sourceMappingURL=index-Di53h19p.js.map
23468
+ //# sourceMappingURL=index-gCvC2wRR.js.map