@mathscan/math-exercise-engine 1.2.13 → 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 +4 -3
- package/dist/{GeometryWidget-h2yNZsas.mjs → GeometryWidget-B-EQ53jz.mjs} +2 -2
- package/dist/{GeometryWidget-h2yNZsas.mjs.map → GeometryWidget-B-EQ53jz.mjs.map} +1 -1
- package/dist/{GeometryWidget-CMJTNSAG.js → GeometryWidget-D6_DlxhZ.js} +2 -2
- package/dist/{GeometryWidget-CMJTNSAG.js.map → GeometryWidget-D6_DlxhZ.js.map} +1 -1
- package/dist/{index-CAvdtiDQ.mjs → index-Daj5xH0X.mjs} +252 -113
- package/dist/{index-CAvdtiDQ.mjs.map → index-Daj5xH0X.mjs.map} +1 -1
- package/dist/{index-CbKXPRsS.js → index-gCvC2wRR.js} +252 -113
- package/dist/{index-CbKXPRsS.js.map → index-gCvC2wRR.js.map} +1 -1
- 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
|
@@ -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
|
-
...
|
|
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
|
-
...
|
|
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,
|
|
@@ -17171,6 +17208,8 @@ function findNextInput(container, currentKey) {
|
|
|
17171
17208
|
}
|
|
17172
17209
|
const KeypadContext = React.createContext(false);
|
|
17173
17210
|
const useHasKeypad = () => React.useContext(KeypadContext);
|
|
17211
|
+
const DragBankContext = React.createContext(false);
|
|
17212
|
+
const useHasDragBank = () => React.useContext(DragBankContext);
|
|
17174
17213
|
const InlineInput = ({
|
|
17175
17214
|
widgetId,
|
|
17176
17215
|
value,
|
|
@@ -17188,6 +17227,7 @@ const InlineInput = ({
|
|
|
17188
17227
|
const playSound = useSound();
|
|
17189
17228
|
const locked = useExerciseLocked();
|
|
17190
17229
|
const hasKeypad = useHasKeypad();
|
|
17230
|
+
const hasDragBank = useHasDragBank();
|
|
17191
17231
|
const handleChange = React.useCallback((newValue, fromDrop = false, sourceOptionId) => {
|
|
17192
17232
|
onChange(widgetId, newValue, fromDrop, sourceOptionId);
|
|
17193
17233
|
}, [onChange, widgetId]);
|
|
@@ -17198,6 +17238,7 @@ const InlineInput = ({
|
|
|
17198
17238
|
const dynamicWidth = `${(width ?? expectedLength ?? 5) * 0.8 + 1}em`;
|
|
17199
17239
|
const statusClass = validationStatus ? ` widget--${validationStatus}` : "";
|
|
17200
17240
|
const disabledClass = disabled ? " inline-input--disabled" : "";
|
|
17241
|
+
const isFixedChip = hasDragBank && disabled && value !== "";
|
|
17201
17242
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
17202
17243
|
Droppable,
|
|
17203
17244
|
{
|
|
@@ -17206,7 +17247,10 @@ const InlineInput = ({
|
|
|
17206
17247
|
onDrop: handleDrop,
|
|
17207
17248
|
disabled: locked || disabled,
|
|
17208
17249
|
className: "inline-widget inline-input-droppable",
|
|
17209
|
-
children:
|
|
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 !== "" ? (
|
|
17210
17254
|
// Show as chip only when value came from drag-drop
|
|
17211
17255
|
/* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
17212
17256
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -17263,11 +17307,13 @@ const DragOptionChip = ({
|
|
|
17263
17307
|
onOptionClick,
|
|
17264
17308
|
className = ""
|
|
17265
17309
|
}) => {
|
|
17266
|
-
const { usedOptionIds, hasDraggedOption, playHintSound } = useExerciseDnd();
|
|
17310
|
+
const { activeItem, usedOptionIds, hasDraggedOption, playHintSound } = useExerciseDnd();
|
|
17267
17311
|
const locked = useExerciseLocked();
|
|
17268
17312
|
const isUsed = usedOptionIds.has(optionId);
|
|
17313
|
+
const isPickedUp = activeItem?.type === "drag-option" && activeItem.id === optionId;
|
|
17269
17314
|
const { jiggling: hinting, onAnimationEnd: onHintEnd } = useJiggle(hasDraggedOption);
|
|
17270
|
-
|
|
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(
|
|
17271
17317
|
Draggable,
|
|
17272
17318
|
{
|
|
17273
17319
|
id: optionId,
|
|
@@ -17275,7 +17321,7 @@ const DragOptionChip = ({
|
|
|
17275
17321
|
data: { value, label: value },
|
|
17276
17322
|
disabled: locked,
|
|
17277
17323
|
className: "drag-option-wrapper",
|
|
17278
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
17324
|
+
children: isPickedUp ? emptySlot : /* @__PURE__ */ jsxRuntime.jsx(
|
|
17279
17325
|
"button",
|
|
17280
17326
|
{
|
|
17281
17327
|
className: hinting && !locked ? "drag-option drag-option--hint" : "drag-option",
|
|
@@ -19710,7 +19756,7 @@ function isGeometryWidget(widget) {
|
|
|
19710
19756
|
return widget.type === JSON_TYPES.GEOMETRY;
|
|
19711
19757
|
}
|
|
19712
19758
|
const GeometryWidget = React.lazy(async () => ({
|
|
19713
|
-
default: (await Promise.resolve().then(() => require("./GeometryWidget-
|
|
19759
|
+
default: (await Promise.resolve().then(() => require("./GeometryWidget-D6_DlxhZ.js"))).GeometryWidget
|
|
19714
19760
|
}));
|
|
19715
19761
|
const WidgetRenderer = ({
|
|
19716
19762
|
widget,
|
|
@@ -22012,7 +22058,7 @@ const LayoutRenderer = ({
|
|
|
22012
22058
|
return null;
|
|
22013
22059
|
}
|
|
22014
22060
|
};
|
|
22015
|
-
function
|
|
22061
|
+
function ChameleonHand({ className }) {
|
|
22016
22062
|
const id = React.useId();
|
|
22017
22063
|
const shadowId = `${id}-shadow`;
|
|
22018
22064
|
const maskId = `${id}-mask`;
|
|
@@ -22039,26 +22085,90 @@ function LionHand({ className }) {
|
|
|
22039
22085
|
y: "19",
|
|
22040
22086
|
width: "49",
|
|
22041
22087
|
height: "42",
|
|
22042
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
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
|
+
)
|
|
22043
22095
|
}
|
|
22044
22096
|
),
|
|
22045
22097
|
/* @__PURE__ */ jsxRuntime.jsxs("g", { mask: `url(#${maskId})`, children: [
|
|
22046
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
22047
|
-
|
|
22048
|
-
|
|
22049
|
-
|
|
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
|
+
)
|
|
22050
22119
|
] })
|
|
22051
22120
|
] }),
|
|
22052
|
-
/* @__PURE__ */ jsxRuntime.jsx("defs", { children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
22053
|
-
|
|
22054
|
-
|
|
22055
|
-
|
|
22056
|
-
|
|
22057
|
-
|
|
22058
|
-
|
|
22059
|
-
|
|
22060
|
-
|
|
22061
|
-
|
|
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
|
+
) })
|
|
22062
22172
|
]
|
|
22063
22173
|
}
|
|
22064
22174
|
);
|
|
@@ -22120,6 +22230,8 @@ const TutorialOverlay = ({ tutorial, root, json, options, onClose }) => {
|
|
|
22120
22230
|
tick();
|
|
22121
22231
|
return () => cancelAnimationFrame(raf);
|
|
22122
22232
|
}, [step]);
|
|
22233
|
+
const showBubble = options.showBubble !== false;
|
|
22234
|
+
const autoAdvance = options.autoAdvance === true || !showBubble;
|
|
22123
22235
|
const [tapIndex, setTapIndex] = React.useState(0);
|
|
22124
22236
|
const [tapCount, setTapCount] = React.useState(0);
|
|
22125
22237
|
const [shownStep, setShownStep] = React.useState(stepIndex);
|
|
@@ -22129,11 +22241,13 @@ const TutorialOverlay = ({ tutorial, root, json, options, onClose }) => {
|
|
|
22129
22241
|
setTapCount(0);
|
|
22130
22242
|
}
|
|
22131
22243
|
React.useEffect(() => {
|
|
22244
|
+
const targets = step.taps(ctxRef.current);
|
|
22245
|
+
if (autoAdvance && tapIndex >= targets.length) return;
|
|
22132
22246
|
let release;
|
|
22133
22247
|
const press = setTimeout(() => {
|
|
22134
22248
|
setTapCount((n) => n + 1);
|
|
22135
|
-
const
|
|
22136
|
-
const el =
|
|
22249
|
+
const targets2 = step.taps(ctxRef.current);
|
|
22250
|
+
const el = targets2[tapIndex % Math.max(1, targets2.length)];
|
|
22137
22251
|
if (el instanceof Element) {
|
|
22138
22252
|
el.classList.add("met-tutorial-pressed");
|
|
22139
22253
|
release = setTimeout(() => el.classList.remove("met-tutorial-pressed"), PRESS_HOLD_MS);
|
|
@@ -22146,23 +22260,44 @@ const TutorialOverlay = ({ tutorial, root, json, options, onClose }) => {
|
|
|
22146
22260
|
if (release) clearTimeout(release);
|
|
22147
22261
|
root.querySelectorAll(".met-tutorial-pressed").forEach((el) => el.classList.remove("met-tutorial-pressed"));
|
|
22148
22262
|
};
|
|
22149
|
-
}, [step, tapIndex, root]);
|
|
22263
|
+
}, [step, tapIndex, root, autoAdvance]);
|
|
22150
22264
|
const text = options.texts?.[step.id] ?? step.text;
|
|
22151
22265
|
const textRef = React.useRef(text);
|
|
22152
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);
|
|
22153
22271
|
React.useEffect(() => {
|
|
22154
22272
|
optionsRef.current.onStep?.(stepIndex, step.id);
|
|
22155
|
-
optionsRef.current.onSpeak?.(step.id, textRef.current)
|
|
22156
|
-
|
|
22157
|
-
|
|
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;
|
|
22158
22295
|
const next = () => {
|
|
22159
22296
|
if (isLast) onClose(true);
|
|
22160
22297
|
else setStepIndex((i) => i + 1);
|
|
22161
22298
|
};
|
|
22162
22299
|
const overlayRef = React.useRef(null);
|
|
22163
22300
|
const nextRef = React.useRef(null);
|
|
22164
|
-
const onCloseRef = React.useRef(onClose);
|
|
22165
|
-
onCloseRef.current = onClose;
|
|
22166
22301
|
React.useEffect(() => {
|
|
22167
22302
|
const previous = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
22168
22303
|
previous?.blur();
|
|
@@ -22195,15 +22330,15 @@ const TutorialOverlay = ({ tutorial, root, json, options, onClose }) => {
|
|
|
22195
22330
|
}, []);
|
|
22196
22331
|
const bubbleShown = layout !== null;
|
|
22197
22332
|
React.useEffect(() => {
|
|
22198
|
-
nextRef.current?.focus({ focusVisible: false });
|
|
22199
|
-
}, [stepIndex, bubbleShown]);
|
|
22333
|
+
(nextRef.current ?? overlayRef.current)?.focus({ focusVisible: false });
|
|
22334
|
+
}, [stepIndex, bubbleShown, showNext]);
|
|
22200
22335
|
const maskId = `met-tutorial-mask-${React.useId()}`;
|
|
22201
22336
|
if (typeof document === "undefined") return null;
|
|
22202
22337
|
const holes = (layout?.spotlight ?? []).filter((r) => r !== null);
|
|
22203
|
-
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;
|
|
22204
22339
|
const Scene = step.Scene;
|
|
22205
22340
|
return reactDom.createPortal(
|
|
22206
|
-
/* @__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: [
|
|
22207
22342
|
/* @__PURE__ */ jsxRuntime.jsxs("svg", { className: "met-tutorial-dim", "aria-hidden": "true", children: [
|
|
22208
22343
|
/* @__PURE__ */ jsxRuntime.jsx("defs", { children: /* @__PURE__ */ jsxRuntime.jsxs("mask", { id: maskId, children: [
|
|
22209
22344
|
/* @__PURE__ */ jsxRuntime.jsx("rect", { width: "100%", height: "100%", fill: "white" }),
|
|
@@ -22238,7 +22373,7 @@ const TutorialOverlay = ({ tutorial, root, json, options, onClose }) => {
|
|
|
22238
22373
|
)),
|
|
22239
22374
|
Scene && layout && /* @__PURE__ */ jsxRuntime.jsx(Scene, { ctx, spotlight: layout.spotlight, taps: layout.taps, tapCount }),
|
|
22240
22375
|
tapTarget && /* @__PURE__ */ jsxRuntime.jsx(Hand, { target: tapTarget, tapKey: `${stepIndex}-${tapIndex}` }),
|
|
22241
|
-
layout && /* @__PURE__ */ jsxRuntime.jsx(
|
|
22376
|
+
layout && showBubble && /* @__PURE__ */ jsxRuntime.jsx(
|
|
22242
22377
|
Bubble,
|
|
22243
22378
|
{
|
|
22244
22379
|
layout,
|
|
@@ -22246,7 +22381,8 @@ const TutorialOverlay = ({ tutorial, root, json, options, onClose }) => {
|
|
|
22246
22381
|
stepIndex,
|
|
22247
22382
|
stepCount: steps.length,
|
|
22248
22383
|
nextLabel: options.nextLabel ?? DEFAULT_NEXT_LABEL,
|
|
22249
|
-
|
|
22384
|
+
showNext,
|
|
22385
|
+
onSpeak: !autoAdvance && options.onSpeak && (options.canSpeak?.(step.id) ?? true) ? () => optionsRef.current.onSpeak?.(step.id, textRef.current) : void 0,
|
|
22250
22386
|
onNext: next,
|
|
22251
22387
|
nextRef
|
|
22252
22388
|
}
|
|
@@ -22274,12 +22410,12 @@ const Hand = ({ target, tapKey }) => {
|
|
|
22274
22410
|
style: { left: TUTORIAL_HAND_TIP.x * HAND_W, top: TUTORIAL_HAND_TIP.y * HAND_H }
|
|
22275
22411
|
}
|
|
22276
22412
|
),
|
|
22277
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
22413
|
+
/* @__PURE__ */ jsxRuntime.jsx(ChameleonHand, {})
|
|
22278
22414
|
] }, tapKey)
|
|
22279
22415
|
}
|
|
22280
22416
|
);
|
|
22281
22417
|
};
|
|
22282
|
-
const Bubble = ({ layout, text, stepIndex, stepCount, nextLabel, onSpeak, onNext, nextRef }) => {
|
|
22418
|
+
const Bubble = ({ layout, text, stepIndex, stepCount, nextLabel, showNext, onSpeak, onNext, nextRef }) => {
|
|
22283
22419
|
const { anchor, root, vw, vh } = layout;
|
|
22284
22420
|
const clampLeft = (x) => Math.min(Math.max(EDGE, x), Math.max(EDGE, vw - BUBBLE_W - EDGE));
|
|
22285
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 };
|
|
@@ -22290,7 +22426,7 @@ const Bubble = ({ layout, text, stepIndex, stepCount, nextLabel, onSpeak, onNext
|
|
|
22290
22426
|
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M4 9v6h4l5 4V5L8 9H4z", fill: "currentColor" }),
|
|
22291
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" })
|
|
22292
22428
|
] }) }),
|
|
22293
|
-
/* @__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 })
|
|
22294
22430
|
] }),
|
|
22295
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)) })
|
|
22296
22432
|
] });
|
|
@@ -22310,7 +22446,16 @@ function answerBox(ctx) {
|
|
|
22310
22446
|
const inputs = getTextInputs(ctx.root);
|
|
22311
22447
|
return inputs.find(canTakeInput) ?? inputs[0] ?? null;
|
|
22312
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
|
+
}
|
|
22313
22456
|
function answerSlots(ctx) {
|
|
22457
|
+
const digits = answerDigits(ctx);
|
|
22458
|
+
if (digits) return digits.length;
|
|
22314
22459
|
const id = answerBox(ctx)?.id.replace(/-input$/, "");
|
|
22315
22460
|
const max = id ? getMaxInputLength(ctx.json, id) : void 0;
|
|
22316
22461
|
return Math.min(MAX_SLOTS, Math.max(1, max ?? DEFAULT_SLOTS));
|
|
@@ -22326,13 +22471,14 @@ function checkTarget(ctx) {
|
|
|
22326
22471
|
STAND_IN_CHECK_SIZE
|
|
22327
22472
|
);
|
|
22328
22473
|
}
|
|
22329
|
-
const demoKeys = (ctx) => DEMO_KEYS.map((k) => key(ctx, k));
|
|
22330
|
-
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(
|
|
22331
22476
|
"div",
|
|
22332
22477
|
{
|
|
22333
22478
|
className: "met-tutorial-fill",
|
|
22479
|
+
dir: "ltr",
|
|
22334
22480
|
style: { left: box.x, top: box.y, width: box.width, height: box.height },
|
|
22335
|
-
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))
|
|
22336
22482
|
}
|
|
22337
22483
|
);
|
|
22338
22484
|
const center = (r) => ({ x: r.x + r.width / 2, y: r.y + r.height / 2 });
|
|
@@ -22340,7 +22486,8 @@ const HopScene = ({ ctx, spotlight, taps, tapCount }) => {
|
|
|
22340
22486
|
const box = spotlight[1];
|
|
22341
22487
|
if (!box) return null;
|
|
22342
22488
|
const slots = answerSlots(ctx);
|
|
22343
|
-
const
|
|
22489
|
+
const digits = answerDigits(ctx);
|
|
22490
|
+
const filled = digits ? Math.min(tapCount, slots) : tapCount === 0 ? 0 : (tapCount - 1) % slots + 1;
|
|
22344
22491
|
const from = tapCount > 0 ? taps[(tapCount - 1) % taps.length] : null;
|
|
22345
22492
|
let arc = null;
|
|
22346
22493
|
if (from) {
|
|
@@ -22351,15 +22498,23 @@ const HopScene = ({ ctx, spotlight, taps, tapCount }) => {
|
|
|
22351
22498
|
}
|
|
22352
22499
|
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
22353
22500
|
arc && /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "met-tutorial-arc", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: arc }) }),
|
|
22354
|
-
arc && /* @__PURE__ */ jsxRuntime.jsx(
|
|
22355
|
-
|
|
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 })
|
|
22356
22511
|
] });
|
|
22357
22512
|
};
|
|
22358
22513
|
const CheckScene = ({ ctx, spotlight }) => {
|
|
22359
22514
|
const box = spotlight[0];
|
|
22360
22515
|
const check = spotlight[1];
|
|
22361
22516
|
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
22362
|
-
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) }),
|
|
22363
22518
|
check && !ctx.submitTarget && /* @__PURE__ */ jsxRuntime.jsx(
|
|
22364
22519
|
"div",
|
|
22365
22520
|
{
|
|
@@ -22400,13 +22555,39 @@ const WIDGET_TUTORIALS = [keypadTutorial];
|
|
|
22400
22555
|
function findWidgetTutorial(json) {
|
|
22401
22556
|
return WIDGET_TUTORIALS.find((t) => t.appliesTo(json)) ?? null;
|
|
22402
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
|
+
}
|
|
22403
22585
|
const TOOL_ASPECT_RATIO = {
|
|
22404
22586
|
ruler: RULER_ASPECT_RATIO,
|
|
22405
22587
|
protractor: PROTRACTOR_ASPECT_RATIO,
|
|
22406
22588
|
"set-square": SET_SQUARE_ASPECT_RATIO
|
|
22407
22589
|
};
|
|
22408
22590
|
const FLIP_BTN_EXTRA = 44;
|
|
22409
|
-
const INPUT_FINISH_IDLE_MS = 1e3;
|
|
22410
22591
|
function warnOnInvalidThemeColors(theme) {
|
|
22411
22592
|
if (process.env.NODE_ENV === "production" || !theme) return;
|
|
22412
22593
|
if (typeof CSS === "undefined" || typeof CSS.supports !== "function") return;
|
|
@@ -22432,6 +22613,8 @@ function buildInitialValues(json) {
|
|
|
22432
22613
|
initial[id] = String(widget.value);
|
|
22433
22614
|
} else if (widget.type === JSON_TYPES.GEOMETRY) {
|
|
22434
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;
|
|
22435
22618
|
}
|
|
22436
22619
|
}
|
|
22437
22620
|
return initial;
|
|
@@ -22444,9 +22627,6 @@ function hasJustFilledBox(json, key2, previous, value) {
|
|
|
22444
22627
|
if (maxLength === void 0) return false;
|
|
22445
22628
|
return String(previous ?? "").length < maxLength && String(value).length >= maxLength;
|
|
22446
22629
|
}
|
|
22447
|
-
function isTypedInputKey(json, key2) {
|
|
22448
|
-
return json.widgets[key2]?.type === JSON_TYPES.INLINE_INPUT || Boolean(parseFractionKey(key2));
|
|
22449
|
-
}
|
|
22450
22630
|
function buildInitialValuesSignature(json) {
|
|
22451
22631
|
return JSON.stringify({
|
|
22452
22632
|
widgets: json.widgets,
|
|
@@ -22518,7 +22698,7 @@ function buildRevealedDropSources(json, revealedValues) {
|
|
|
22518
22698
|
return sources;
|
|
22519
22699
|
}
|
|
22520
22700
|
const ExerciseRenderer = React.forwardRef(({
|
|
22521
|
-
json,
|
|
22701
|
+
json: suppliedJson,
|
|
22522
22702
|
onSubmit,
|
|
22523
22703
|
onChange,
|
|
22524
22704
|
showSubmitButton = false,
|
|
@@ -22527,29 +22707,12 @@ const ExerciseRenderer = React.forwardRef(({
|
|
|
22527
22707
|
sounds,
|
|
22528
22708
|
theme
|
|
22529
22709
|
}, ref) => {
|
|
22710
|
+
const json = React.useMemo(() => withDisabledPrefilledInputs(suppliedJson), [suppliedJson]);
|
|
22530
22711
|
const initialValuesSignature = React.useMemo(() => buildInitialValuesSignature(json), [json]);
|
|
22531
22712
|
const [values, setValues] = React.useState(() => buildInitialValues(json));
|
|
22532
22713
|
const valuesRef = React.useRef(values);
|
|
22533
22714
|
valuesRef.current = values;
|
|
22534
22715
|
const touchedRef = React.useRef(/* @__PURE__ */ new Set());
|
|
22535
|
-
const pendingRef = React.useRef(/* @__PURE__ */ new Map());
|
|
22536
|
-
const onChangeRef = React.useRef(onChange);
|
|
22537
|
-
onChangeRef.current = onChange;
|
|
22538
|
-
const clearPending = (key2) => {
|
|
22539
|
-
const timer = pendingRef.current.get(key2);
|
|
22540
|
-
if (timer === void 0) return false;
|
|
22541
|
-
clearTimeout(timer);
|
|
22542
|
-
pendingRef.current.delete(key2);
|
|
22543
|
-
return true;
|
|
22544
|
-
};
|
|
22545
|
-
const clearAllPending = () => {
|
|
22546
|
-
pendingRef.current.forEach((timer) => clearTimeout(timer));
|
|
22547
|
-
pendingRef.current = /* @__PURE__ */ new Map();
|
|
22548
|
-
};
|
|
22549
|
-
const finishPending = (key2) => {
|
|
22550
|
-
if (clearPending(key2)) onChangeRef.current?.(valuesRef.current);
|
|
22551
|
-
};
|
|
22552
|
-
React.useEffect(() => clearAllPending, []);
|
|
22553
22716
|
const [loadedValuesSignature, setLoadedValuesSignature] = React.useState(initialValuesSignature);
|
|
22554
22717
|
const [droppedSources, setDroppedSources] = React.useState(/* @__PURE__ */ new Map());
|
|
22555
22718
|
const [focusedInputId, setFocusedInputId] = React.useState(null);
|
|
@@ -22580,13 +22743,11 @@ const ExerciseRenderer = React.forwardRef(({
|
|
|
22580
22743
|
setLoadedValuesSignature(initialValuesSignature);
|
|
22581
22744
|
touchedRef.current = /* @__PURE__ */ new Set();
|
|
22582
22745
|
setTutorialRun(null);
|
|
22583
|
-
clearAllPending();
|
|
22584
22746
|
}, [json, initialValuesSignature, loadedValuesSignature]);
|
|
22585
22747
|
const locked = validationDetails !== null || externallyLocked;
|
|
22586
22748
|
const lastResultRef = React.useRef(null);
|
|
22587
22749
|
const runSubmit = () => {
|
|
22588
22750
|
if (locked) return lastResultRef.current ?? validate(json, values);
|
|
22589
|
-
clearAllPending();
|
|
22590
22751
|
const result = validate(json, values);
|
|
22591
22752
|
playSound(result.isCorrect ? "answer-correct" : "answer-wrong");
|
|
22592
22753
|
const details = { ...result.details };
|
|
@@ -22624,11 +22785,6 @@ const ExerciseRenderer = React.forwardRef(({
|
|
|
22624
22785
|
for (const id of answerable) {
|
|
22625
22786
|
if (!touchedRef.current.has(id)) missingSet.add(id);
|
|
22626
22787
|
}
|
|
22627
|
-
const isAnyFilled = answerable.some((id) => !missingSet.has(id));
|
|
22628
|
-
for (const key2 of pendingRef.current.keys()) {
|
|
22629
|
-
const id = toBaseId(key2);
|
|
22630
|
-
if (answerable.includes(id)) missingSet.add(id);
|
|
22631
|
-
}
|
|
22632
22788
|
const missing = [...missingSet];
|
|
22633
22789
|
const filledIds = answerable.filter((id) => !missingSet.has(id));
|
|
22634
22790
|
return {
|
|
@@ -22637,7 +22793,8 @@ const ExerciseRenderer = React.forwardRef(({
|
|
|
22637
22793
|
missing,
|
|
22638
22794
|
filledIds,
|
|
22639
22795
|
isComplete: answerable.length > 0 && missing.length === 0,
|
|
22640
|
-
isAnyFilled
|
|
22796
|
+
isAnyFilled: filledIds.length > 0,
|
|
22797
|
+
...getAnswerLengthCompletion(json, answerable, valuesRef.current, touchedRef.current)
|
|
22641
22798
|
};
|
|
22642
22799
|
},
|
|
22643
22800
|
reset: () => {
|
|
@@ -22651,12 +22808,10 @@ const ExerciseRenderer = React.forwardRef(({
|
|
|
22651
22808
|
setRevealed(false);
|
|
22652
22809
|
setLoadedValuesSignature(initialValuesSignature);
|
|
22653
22810
|
touchedRef.current = /* @__PURE__ */ new Set();
|
|
22654
|
-
clearAllPending();
|
|
22655
22811
|
},
|
|
22656
22812
|
revealAnswers: () => {
|
|
22657
22813
|
const revealedValues = buildRevealedValues(json);
|
|
22658
22814
|
if (Object.keys(revealedValues).length === 0) return;
|
|
22659
|
-
clearAllPending();
|
|
22660
22815
|
playSound("answer-reveal");
|
|
22661
22816
|
const details = {};
|
|
22662
22817
|
for (const [widgetId, userValue] of Object.entries(revealedValues)) {
|
|
@@ -22681,7 +22836,7 @@ const ExerciseRenderer = React.forwardRef(({
|
|
|
22681
22836
|
const container = rootRef.current ?? document.querySelector(".math-exercise-renderer");
|
|
22682
22837
|
if (!container) return;
|
|
22683
22838
|
const candidates = getTextInputs(container);
|
|
22684
|
-
const target = candidates.find(canTakeInput) ?? (locked ? void 0 : candidates
|
|
22839
|
+
const target = candidates.find(canTakeInput) ?? (locked ? void 0 : candidates.find(canAdvanceTo));
|
|
22685
22840
|
target?.focus();
|
|
22686
22841
|
},
|
|
22687
22842
|
hasTutorial: () => tutorial !== null,
|
|
@@ -22716,28 +22871,11 @@ const ExerciseRenderer = React.forwardRef(({
|
|
|
22716
22871
|
}
|
|
22717
22872
|
return next2;
|
|
22718
22873
|
});
|
|
22719
|
-
if (!silent && isTypedInputKey(json, widgetId)) {
|
|
22720
|
-
clearPending(widgetId);
|
|
22721
|
-
const maxLength = getMaxInputLength(json, widgetId);
|
|
22722
|
-
const isFull = maxLength !== void 0 && String(value).length >= maxLength;
|
|
22723
|
-
if (!fromDrop && value !== "" && !isFull) {
|
|
22724
|
-
pendingRef.current.set(
|
|
22725
|
-
widgetId,
|
|
22726
|
-
setTimeout(() => finishPending(widgetId), INPUT_FINISH_IDLE_MS)
|
|
22727
|
-
);
|
|
22728
|
-
}
|
|
22729
|
-
}
|
|
22730
22874
|
onChange?.(next);
|
|
22731
22875
|
if (!fromDrop && !silent && hasJustFilledBox(json, widgetId, previous, value)) {
|
|
22732
22876
|
focusNextInput(widgetId);
|
|
22733
22877
|
}
|
|
22734
22878
|
};
|
|
22735
|
-
const handleInputFocus = (key2) => {
|
|
22736
|
-
for (const pendingKey of [...pendingRef.current.keys()]) {
|
|
22737
|
-
if (pendingKey !== key2) finishPending(pendingKey);
|
|
22738
|
-
}
|
|
22739
|
-
setFocusedInputId(key2);
|
|
22740
|
-
};
|
|
22741
22879
|
const handleSubmit = () => {
|
|
22742
22880
|
runSubmit();
|
|
22743
22881
|
};
|
|
@@ -22870,7 +23008,7 @@ const ExerciseRenderer = React.forwardRef(({
|
|
|
22870
23008
|
if (setSquareWidget) add2("set-square-1", "set-square", Math.round(getSize(setSquareWidget) * 160));
|
|
22871
23009
|
return { tools: list, toolsMinHeight: maxH };
|
|
22872
23010
|
}, [rulerWidget, protractorWidget, setSquareWidget]);
|
|
22873
|
-
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.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: [
|
|
22874
23012
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
22875
23013
|
"div",
|
|
22876
23014
|
{
|
|
@@ -22895,7 +23033,7 @@ const ExerciseRenderer = React.forwardRef(({
|
|
|
22895
23033
|
droppedInputs,
|
|
22896
23034
|
validationDetails,
|
|
22897
23035
|
onChange: handleChange,
|
|
22898
|
-
onInputFocus:
|
|
23036
|
+
onInputFocus: setFocusedInputId,
|
|
22899
23037
|
resetKey
|
|
22900
23038
|
},
|
|
22901
23039
|
item.id
|
|
@@ -22911,7 +23049,7 @@ const ExerciseRenderer = React.forwardRef(({
|
|
|
22911
23049
|
droppedInputs,
|
|
22912
23050
|
validationDetails,
|
|
22913
23051
|
onChange: handleChange,
|
|
22914
|
-
onInputFocus:
|
|
23052
|
+
onInputFocus: setFocusedInputId,
|
|
22915
23053
|
resetKey
|
|
22916
23054
|
},
|
|
22917
23055
|
item.id
|
|
@@ -22928,7 +23066,7 @@ const ExerciseRenderer = React.forwardRef(({
|
|
|
22928
23066
|
droppedInputs,
|
|
22929
23067
|
validationDetails,
|
|
22930
23068
|
onChange: handleChange,
|
|
22931
|
-
onInputFocus:
|
|
23069
|
+
onInputFocus: setFocusedInputId,
|
|
22932
23070
|
resetKey
|
|
22933
23071
|
},
|
|
22934
23072
|
item.id
|
|
@@ -22977,12 +23115,13 @@ const ExerciseRenderer = React.forwardRef(({
|
|
|
22977
23115
|
options: tutorialRun.options,
|
|
22978
23116
|
onClose: (finished) => {
|
|
22979
23117
|
setTutorialRun(null);
|
|
23118
|
+
tutorialRun.options.onClose?.();
|
|
22980
23119
|
if (finished) tutorialRun.options.onDone?.();
|
|
22981
23120
|
}
|
|
22982
23121
|
},
|
|
22983
23122
|
tutorialRun.run
|
|
22984
23123
|
)
|
|
22985
|
-
] }) }) }) }) });
|
|
23124
|
+
] }) }) }) }) }) });
|
|
22986
23125
|
});
|
|
22987
23126
|
ExerciseRenderer.displayName = "ExerciseRenderer";
|
|
22988
23127
|
function renderExercise(options) {
|
|
@@ -23326,4 +23465,4 @@ exports.validateUniqueIds = validateUniqueIds;
|
|
|
23326
23465
|
exports.validateVisualFractionCapacity = validateVisualFractionCapacity;
|
|
23327
23466
|
exports.validateWidgetJSON = validateWidgetJSON;
|
|
23328
23467
|
exports.validateWidgetJSONSafe = validateWidgetJSONSafe;
|
|
23329
|
-
//# sourceMappingURL=index-
|
|
23468
|
+
//# sourceMappingURL=index-gCvC2wRR.js.map
|