@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.
- package/README.md +5 -2
- package/dist/{GeometryWidget-Qi5s7Lql.mjs → GeometryWidget-B-EQ53jz.mjs} +2 -2
- package/dist/{GeometryWidget-Qi5s7Lql.mjs.map → GeometryWidget-B-EQ53jz.mjs.map} +1 -1
- package/dist/{GeometryWidget-BMOiQMvj.js → GeometryWidget-D6_DlxhZ.js} +2 -2
- package/dist/{GeometryWidget-BMOiQMvj.js.map → GeometryWidget-D6_DlxhZ.js.map} +1 -1
- package/dist/{index-BtJRsuwL.mjs → index-Daj5xH0X.mjs} +263 -66
- package/dist/index-Daj5xH0X.mjs.map +1 -0
- package/dist/{index-Di53h19p.js → index-gCvC2wRR.js} +263 -66
- package/dist/index-gCvC2wRR.js.map +1 -0
- package/dist/index.d.ts +777 -5
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/dist/style.css +23 -12
- package/package.json +1 -1
- package/dist/index-BtJRsuwL.mjs.map +0 -1
- package/dist/index-Di53h19p.js.map +0 -1
|
@@ -2091,6 +2091,8 @@ const InlineInputWidgetSchema = z.discriminatedUnion("inputType", [
|
|
|
2091
2091
|
width: InlineInputWidthSchema,
|
|
2092
2092
|
placeholder: z.string().max(32).optional(),
|
|
2093
2093
|
disabled: z.boolean().optional(),
|
|
2094
|
+
/** Starting value the student can edit (shown read-only when disabled). */
|
|
2095
|
+
prefill: z.string().optional(),
|
|
2094
2096
|
answer: z.union([z.string(), z.number()]).optional()
|
|
2095
2097
|
}),
|
|
2096
2098
|
z.object({
|
|
@@ -2101,6 +2103,8 @@ const InlineInputWidgetSchema = z.discriminatedUnion("inputType", [
|
|
|
2101
2103
|
width: InlineInputWidthSchema,
|
|
2102
2104
|
placeholder: z.string().max(32).optional(),
|
|
2103
2105
|
disabled: z.boolean().optional(),
|
|
2106
|
+
/** Starting value the student can edit (shown read-only when disabled). */
|
|
2107
|
+
prefill: z.string().optional(),
|
|
2104
2108
|
answer: z.union([z.string(), z.number()]).optional()
|
|
2105
2109
|
})
|
|
2106
2110
|
]);
|
|
@@ -2256,7 +2260,7 @@ function createBoldText(value, highlight) {
|
|
|
2256
2260
|
highlight
|
|
2257
2261
|
});
|
|
2258
2262
|
}
|
|
2259
|
-
function createStringInput(name, length, answer, placeholder, disabled, width) {
|
|
2263
|
+
function createStringInput(name, length, answer, placeholder, disabled, width, prefill) {
|
|
2260
2264
|
return parseOrThrow(
|
|
2261
2265
|
InlineInputWidgetSchema,
|
|
2262
2266
|
{
|
|
@@ -2267,13 +2271,14 @@ function createStringInput(name, length, answer, placeholder, disabled, width) {
|
|
|
2267
2271
|
width,
|
|
2268
2272
|
placeholder,
|
|
2269
2273
|
disabled,
|
|
2274
|
+
prefill,
|
|
2270
2275
|
// A disabled input is never graded, so its answer is dropped.
|
|
2271
2276
|
answer: disabled ? void 0 : answer
|
|
2272
2277
|
},
|
|
2273
2278
|
"STRING_INPUT"
|
|
2274
2279
|
);
|
|
2275
2280
|
}
|
|
2276
|
-
function createNumericInput(name, digits, answer, placeholder, disabled, width) {
|
|
2281
|
+
function createNumericInput(name, digits, answer, placeholder, disabled, width, prefill) {
|
|
2277
2282
|
return parseOrThrow(
|
|
2278
2283
|
InlineInputWidgetSchema,
|
|
2279
2284
|
{
|
|
@@ -2284,6 +2289,7 @@ function createNumericInput(name, digits, answer, placeholder, disabled, width)
|
|
|
2284
2289
|
width,
|
|
2285
2290
|
placeholder,
|
|
2286
2291
|
disabled,
|
|
2292
|
+
prefill,
|
|
2287
2293
|
// A disabled input is never graded, so its answer is dropped.
|
|
2288
2294
|
answer: disabled ? void 0 : answer
|
|
2289
2295
|
},
|
|
@@ -4323,6 +4329,7 @@ class NaturalDSLVisitor extends BaseCstVisitor {
|
|
|
4323
4329
|
const name = params.name;
|
|
4324
4330
|
const answer = params.answer;
|
|
4325
4331
|
const placeholder = params.placeholder;
|
|
4332
|
+
const prefill = params.prefill === void 0 ? void 0 : String(params.prefill);
|
|
4326
4333
|
let disabled;
|
|
4327
4334
|
if (params.disabled === true || params.disabled === "true") disabled = true;
|
|
4328
4335
|
else if (params.disabled === false || params.disabled === "false") disabled = false;
|
|
@@ -4340,10 +4347,10 @@ class NaturalDSLVisitor extends BaseCstVisitor {
|
|
|
4340
4347
|
}
|
|
4341
4348
|
if (ctx.stringInput) {
|
|
4342
4349
|
const length = params.length;
|
|
4343
|
-
return createStringInput(name, length, answer, placeholder, disabled, width);
|
|
4350
|
+
return createStringInput(name, length, answer, placeholder, disabled, width, prefill);
|
|
4344
4351
|
}
|
|
4345
4352
|
const digits = params.digits;
|
|
4346
|
-
return createNumericInput(name, digits, answer, placeholder, disabled, width);
|
|
4353
|
+
return createNumericInput(name, digits, answer, placeholder, disabled, width, prefill);
|
|
4347
4354
|
}
|
|
4348
4355
|
inlineDragOption(ctx) {
|
|
4349
4356
|
const valueToken = ctx.StringLiteral[0];
|
|
@@ -5087,7 +5094,9 @@ const StringInputJSONSchema = z.object({
|
|
|
5087
5094
|
width: InlineInputWidthSchema,
|
|
5088
5095
|
placeholder: z.string().max(32).optional(),
|
|
5089
5096
|
/** Gray, non-interactive, never graded (any answer for it is ignored). */
|
|
5090
|
-
disabled: z.boolean().optional()
|
|
5097
|
+
disabled: z.boolean().optional(),
|
|
5098
|
+
/** Starting value the student can edit (shown read-only when disabled). */
|
|
5099
|
+
prefill: z.string().optional()
|
|
5091
5100
|
});
|
|
5092
5101
|
const NumericInputJSONSchema = z.object({
|
|
5093
5102
|
type: z.literal(JSON_TYPES.INLINE_INPUT),
|
|
@@ -5097,7 +5106,9 @@ const NumericInputJSONSchema = z.object({
|
|
|
5097
5106
|
width: InlineInputWidthSchema,
|
|
5098
5107
|
placeholder: z.string().max(32).optional(),
|
|
5099
5108
|
/** Gray, non-interactive, never graded (any answer for it is ignored). */
|
|
5100
|
-
disabled: z.boolean().optional()
|
|
5109
|
+
disabled: z.boolean().optional(),
|
|
5110
|
+
/** Starting value the student can edit (shown read-only when disabled). */
|
|
5111
|
+
prefill: z.string().optional()
|
|
5101
5112
|
});
|
|
5102
5113
|
const InlineInputJSONSchema = z.discriminatedUnion("inputType", [
|
|
5103
5114
|
StringInputJSONSchema,
|
|
@@ -10992,8 +11003,10 @@ function toJSON(ast) {
|
|
|
10992
11003
|
layout.push(...lineItems);
|
|
10993
11004
|
}
|
|
10994
11005
|
}
|
|
11006
|
+
const hasDragBank = ast.tools?.some((tool) => tool.type === AST_TYPES.DRAG_OPTIONS) ?? false;
|
|
10995
11007
|
for (const inline of allInlines) {
|
|
10996
11008
|
if (inline.type === AST_TYPES.INLINE_INPUT) {
|
|
11009
|
+
const disabled = inline.disabled || hasDragBank && inline.prefill !== void 0;
|
|
10997
11010
|
if (inline.inputType === INPUT_TYPES.STRING) {
|
|
10998
11011
|
widgets[inline.name] = {
|
|
10999
11012
|
type: JSON_TYPES.INLINE_INPUT,
|
|
@@ -11001,7 +11014,8 @@ function toJSON(ast) {
|
|
|
11001
11014
|
length: inline.length,
|
|
11002
11015
|
...inline.width !== void 0 && { width: inline.width },
|
|
11003
11016
|
placeholder: inline.placeholder,
|
|
11004
|
-
...
|
|
11017
|
+
...disabled && { disabled: true },
|
|
11018
|
+
...inline.prefill !== void 0 && { prefill: inline.prefill }
|
|
11005
11019
|
};
|
|
11006
11020
|
} else {
|
|
11007
11021
|
widgets[inline.name] = {
|
|
@@ -11010,10 +11024,11 @@ function toJSON(ast) {
|
|
|
11010
11024
|
digits: inline.digits,
|
|
11011
11025
|
...inline.width !== void 0 && { width: inline.width },
|
|
11012
11026
|
placeholder: inline.placeholder,
|
|
11013
|
-
...
|
|
11027
|
+
...disabled && { disabled: true },
|
|
11028
|
+
...inline.prefill !== void 0 && { prefill: inline.prefill }
|
|
11014
11029
|
};
|
|
11015
11030
|
}
|
|
11016
|
-
if (inline.answer !== void 0) {
|
|
11031
|
+
if (inline.answer !== void 0 && !disabled) {
|
|
11017
11032
|
answers[inline.name] = inline.answer;
|
|
11018
11033
|
}
|
|
11019
11034
|
} else if (inline.type === AST_TYPES.CLOCK && inline.mode === "interactive") {
|
|
@@ -12239,7 +12254,20 @@ function revealGeometryWidget(json) {
|
|
|
12239
12254
|
if (!revealAnswer(store, loaded.data).success) return null;
|
|
12240
12255
|
return serializeGeometryState(store.getState().geometry);
|
|
12241
12256
|
}
|
|
12257
|
+
function withDisabledPrefilledInputs(exercise) {
|
|
12258
|
+
if (exercise.widgets[DRAG_OPTIONS_WIDGET_ID]?.type !== JSON_TYPES.DRAG_OPTIONS) return exercise;
|
|
12259
|
+
const widgets = { ...exercise.widgets };
|
|
12260
|
+
let changed = false;
|
|
12261
|
+
for (const [id, widget] of Object.entries(widgets)) {
|
|
12262
|
+
if (widget.type === JSON_TYPES.INLINE_INPUT && widget.prefill !== void 0 && !widget.disabled) {
|
|
12263
|
+
widgets[id] = { ...widget, disabled: true };
|
|
12264
|
+
changed = true;
|
|
12265
|
+
}
|
|
12266
|
+
}
|
|
12267
|
+
return changed ? { ...exercise, widgets } : exercise;
|
|
12268
|
+
}
|
|
12242
12269
|
function withoutDisabledWidgets(exercise) {
|
|
12270
|
+
exercise = withDisabledPrefilledInputs(exercise);
|
|
12243
12271
|
const validation = exercise.validation;
|
|
12244
12272
|
if (!validation) return exercise;
|
|
12245
12273
|
const disabled = new Set(
|
|
@@ -12262,6 +12290,7 @@ function withoutDisabledWidgets(exercise) {
|
|
|
12262
12290
|
};
|
|
12263
12291
|
}
|
|
12264
12292
|
function findDisabledInExpression(exercise) {
|
|
12293
|
+
exercise = withDisabledPrefilledInputs(exercise);
|
|
12265
12294
|
const expression = exercise.validation?.expression;
|
|
12266
12295
|
if (!expression) return [];
|
|
12267
12296
|
return extractVariables(expression, Object.keys(exercise.widgets)).filter((name) => exercise.widgets[name] && isDisabledInputJSON(exercise.widgets[name]));
|
|
@@ -16342,7 +16371,6 @@ const Draggable = ({
|
|
|
16342
16371
|
attributes,
|
|
16343
16372
|
listeners,
|
|
16344
16373
|
setNodeRef,
|
|
16345
|
-
transform,
|
|
16346
16374
|
isDragging
|
|
16347
16375
|
} = useDraggable({
|
|
16348
16376
|
id,
|
|
@@ -16350,7 +16378,6 @@ const Draggable = ({
|
|
|
16350
16378
|
disabled
|
|
16351
16379
|
});
|
|
16352
16380
|
const style = {
|
|
16353
|
-
transform: CSS$1.Translate.toString(transform),
|
|
16354
16381
|
cursor: disabled ? "default" : "grab",
|
|
16355
16382
|
touchAction: "none"
|
|
16356
16383
|
};
|
|
@@ -17101,6 +17128,16 @@ const SlotSizer = ({
|
|
|
17101
17128
|
style: { ...style, visibility: "hidden" }
|
|
17102
17129
|
}
|
|
17103
17130
|
);
|
|
17131
|
+
const FixedInputChip = ({ value }) => /* @__PURE__ */ jsx(
|
|
17132
|
+
"button",
|
|
17133
|
+
{
|
|
17134
|
+
type: "button",
|
|
17135
|
+
className: "inline-input-chip",
|
|
17136
|
+
disabled: true,
|
|
17137
|
+
style: OVERLAY_STYLE,
|
|
17138
|
+
children: value
|
|
17139
|
+
}
|
|
17140
|
+
);
|
|
17104
17141
|
const PlacedInputChip = ({
|
|
17105
17142
|
widgetId,
|
|
17106
17143
|
value,
|
|
@@ -17159,12 +17196,19 @@ function getTextInputs(container) {
|
|
|
17159
17196
|
function canTakeInput(el) {
|
|
17160
17197
|
return !el.readOnly && !el.disabled;
|
|
17161
17198
|
}
|
|
17199
|
+
function canAdvanceTo(el) {
|
|
17200
|
+
return !el.disabled && el.tabIndex >= 0;
|
|
17201
|
+
}
|
|
17162
17202
|
function findNextInput(container, currentKey) {
|
|
17163
17203
|
const inputs = getTextInputs(container);
|
|
17164
17204
|
const currentIndex = inputs.findIndex((el) => el.id === getInputElementId(currentKey));
|
|
17165
17205
|
if (currentIndex === -1) return null;
|
|
17166
|
-
return inputs.slice(currentIndex + 1).find(
|
|
17206
|
+
return inputs.slice(currentIndex + 1).find(canAdvanceTo) ?? null;
|
|
17167
17207
|
}
|
|
17208
|
+
const KeypadContext = createContext(false);
|
|
17209
|
+
const useHasKeypad = () => useContext(KeypadContext);
|
|
17210
|
+
const DragBankContext = createContext(false);
|
|
17211
|
+
const useHasDragBank = () => useContext(DragBankContext);
|
|
17168
17212
|
const InlineInput = ({
|
|
17169
17213
|
widgetId,
|
|
17170
17214
|
value,
|
|
@@ -17181,6 +17225,8 @@ const InlineInput = ({
|
|
|
17181
17225
|
}) => {
|
|
17182
17226
|
const playSound = useSound();
|
|
17183
17227
|
const locked = useExerciseLocked();
|
|
17228
|
+
const hasKeypad = useHasKeypad();
|
|
17229
|
+
const hasDragBank = useHasDragBank();
|
|
17184
17230
|
const handleChange = useCallback((newValue, fromDrop = false, sourceOptionId) => {
|
|
17185
17231
|
onChange(widgetId, newValue, fromDrop, sourceOptionId);
|
|
17186
17232
|
}, [onChange, widgetId]);
|
|
@@ -17191,6 +17237,7 @@ const InlineInput = ({
|
|
|
17191
17237
|
const dynamicWidth = `${(width ?? expectedLength ?? 5) * 0.8 + 1}em`;
|
|
17192
17238
|
const statusClass = validationStatus ? ` widget--${validationStatus}` : "";
|
|
17193
17239
|
const disabledClass = disabled ? " inline-input--disabled" : "";
|
|
17240
|
+
const isFixedChip = hasDragBank && disabled && value !== "";
|
|
17194
17241
|
return /* @__PURE__ */ jsx(
|
|
17195
17242
|
Droppable,
|
|
17196
17243
|
{
|
|
@@ -17199,7 +17246,10 @@ const InlineInput = ({
|
|
|
17199
17246
|
onDrop: handleDrop,
|
|
17200
17247
|
disabled: locked || disabled,
|
|
17201
17248
|
className: "inline-widget inline-input-droppable",
|
|
17202
|
-
children:
|
|
17249
|
+
children: isFixedChip ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
17250
|
+
/* @__PURE__ */ jsx(SlotSizer, { className: "inline-input", style: { width: dynamicWidth }, placeholder }),
|
|
17251
|
+
/* @__PURE__ */ jsx(FixedInputChip, { value })
|
|
17252
|
+
] }) : isDropped && value !== "" ? (
|
|
17203
17253
|
// Show as chip only when value came from drag-drop
|
|
17204
17254
|
/* @__PURE__ */ jsxs(Fragment, { children: [
|
|
17205
17255
|
/* @__PURE__ */ jsx(
|
|
@@ -17235,6 +17285,7 @@ const InlineInput = ({
|
|
|
17235
17285
|
},
|
|
17236
17286
|
onFocus: () => onFocus?.(widgetId),
|
|
17237
17287
|
readOnly: locked,
|
|
17288
|
+
inputMode: hasKeypad ? "none" : void 0,
|
|
17238
17289
|
disabled,
|
|
17239
17290
|
maxLength: expectedLength,
|
|
17240
17291
|
style: { width: dynamicWidth },
|
|
@@ -17255,11 +17306,13 @@ const DragOptionChip = ({
|
|
|
17255
17306
|
onOptionClick,
|
|
17256
17307
|
className = ""
|
|
17257
17308
|
}) => {
|
|
17258
|
-
const { usedOptionIds, hasDraggedOption, playHintSound } = useExerciseDnd();
|
|
17309
|
+
const { activeItem, usedOptionIds, hasDraggedOption, playHintSound } = useExerciseDnd();
|
|
17259
17310
|
const locked = useExerciseLocked();
|
|
17260
17311
|
const isUsed = usedOptionIds.has(optionId);
|
|
17312
|
+
const isPickedUp = activeItem?.type === "drag-option" && activeItem.id === optionId;
|
|
17261
17313
|
const { jiggling: hinting, onAnimationEnd: onHintEnd } = useJiggle(hasDraggedOption);
|
|
17262
|
-
|
|
17314
|
+
const emptySlot = /* @__PURE__ */ jsx("div", { className: "drag-option-empty", "aria-hidden": "true", children: /* @__PURE__ */ jsx("button", { className: "drag-option", disabled: true, children: value }) });
|
|
17315
|
+
return /* @__PURE__ */ jsx("div", { className: `drag-option-slot ${className}`.trim(), children: isUsed ? emptySlot : /* @__PURE__ */ jsx(
|
|
17263
17316
|
Draggable,
|
|
17264
17317
|
{
|
|
17265
17318
|
id: optionId,
|
|
@@ -17267,7 +17320,7 @@ const DragOptionChip = ({
|
|
|
17267
17320
|
data: { value, label: value },
|
|
17268
17321
|
disabled: locked,
|
|
17269
17322
|
className: "drag-option-wrapper",
|
|
17270
|
-
children: /* @__PURE__ */ jsx(
|
|
17323
|
+
children: isPickedUp ? emptySlot : /* @__PURE__ */ jsx(
|
|
17271
17324
|
"button",
|
|
17272
17325
|
{
|
|
17273
17326
|
className: hinting && !locked ? "drag-option drag-option--hint" : "drag-option",
|
|
@@ -17437,6 +17490,7 @@ const FractionInput = ({
|
|
|
17437
17490
|
}) => {
|
|
17438
17491
|
const playSound = useSound();
|
|
17439
17492
|
const locked = useExerciseLocked();
|
|
17493
|
+
const hasKeypad = useHasKeypad();
|
|
17440
17494
|
const numKey = getFractionKey(widgetId, "num");
|
|
17441
17495
|
const denKey = getFractionKey(widgetId, "den");
|
|
17442
17496
|
const numValue = values[numKey] ?? "";
|
|
@@ -17490,6 +17544,7 @@ const FractionInput = ({
|
|
|
17490
17544
|
},
|
|
17491
17545
|
onFocus: () => onFocus?.(subKey),
|
|
17492
17546
|
readOnly: locked,
|
|
17547
|
+
inputMode: hasKeypad ? "none" : void 0,
|
|
17493
17548
|
maxLength: placeholder ? Math.max(20, placeholder.length) : 4,
|
|
17494
17549
|
size: placeholder ? placeholder.length : void 0,
|
|
17495
17550
|
"aria-label": label,
|
|
@@ -19700,7 +19755,7 @@ function isGeometryWidget(widget) {
|
|
|
19700
19755
|
return widget.type === JSON_TYPES.GEOMETRY;
|
|
19701
19756
|
}
|
|
19702
19757
|
const GeometryWidget = React.lazy(async () => ({
|
|
19703
|
-
default: (await import("./GeometryWidget-
|
|
19758
|
+
default: (await import("./GeometryWidget-B-EQ53jz.mjs")).GeometryWidget
|
|
19704
19759
|
}));
|
|
19705
19760
|
const WidgetRenderer = ({
|
|
19706
19761
|
widget,
|
|
@@ -22002,7 +22057,7 @@ const LayoutRenderer = ({
|
|
|
22002
22057
|
return null;
|
|
22003
22058
|
}
|
|
22004
22059
|
};
|
|
22005
|
-
function
|
|
22060
|
+
function ChameleonHand({ className }) {
|
|
22006
22061
|
const id = useId();
|
|
22007
22062
|
const shadowId = `${id}-shadow`;
|
|
22008
22063
|
const maskId = `${id}-mask`;
|
|
@@ -22029,26 +22084,90 @@ function LionHand({ className }) {
|
|
|
22029
22084
|
y: "19",
|
|
22030
22085
|
width: "49",
|
|
22031
22086
|
height: "42",
|
|
22032
|
-
children: /* @__PURE__ */ jsx(
|
|
22087
|
+
children: /* @__PURE__ */ jsx(
|
|
22088
|
+
"path",
|
|
22089
|
+
{
|
|
22090
|
+
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",
|
|
22091
|
+
fill: "#B7DB4E"
|
|
22092
|
+
}
|
|
22093
|
+
)
|
|
22033
22094
|
}
|
|
22034
22095
|
),
|
|
22035
22096
|
/* @__PURE__ */ jsxs("g", { mask: `url(#${maskId})`, children: [
|
|
22036
|
-
/* @__PURE__ */ jsx(
|
|
22037
|
-
|
|
22038
|
-
|
|
22039
|
-
|
|
22097
|
+
/* @__PURE__ */ jsx(
|
|
22098
|
+
"path",
|
|
22099
|
+
{
|
|
22100
|
+
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",
|
|
22101
|
+
fill: "#B7DB4E"
|
|
22102
|
+
}
|
|
22103
|
+
),
|
|
22104
|
+
/* @__PURE__ */ jsx(
|
|
22105
|
+
"path",
|
|
22106
|
+
{
|
|
22107
|
+
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",
|
|
22108
|
+
fill: "#9CAF63"
|
|
22109
|
+
}
|
|
22110
|
+
),
|
|
22111
|
+
/* @__PURE__ */ jsx(
|
|
22112
|
+
"path",
|
|
22113
|
+
{
|
|
22114
|
+
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",
|
|
22115
|
+
fill: "#9CAF63"
|
|
22116
|
+
}
|
|
22117
|
+
)
|
|
22040
22118
|
] })
|
|
22041
22119
|
] }),
|
|
22042
|
-
/* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsxs(
|
|
22043
|
-
|
|
22044
|
-
|
|
22045
|
-
|
|
22046
|
-
|
|
22047
|
-
|
|
22048
|
-
|
|
22049
|
-
|
|
22050
|
-
|
|
22051
|
-
|
|
22120
|
+
/* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsxs(
|
|
22121
|
+
"filter",
|
|
22122
|
+
{
|
|
22123
|
+
id: shadowId,
|
|
22124
|
+
x: "-1.99995",
|
|
22125
|
+
y: "0",
|
|
22126
|
+
width: "78.3658",
|
|
22127
|
+
height: "81.8518",
|
|
22128
|
+
filterUnits: "userSpaceOnUse",
|
|
22129
|
+
colorInterpolationFilters: "sRGB",
|
|
22130
|
+
children: [
|
|
22131
|
+
/* @__PURE__ */ jsx("feFlood", { floodOpacity: "0", result: "BackgroundImageFix" }),
|
|
22132
|
+
/* @__PURE__ */ jsx(
|
|
22133
|
+
"feColorMatrix",
|
|
22134
|
+
{
|
|
22135
|
+
in: "SourceAlpha",
|
|
22136
|
+
type: "matrix",
|
|
22137
|
+
values: "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0",
|
|
22138
|
+
result: "hardAlpha"
|
|
22139
|
+
}
|
|
22140
|
+
),
|
|
22141
|
+
/* @__PURE__ */ jsx("feOffset", { dy: "2" }),
|
|
22142
|
+
/* @__PURE__ */ jsx("feGaussianBlur", { stdDeviation: "1" }),
|
|
22143
|
+
/* @__PURE__ */ jsx("feComposite", { in2: "hardAlpha", operator: "out" }),
|
|
22144
|
+
/* @__PURE__ */ jsx(
|
|
22145
|
+
"feColorMatrix",
|
|
22146
|
+
{
|
|
22147
|
+
type: "matrix",
|
|
22148
|
+
values: "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"
|
|
22149
|
+
}
|
|
22150
|
+
),
|
|
22151
|
+
/* @__PURE__ */ jsx(
|
|
22152
|
+
"feBlend",
|
|
22153
|
+
{
|
|
22154
|
+
mode: "normal",
|
|
22155
|
+
in2: "BackgroundImageFix",
|
|
22156
|
+
result: "effect1_dropShadow"
|
|
22157
|
+
}
|
|
22158
|
+
),
|
|
22159
|
+
/* @__PURE__ */ jsx(
|
|
22160
|
+
"feBlend",
|
|
22161
|
+
{
|
|
22162
|
+
mode: "normal",
|
|
22163
|
+
in: "SourceGraphic",
|
|
22164
|
+
in2: "effect1_dropShadow",
|
|
22165
|
+
result: "shape"
|
|
22166
|
+
}
|
|
22167
|
+
)
|
|
22168
|
+
]
|
|
22169
|
+
}
|
|
22170
|
+
) })
|
|
22052
22171
|
]
|
|
22053
22172
|
}
|
|
22054
22173
|
);
|
|
@@ -22110,6 +22229,8 @@ const TutorialOverlay = ({ tutorial, root, json, options, onClose }) => {
|
|
|
22110
22229
|
tick();
|
|
22111
22230
|
return () => cancelAnimationFrame(raf);
|
|
22112
22231
|
}, [step]);
|
|
22232
|
+
const showBubble = options.showBubble !== false;
|
|
22233
|
+
const autoAdvance = options.autoAdvance === true || !showBubble;
|
|
22113
22234
|
const [tapIndex, setTapIndex] = useState(0);
|
|
22114
22235
|
const [tapCount, setTapCount] = useState(0);
|
|
22115
22236
|
const [shownStep, setShownStep] = useState(stepIndex);
|
|
@@ -22119,11 +22240,13 @@ const TutorialOverlay = ({ tutorial, root, json, options, onClose }) => {
|
|
|
22119
22240
|
setTapCount(0);
|
|
22120
22241
|
}
|
|
22121
22242
|
useEffect(() => {
|
|
22243
|
+
const targets = step.taps(ctxRef.current);
|
|
22244
|
+
if (autoAdvance && tapIndex >= targets.length) return;
|
|
22122
22245
|
let release;
|
|
22123
22246
|
const press = setTimeout(() => {
|
|
22124
22247
|
setTapCount((n) => n + 1);
|
|
22125
|
-
const
|
|
22126
|
-
const el =
|
|
22248
|
+
const targets2 = step.taps(ctxRef.current);
|
|
22249
|
+
const el = targets2[tapIndex % Math.max(1, targets2.length)];
|
|
22127
22250
|
if (el instanceof Element) {
|
|
22128
22251
|
el.classList.add("met-tutorial-pressed");
|
|
22129
22252
|
release = setTimeout(() => el.classList.remove("met-tutorial-pressed"), PRESS_HOLD_MS);
|
|
@@ -22136,23 +22259,44 @@ const TutorialOverlay = ({ tutorial, root, json, options, onClose }) => {
|
|
|
22136
22259
|
if (release) clearTimeout(release);
|
|
22137
22260
|
root.querySelectorAll(".met-tutorial-pressed").forEach((el) => el.classList.remove("met-tutorial-pressed"));
|
|
22138
22261
|
};
|
|
22139
|
-
}, [step, tapIndex, root]);
|
|
22262
|
+
}, [step, tapIndex, root, autoAdvance]);
|
|
22140
22263
|
const text = options.texts?.[step.id] ?? step.text;
|
|
22141
22264
|
const textRef = useRef(text);
|
|
22142
22265
|
textRef.current = text;
|
|
22266
|
+
const onCloseRef = useRef(onClose);
|
|
22267
|
+
onCloseRef.current = onClose;
|
|
22268
|
+
const isLast = stepIndex === steps.length - 1;
|
|
22269
|
+
const [completedStep, setCompletedStep] = useState(null);
|
|
22143
22270
|
useEffect(() => {
|
|
22144
22271
|
optionsRef.current.onStep?.(stepIndex, step.id);
|
|
22145
|
-
optionsRef.current.onSpeak?.(step.id, textRef.current)
|
|
22146
|
-
|
|
22147
|
-
|
|
22272
|
+
const speech = Promise.resolve(optionsRef.current.onSpeak?.(step.id, textRef.current)).catch(() => {
|
|
22273
|
+
});
|
|
22274
|
+
if (!autoAdvance) return;
|
|
22275
|
+
const duration = Math.max(2, step.taps(ctxRef.current).length) * CYCLE_MS;
|
|
22276
|
+
let cancelled = false;
|
|
22277
|
+
let timer;
|
|
22278
|
+
const animation = new Promise((resolve2) => {
|
|
22279
|
+
timer = setTimeout(resolve2, duration);
|
|
22280
|
+
});
|
|
22281
|
+
void Promise.all([speech, animation]).then(() => {
|
|
22282
|
+
if (cancelled) return;
|
|
22283
|
+
if (isLast) {
|
|
22284
|
+
if (showBubble) setCompletedStep(stepIndex);
|
|
22285
|
+
else onCloseRef.current(true);
|
|
22286
|
+
} else setStepIndex((i) => i + 1);
|
|
22287
|
+
});
|
|
22288
|
+
return () => {
|
|
22289
|
+
cancelled = true;
|
|
22290
|
+
clearTimeout(timer);
|
|
22291
|
+
};
|
|
22292
|
+
}, [stepIndex, step, autoAdvance, isLast, showBubble]);
|
|
22293
|
+
const showNext = !autoAdvance || isLast && completedStep === stepIndex;
|
|
22148
22294
|
const next = () => {
|
|
22149
22295
|
if (isLast) onClose(true);
|
|
22150
22296
|
else setStepIndex((i) => i + 1);
|
|
22151
22297
|
};
|
|
22152
22298
|
const overlayRef = useRef(null);
|
|
22153
22299
|
const nextRef = useRef(null);
|
|
22154
|
-
const onCloseRef = useRef(onClose);
|
|
22155
|
-
onCloseRef.current = onClose;
|
|
22156
22300
|
useEffect(() => {
|
|
22157
22301
|
const previous = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
22158
22302
|
previous?.blur();
|
|
@@ -22185,15 +22329,15 @@ const TutorialOverlay = ({ tutorial, root, json, options, onClose }) => {
|
|
|
22185
22329
|
}, []);
|
|
22186
22330
|
const bubbleShown = layout !== null;
|
|
22187
22331
|
useEffect(() => {
|
|
22188
|
-
nextRef.current?.focus({ focusVisible: false });
|
|
22189
|
-
}, [stepIndex, bubbleShown]);
|
|
22332
|
+
(nextRef.current ?? overlayRef.current)?.focus({ focusVisible: false });
|
|
22333
|
+
}, [stepIndex, bubbleShown, showNext]);
|
|
22190
22334
|
const maskId = `met-tutorial-mask-${useId()}`;
|
|
22191
22335
|
if (typeof document === "undefined") return null;
|
|
22192
22336
|
const holes = (layout?.spotlight ?? []).filter((r) => r !== null);
|
|
22193
|
-
const tapTarget = layout?.taps.length ? layout.taps[tapIndex % layout.taps.length] ?? null : null;
|
|
22337
|
+
const tapTarget = layout?.taps.length && (!autoAdvance || tapIndex < layout.taps.length) ? layout.taps[tapIndex % layout.taps.length] ?? null : null;
|
|
22194
22338
|
const Scene = step.Scene;
|
|
22195
22339
|
return createPortal(
|
|
22196
|
-
/* @__PURE__ */ jsxs("div", { ref: overlayRef, className: "met-tutorial", role: "dialog", "aria-modal": "true", "aria-label": text, children: [
|
|
22340
|
+
/* @__PURE__ */ jsxs("div", { ref: overlayRef, className: "met-tutorial", tabIndex: -1, role: "dialog", "aria-modal": "true", "aria-label": text, children: [
|
|
22197
22341
|
/* @__PURE__ */ jsxs("svg", { className: "met-tutorial-dim", "aria-hidden": "true", children: [
|
|
22198
22342
|
/* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsxs("mask", { id: maskId, children: [
|
|
22199
22343
|
/* @__PURE__ */ jsx("rect", { width: "100%", height: "100%", fill: "white" }),
|
|
@@ -22228,7 +22372,7 @@ const TutorialOverlay = ({ tutorial, root, json, options, onClose }) => {
|
|
|
22228
22372
|
)),
|
|
22229
22373
|
Scene && layout && /* @__PURE__ */ jsx(Scene, { ctx, spotlight: layout.spotlight, taps: layout.taps, tapCount }),
|
|
22230
22374
|
tapTarget && /* @__PURE__ */ jsx(Hand, { target: tapTarget, tapKey: `${stepIndex}-${tapIndex}` }),
|
|
22231
|
-
layout && /* @__PURE__ */ jsx(
|
|
22375
|
+
layout && showBubble && /* @__PURE__ */ jsx(
|
|
22232
22376
|
Bubble,
|
|
22233
22377
|
{
|
|
22234
22378
|
layout,
|
|
@@ -22236,7 +22380,8 @@ const TutorialOverlay = ({ tutorial, root, json, options, onClose }) => {
|
|
|
22236
22380
|
stepIndex,
|
|
22237
22381
|
stepCount: steps.length,
|
|
22238
22382
|
nextLabel: options.nextLabel ?? DEFAULT_NEXT_LABEL,
|
|
22239
|
-
|
|
22383
|
+
showNext,
|
|
22384
|
+
onSpeak: !autoAdvance && options.onSpeak && (options.canSpeak?.(step.id) ?? true) ? () => optionsRef.current.onSpeak?.(step.id, textRef.current) : void 0,
|
|
22240
22385
|
onNext: next,
|
|
22241
22386
|
nextRef
|
|
22242
22387
|
}
|
|
@@ -22264,12 +22409,12 @@ const Hand = ({ target, tapKey }) => {
|
|
|
22264
22409
|
style: { left: TUTORIAL_HAND_TIP.x * HAND_W, top: TUTORIAL_HAND_TIP.y * HAND_H }
|
|
22265
22410
|
}
|
|
22266
22411
|
),
|
|
22267
|
-
/* @__PURE__ */ jsx(
|
|
22412
|
+
/* @__PURE__ */ jsx(ChameleonHand, {})
|
|
22268
22413
|
] }, tapKey)
|
|
22269
22414
|
}
|
|
22270
22415
|
);
|
|
22271
22416
|
};
|
|
22272
|
-
const Bubble = ({ layout, text, stepIndex, stepCount, nextLabel, onSpeak, onNext, nextRef }) => {
|
|
22417
|
+
const Bubble = ({ layout, text, stepIndex, stepCount, nextLabel, showNext, onSpeak, onNext, nextRef }) => {
|
|
22273
22418
|
const { anchor, root, vw, vh } = layout;
|
|
22274
22419
|
const clampLeft = (x) => Math.min(Math.max(EDGE, x), Math.max(EDGE, vw - BUBBLE_W - EDGE));
|
|
22275
22420
|
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 };
|
|
@@ -22280,7 +22425,7 @@ const Bubble = ({ layout, text, stepIndex, stepCount, nextLabel, onSpeak, onNext
|
|
|
22280
22425
|
/* @__PURE__ */ jsx("path", { d: "M4 9v6h4l5 4V5L8 9H4z", fill: "currentColor" }),
|
|
22281
22426
|
/* @__PURE__ */ 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" })
|
|
22282
22427
|
] }) }),
|
|
22283
|
-
/* @__PURE__ */ jsx("button", { ref: nextRef, type: "button", className: "met-tutorial-next", onClick: onNext, children: nextLabel })
|
|
22428
|
+
showNext && /* @__PURE__ */ jsx("button", { ref: nextRef, type: "button", className: "met-tutorial-next", onClick: onNext, children: nextLabel })
|
|
22284
22429
|
] }),
|
|
22285
22430
|
stepCount > 1 && /* @__PURE__ */ jsx("div", { className: "met-tutorial-dots", "aria-hidden": "true", children: Array.from({ length: stepCount }, (_, i) => /* @__PURE__ */ jsx("span", { className: i === stepIndex ? "is-current" : i < stepIndex ? "is-done" : "" }, i)) })
|
|
22286
22431
|
] });
|
|
@@ -22300,7 +22445,16 @@ function answerBox(ctx) {
|
|
|
22300
22445
|
const inputs = getTextInputs(ctx.root);
|
|
22301
22446
|
return inputs.find(canTakeInput) ?? inputs[0] ?? null;
|
|
22302
22447
|
}
|
|
22448
|
+
function answerDigits(ctx) {
|
|
22449
|
+
const id = answerBox(ctx)?.id.replace(/-input$/, "");
|
|
22450
|
+
const expected = id ? ctx.json.validation?.answers?.[id] : void 0;
|
|
22451
|
+
if (typeof expected !== "number" && typeof expected !== "string") return null;
|
|
22452
|
+
const value = String(expected);
|
|
22453
|
+
return /^\d+$/.test(value) ? [...value] : null;
|
|
22454
|
+
}
|
|
22303
22455
|
function answerSlots(ctx) {
|
|
22456
|
+
const digits = answerDigits(ctx);
|
|
22457
|
+
if (digits) return digits.length;
|
|
22304
22458
|
const id = answerBox(ctx)?.id.replace(/-input$/, "");
|
|
22305
22459
|
const max = id ? getMaxInputLength(ctx.json, id) : void 0;
|
|
22306
22460
|
return Math.min(MAX_SLOTS, Math.max(1, max ?? DEFAULT_SLOTS));
|
|
@@ -22316,13 +22470,14 @@ function checkTarget(ctx) {
|
|
|
22316
22470
|
STAND_IN_CHECK_SIZE
|
|
22317
22471
|
);
|
|
22318
22472
|
}
|
|
22319
|
-
const demoKeys = (ctx) => DEMO_KEYS.map((k) => key(ctx, k));
|
|
22320
|
-
const StandInFill = ({ box, slots, filled }) => /* @__PURE__ */ jsx(
|
|
22473
|
+
const demoKeys = (ctx) => (answerDigits(ctx) ?? DEMO_KEYS).map((k) => key(ctx, k));
|
|
22474
|
+
const StandInFill = ({ box, slots, filled, digits }) => /* @__PURE__ */ jsx(
|
|
22321
22475
|
"div",
|
|
22322
22476
|
{
|
|
22323
22477
|
className: "met-tutorial-fill",
|
|
22478
|
+
dir: "ltr",
|
|
22324
22479
|
style: { left: box.x, top: box.y, width: box.width, height: box.height },
|
|
22325
|
-
children: Array.from({ length: slots }, (_, i) => /* @__PURE__ */ jsx("span", { className: i < filled ? "met-tutorial-fill-dot" : "met-tutorial-fill-slot", children: i < filled ? "●" : "_" }, i))
|
|
22480
|
+
children: Array.from({ length: slots }, (_, i) => /* @__PURE__ */ jsx("span", { className: i < filled ? "met-tutorial-fill-dot" : "met-tutorial-fill-slot", children: i < filled ? digits?.[i] ?? "●" : "_" }, i))
|
|
22326
22481
|
}
|
|
22327
22482
|
);
|
|
22328
22483
|
const center = (r) => ({ x: r.x + r.width / 2, y: r.y + r.height / 2 });
|
|
@@ -22330,7 +22485,8 @@ const HopScene = ({ ctx, spotlight, taps, tapCount }) => {
|
|
|
22330
22485
|
const box = spotlight[1];
|
|
22331
22486
|
if (!box) return null;
|
|
22332
22487
|
const slots = answerSlots(ctx);
|
|
22333
|
-
const
|
|
22488
|
+
const digits = answerDigits(ctx);
|
|
22489
|
+
const filled = digits ? Math.min(tapCount, slots) : tapCount === 0 ? 0 : (tapCount - 1) % slots + 1;
|
|
22334
22490
|
const from = tapCount > 0 ? taps[(tapCount - 1) % taps.length] : null;
|
|
22335
22491
|
let arc = null;
|
|
22336
22492
|
if (from) {
|
|
@@ -22341,15 +22497,23 @@ const HopScene = ({ ctx, spotlight, taps, tapCount }) => {
|
|
|
22341
22497
|
}
|
|
22342
22498
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
22343
22499
|
arc && /* @__PURE__ */ jsx("svg", { className: "met-tutorial-arc", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: arc }) }),
|
|
22344
|
-
arc && /* @__PURE__ */ jsx(
|
|
22345
|
-
|
|
22500
|
+
arc && /* @__PURE__ */ jsx(
|
|
22501
|
+
"div",
|
|
22502
|
+
{
|
|
22503
|
+
className: `met-tutorial-hop${digits ? " met-tutorial-hop--digit" : ""}`,
|
|
22504
|
+
style: { offsetPath: `path('${arc}')` },
|
|
22505
|
+
children: digits?.[(tapCount - 1) % digits.length]
|
|
22506
|
+
},
|
|
22507
|
+
tapCount
|
|
22508
|
+
),
|
|
22509
|
+
/* @__PURE__ */ jsx(StandInFill, { box, slots, filled, digits })
|
|
22346
22510
|
] });
|
|
22347
22511
|
};
|
|
22348
22512
|
const CheckScene = ({ ctx, spotlight }) => {
|
|
22349
22513
|
const box = spotlight[0];
|
|
22350
22514
|
const check = spotlight[1];
|
|
22351
22515
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
22352
|
-
box && /* @__PURE__ */ jsx(StandInFill, { box, slots: answerSlots(ctx), filled: answerSlots(ctx) }),
|
|
22516
|
+
box && /* @__PURE__ */ jsx(StandInFill, { box, slots: answerSlots(ctx), filled: answerSlots(ctx), digits: answerDigits(ctx) }),
|
|
22353
22517
|
check && !ctx.submitTarget && /* @__PURE__ */ jsx(
|
|
22354
22518
|
"div",
|
|
22355
22519
|
{
|
|
@@ -22390,6 +22554,33 @@ const WIDGET_TUTORIALS = [keypadTutorial];
|
|
|
22390
22554
|
function findWidgetTutorial(json) {
|
|
22391
22555
|
return WIDGET_TUTORIALS.find((t) => t.appliesTo(json)) ?? null;
|
|
22392
22556
|
}
|
|
22557
|
+
function getAnswerLengthCompletion(json, answerable, values, touched) {
|
|
22558
|
+
const fields = [];
|
|
22559
|
+
for (const widgetId of answerable) {
|
|
22560
|
+
const widget = json.widgets[widgetId];
|
|
22561
|
+
const expected = json.validation?.answers?.[widgetId];
|
|
22562
|
+
if (widget?.type === JSON_TYPES.INLINE_INPUT && !widget.disabled) {
|
|
22563
|
+
fields.push({ id: widgetId, widgetId, expected });
|
|
22564
|
+
} else if (widget?.type === JSON_TYPES.FRACTION) {
|
|
22565
|
+
const parts = typeof expected === "string" ? expected.split("/") : [];
|
|
22566
|
+
const validParts = parts.length === 2;
|
|
22567
|
+
if (widget.numerator === null) {
|
|
22568
|
+
fields.push({ id: getFractionKey(widgetId, "num"), widgetId, expected: validParts ? parts[0] : void 0 });
|
|
22569
|
+
}
|
|
22570
|
+
if (widget.denominator === null) {
|
|
22571
|
+
fields.push({ id: getFractionKey(widgetId, "den"), widgetId, expected: validParts ? parts[1] : void 0 });
|
|
22572
|
+
}
|
|
22573
|
+
}
|
|
22574
|
+
}
|
|
22575
|
+
const answerLengthMatchedIds = fields.filter(({ id, widgetId, expected }) => {
|
|
22576
|
+
const value = values[id];
|
|
22577
|
+
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;
|
|
22578
|
+
}).map(({ id }) => id);
|
|
22579
|
+
return {
|
|
22580
|
+
isAnswerLengthMatched: fields.length > 0 && answerLengthMatchedIds.length === fields.length,
|
|
22581
|
+
answerLengthMatchedIds
|
|
22582
|
+
};
|
|
22583
|
+
}
|
|
22393
22584
|
const TOOL_ASPECT_RATIO = {
|
|
22394
22585
|
ruler: RULER_ASPECT_RATIO,
|
|
22395
22586
|
protractor: PROTRACTOR_ASPECT_RATIO,
|
|
@@ -22421,6 +22612,8 @@ function buildInitialValues(json) {
|
|
|
22421
22612
|
initial[id] = String(widget.value);
|
|
22422
22613
|
} else if (widget.type === JSON_TYPES.GEOMETRY) {
|
|
22423
22614
|
initial[id] = serializeGeometryState(createEmptyGeometryAttempt());
|
|
22615
|
+
} else if (widget.type === JSON_TYPES.INLINE_INPUT && widget.prefill !== void 0) {
|
|
22616
|
+
initial[id] = widget.prefill;
|
|
22424
22617
|
}
|
|
22425
22618
|
}
|
|
22426
22619
|
return initial;
|
|
@@ -22504,7 +22697,7 @@ function buildRevealedDropSources(json, revealedValues) {
|
|
|
22504
22697
|
return sources;
|
|
22505
22698
|
}
|
|
22506
22699
|
const ExerciseRenderer = forwardRef(({
|
|
22507
|
-
json,
|
|
22700
|
+
json: suppliedJson,
|
|
22508
22701
|
onSubmit,
|
|
22509
22702
|
onChange,
|
|
22510
22703
|
showSubmitButton = false,
|
|
@@ -22513,6 +22706,7 @@ const ExerciseRenderer = forwardRef(({
|
|
|
22513
22706
|
sounds,
|
|
22514
22707
|
theme
|
|
22515
22708
|
}, ref) => {
|
|
22709
|
+
const json = useMemo(() => withDisabledPrefilledInputs(suppliedJson), [suppliedJson]);
|
|
22516
22710
|
const initialValuesSignature = useMemo(() => buildInitialValuesSignature(json), [json]);
|
|
22517
22711
|
const [values, setValues] = useState(() => buildInitialValues(json));
|
|
22518
22712
|
const valuesRef = useRef(values);
|
|
@@ -22598,7 +22792,8 @@ const ExerciseRenderer = forwardRef(({
|
|
|
22598
22792
|
missing,
|
|
22599
22793
|
filledIds,
|
|
22600
22794
|
isComplete: answerable.length > 0 && missing.length === 0,
|
|
22601
|
-
isAnyFilled: filledIds.length > 0
|
|
22795
|
+
isAnyFilled: filledIds.length > 0,
|
|
22796
|
+
...getAnswerLengthCompletion(json, answerable, valuesRef.current, touchedRef.current)
|
|
22602
22797
|
};
|
|
22603
22798
|
},
|
|
22604
22799
|
reset: () => {
|
|
@@ -22640,7 +22835,7 @@ const ExerciseRenderer = forwardRef(({
|
|
|
22640
22835
|
const container = rootRef.current ?? document.querySelector(".math-exercise-renderer");
|
|
22641
22836
|
if (!container) return;
|
|
22642
22837
|
const candidates = getTextInputs(container);
|
|
22643
|
-
const target = candidates.find(canTakeInput) ?? (locked ? void 0 : candidates
|
|
22838
|
+
const target = candidates.find(canTakeInput) ?? (locked ? void 0 : candidates.find(canAdvanceTo));
|
|
22644
22839
|
target?.focus();
|
|
22645
22840
|
},
|
|
22646
22841
|
hasTutorial: () => tutorial !== null,
|
|
@@ -22812,7 +23007,7 @@ const ExerciseRenderer = forwardRef(({
|
|
|
22812
23007
|
if (setSquareWidget) add2("set-square-1", "set-square", Math.round(getSize(setSquareWidget) * 160));
|
|
22813
23008
|
return { tools: list, toolsMinHeight: maxH };
|
|
22814
23009
|
}, [rulerWidget, protractorWidget, setSquareWidget]);
|
|
22815
|
-
return /* @__PURE__ */ jsx(SoundPlayerProvider, { play: playSound, children: /* @__PURE__ */ jsx(ExerciseLockProvider, { locked, children: /* @__PURE__ */ jsx(ExerciseDndProvider, { droppedSources, exerciseKey: loadedValuesSignature, children: /* @__PURE__ */ jsxs("div", { ref: rootRef, className: `math-exercise-renderer ${revealed ? "revealed" : ""} ${locked ? "locked" : ""} ${className}`, style: themeStyle, children: [
|
|
23010
|
+
return /* @__PURE__ */ jsx(SoundPlayerProvider, { play: playSound, children: /* @__PURE__ */ jsx(ExerciseLockProvider, { locked, children: /* @__PURE__ */ jsx(ExerciseDndProvider, { droppedSources, exerciseKey: loadedValuesSignature, children: /* @__PURE__ */ jsx(KeypadContext.Provider, { value: Boolean(numericKeypadWidget), children: /* @__PURE__ */ jsx(DragBankContext.Provider, { value: json.widgets[DRAG_OPTIONS_WIDGET_ID] !== void 0, children: /* @__PURE__ */ jsxs("div", { ref: rootRef, className: `math-exercise-renderer ${revealed ? "revealed" : ""} ${locked ? "locked" : ""} ${className}`, style: themeStyle, children: [
|
|
22816
23011
|
/* @__PURE__ */ jsxs(
|
|
22817
23012
|
"div",
|
|
22818
23013
|
{
|
|
@@ -22888,9 +23083,10 @@ const ExerciseRenderer = forwardRef(({
|
|
|
22888
23083
|
onChange: () => {
|
|
22889
23084
|
},
|
|
22890
23085
|
onKeypadChange: (newValue) => {
|
|
22891
|
-
if (focusedInputId)
|
|
22892
|
-
|
|
22893
|
-
|
|
23086
|
+
if (!focusedInputId) return;
|
|
23087
|
+
const maxLength = getMaxInputLength(json, focusedInputId);
|
|
23088
|
+
if (maxLength !== void 0 && newValue.length > maxLength) return;
|
|
23089
|
+
handleChange(focusedInputId, newValue, false);
|
|
22894
23090
|
}
|
|
22895
23091
|
}
|
|
22896
23092
|
) }),
|
|
@@ -22918,12 +23114,13 @@ const ExerciseRenderer = forwardRef(({
|
|
|
22918
23114
|
options: tutorialRun.options,
|
|
22919
23115
|
onClose: (finished) => {
|
|
22920
23116
|
setTutorialRun(null);
|
|
23117
|
+
tutorialRun.options.onClose?.();
|
|
22921
23118
|
if (finished) tutorialRun.options.onDone?.();
|
|
22922
23119
|
}
|
|
22923
23120
|
},
|
|
22924
23121
|
tutorialRun.run
|
|
22925
23122
|
)
|
|
22926
|
-
] }) }) }) });
|
|
23123
|
+
] }) }) }) }) }) });
|
|
22927
23124
|
});
|
|
22928
23125
|
ExerciseRenderer.displayName = "ExerciseRenderer";
|
|
22929
23126
|
function renderExercise(options) {
|
|
@@ -23269,4 +23466,4 @@ export {
|
|
|
23269
23466
|
extractVariables as y,
|
|
23270
23467
|
validateBooleanExpression as z
|
|
23271
23468
|
};
|
|
23272
|
-
//# sourceMappingURL=index-
|
|
23469
|
+
//# sourceMappingURL=index-Daj5xH0X.mjs.map
|