@mathscan/math-exercise-engine 1.2.11 → 1.2.13
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 +3 -1
- package/dist/{GeometryWidget-BTPjXJz-.js → GeometryWidget-CMJTNSAG.js} +2 -2
- package/dist/{GeometryWidget-BTPjXJz-.js.map → GeometryWidget-CMJTNSAG.js.map} +1 -1
- package/dist/{GeometryWidget-2TCLbTEv.mjs → GeometryWidget-h2yNZsas.mjs} +2 -2
- package/dist/{GeometryWidget-2TCLbTEv.mjs.map → GeometryWidget-h2yNZsas.mjs.map} +1 -1
- package/dist/{index-6z_8j1tM.mjs → index-CAvdtiDQ.mjs} +71 -12
- package/dist/index-CAvdtiDQ.mjs.map +1 -0
- package/dist/{index-C3qDr71I.js → index-CbKXPRsS.js} +71 -12
- package/dist/index-CbKXPRsS.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +1 -1
- package/dist/index-6z_8j1tM.mjs.map +0 -1
- package/dist/index-C3qDr71I.js.map +0 -1
|
@@ -17160,12 +17160,17 @@ function getTextInputs(container) {
|
|
|
17160
17160
|
function canTakeInput(el) {
|
|
17161
17161
|
return !el.readOnly && !el.disabled;
|
|
17162
17162
|
}
|
|
17163
|
+
function canAdvanceTo(el) {
|
|
17164
|
+
return !el.disabled && el.tabIndex >= 0;
|
|
17165
|
+
}
|
|
17163
17166
|
function findNextInput(container, currentKey) {
|
|
17164
17167
|
const inputs = getTextInputs(container);
|
|
17165
17168
|
const currentIndex = inputs.findIndex((el) => el.id === getInputElementId(currentKey));
|
|
17166
17169
|
if (currentIndex === -1) return null;
|
|
17167
|
-
return inputs.slice(currentIndex + 1).find(
|
|
17170
|
+
return inputs.slice(currentIndex + 1).find(canAdvanceTo) ?? null;
|
|
17168
17171
|
}
|
|
17172
|
+
const KeypadContext = React.createContext(false);
|
|
17173
|
+
const useHasKeypad = () => React.useContext(KeypadContext);
|
|
17169
17174
|
const InlineInput = ({
|
|
17170
17175
|
widgetId,
|
|
17171
17176
|
value,
|
|
@@ -17182,6 +17187,7 @@ const InlineInput = ({
|
|
|
17182
17187
|
}) => {
|
|
17183
17188
|
const playSound = useSound();
|
|
17184
17189
|
const locked = useExerciseLocked();
|
|
17190
|
+
const hasKeypad = useHasKeypad();
|
|
17185
17191
|
const handleChange = React.useCallback((newValue, fromDrop = false, sourceOptionId) => {
|
|
17186
17192
|
onChange(widgetId, newValue, fromDrop, sourceOptionId);
|
|
17187
17193
|
}, [onChange, widgetId]);
|
|
@@ -17236,6 +17242,7 @@ const InlineInput = ({
|
|
|
17236
17242
|
},
|
|
17237
17243
|
onFocus: () => onFocus?.(widgetId),
|
|
17238
17244
|
readOnly: locked,
|
|
17245
|
+
inputMode: hasKeypad ? "none" : void 0,
|
|
17239
17246
|
disabled,
|
|
17240
17247
|
maxLength: expectedLength,
|
|
17241
17248
|
style: { width: dynamicWidth },
|
|
@@ -17438,6 +17445,7 @@ const FractionInput = ({
|
|
|
17438
17445
|
}) => {
|
|
17439
17446
|
const playSound = useSound();
|
|
17440
17447
|
const locked = useExerciseLocked();
|
|
17448
|
+
const hasKeypad = useHasKeypad();
|
|
17441
17449
|
const numKey = getFractionKey(widgetId, "num");
|
|
17442
17450
|
const denKey = getFractionKey(widgetId, "den");
|
|
17443
17451
|
const numValue = values[numKey] ?? "";
|
|
@@ -17491,6 +17499,7 @@ const FractionInput = ({
|
|
|
17491
17499
|
},
|
|
17492
17500
|
onFocus: () => onFocus?.(subKey),
|
|
17493
17501
|
readOnly: locked,
|
|
17502
|
+
inputMode: hasKeypad ? "none" : void 0,
|
|
17494
17503
|
maxLength: placeholder ? Math.max(20, placeholder.length) : 4,
|
|
17495
17504
|
size: placeholder ? placeholder.length : void 0,
|
|
17496
17505
|
"aria-label": label,
|
|
@@ -19701,7 +19710,7 @@ function isGeometryWidget(widget) {
|
|
|
19701
19710
|
return widget.type === JSON_TYPES.GEOMETRY;
|
|
19702
19711
|
}
|
|
19703
19712
|
const GeometryWidget = React.lazy(async () => ({
|
|
19704
|
-
default: (await Promise.resolve().then(() => require("./GeometryWidget-
|
|
19713
|
+
default: (await Promise.resolve().then(() => require("./GeometryWidget-CMJTNSAG.js"))).GeometryWidget
|
|
19705
19714
|
}));
|
|
19706
19715
|
const WidgetRenderer = ({
|
|
19707
19716
|
widget,
|
|
@@ -22397,6 +22406,7 @@ const TOOL_ASPECT_RATIO = {
|
|
|
22397
22406
|
"set-square": SET_SQUARE_ASPECT_RATIO
|
|
22398
22407
|
};
|
|
22399
22408
|
const FLIP_BTN_EXTRA = 44;
|
|
22409
|
+
const INPUT_FINISH_IDLE_MS = 1e3;
|
|
22400
22410
|
function warnOnInvalidThemeColors(theme) {
|
|
22401
22411
|
if (process.env.NODE_ENV === "production" || !theme) return;
|
|
22402
22412
|
if (typeof CSS === "undefined" || typeof CSS.supports !== "function") return;
|
|
@@ -22434,6 +22444,9 @@ function hasJustFilledBox(json, key2, previous, value) {
|
|
|
22434
22444
|
if (maxLength === void 0) return false;
|
|
22435
22445
|
return String(previous ?? "").length < maxLength && String(value).length >= maxLength;
|
|
22436
22446
|
}
|
|
22447
|
+
function isTypedInputKey(json, key2) {
|
|
22448
|
+
return json.widgets[key2]?.type === JSON_TYPES.INLINE_INPUT || Boolean(parseFractionKey(key2));
|
|
22449
|
+
}
|
|
22437
22450
|
function buildInitialValuesSignature(json) {
|
|
22438
22451
|
return JSON.stringify({
|
|
22439
22452
|
widgets: json.widgets,
|
|
@@ -22519,6 +22532,24 @@ const ExerciseRenderer = React.forwardRef(({
|
|
|
22519
22532
|
const valuesRef = React.useRef(values);
|
|
22520
22533
|
valuesRef.current = values;
|
|
22521
22534
|
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, []);
|
|
22522
22553
|
const [loadedValuesSignature, setLoadedValuesSignature] = React.useState(initialValuesSignature);
|
|
22523
22554
|
const [droppedSources, setDroppedSources] = React.useState(/* @__PURE__ */ new Map());
|
|
22524
22555
|
const [focusedInputId, setFocusedInputId] = React.useState(null);
|
|
@@ -22549,11 +22580,13 @@ const ExerciseRenderer = React.forwardRef(({
|
|
|
22549
22580
|
setLoadedValuesSignature(initialValuesSignature);
|
|
22550
22581
|
touchedRef.current = /* @__PURE__ */ new Set();
|
|
22551
22582
|
setTutorialRun(null);
|
|
22583
|
+
clearAllPending();
|
|
22552
22584
|
}, [json, initialValuesSignature, loadedValuesSignature]);
|
|
22553
22585
|
const locked = validationDetails !== null || externallyLocked;
|
|
22554
22586
|
const lastResultRef = React.useRef(null);
|
|
22555
22587
|
const runSubmit = () => {
|
|
22556
22588
|
if (locked) return lastResultRef.current ?? validate(json, values);
|
|
22589
|
+
clearAllPending();
|
|
22557
22590
|
const result = validate(json, values);
|
|
22558
22591
|
playSound(result.isCorrect ? "answer-correct" : "answer-wrong");
|
|
22559
22592
|
const details = { ...result.details };
|
|
@@ -22591,6 +22624,11 @@ const ExerciseRenderer = React.forwardRef(({
|
|
|
22591
22624
|
for (const id of answerable) {
|
|
22592
22625
|
if (!touchedRef.current.has(id)) missingSet.add(id);
|
|
22593
22626
|
}
|
|
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
|
+
}
|
|
22594
22632
|
const missing = [...missingSet];
|
|
22595
22633
|
const filledIds = answerable.filter((id) => !missingSet.has(id));
|
|
22596
22634
|
return {
|
|
@@ -22599,7 +22637,7 @@ const ExerciseRenderer = React.forwardRef(({
|
|
|
22599
22637
|
missing,
|
|
22600
22638
|
filledIds,
|
|
22601
22639
|
isComplete: answerable.length > 0 && missing.length === 0,
|
|
22602
|
-
isAnyFilled
|
|
22640
|
+
isAnyFilled
|
|
22603
22641
|
};
|
|
22604
22642
|
},
|
|
22605
22643
|
reset: () => {
|
|
@@ -22613,10 +22651,12 @@ const ExerciseRenderer = React.forwardRef(({
|
|
|
22613
22651
|
setRevealed(false);
|
|
22614
22652
|
setLoadedValuesSignature(initialValuesSignature);
|
|
22615
22653
|
touchedRef.current = /* @__PURE__ */ new Set();
|
|
22654
|
+
clearAllPending();
|
|
22616
22655
|
},
|
|
22617
22656
|
revealAnswers: () => {
|
|
22618
22657
|
const revealedValues = buildRevealedValues(json);
|
|
22619
22658
|
if (Object.keys(revealedValues).length === 0) return;
|
|
22659
|
+
clearAllPending();
|
|
22620
22660
|
playSound("answer-reveal");
|
|
22621
22661
|
const details = {};
|
|
22622
22662
|
for (const [widgetId, userValue] of Object.entries(revealedValues)) {
|
|
@@ -22645,6 +22685,7 @@ const ExerciseRenderer = React.forwardRef(({
|
|
|
22645
22685
|
target?.focus();
|
|
22646
22686
|
},
|
|
22647
22687
|
hasTutorial: () => tutorial !== null,
|
|
22688
|
+
tutorialId: () => tutorial?.id ?? null,
|
|
22648
22689
|
startTutorial: (options = {}) => {
|
|
22649
22690
|
if (!tutorial || !rootRef.current) return false;
|
|
22650
22691
|
setTutorialRun((prev) => ({ options, run: (prev?.run ?? 0) + 1 }));
|
|
@@ -22675,11 +22716,28 @@ const ExerciseRenderer = React.forwardRef(({
|
|
|
22675
22716
|
}
|
|
22676
22717
|
return next2;
|
|
22677
22718
|
});
|
|
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
|
+
}
|
|
22678
22730
|
onChange?.(next);
|
|
22679
22731
|
if (!fromDrop && !silent && hasJustFilledBox(json, widgetId, previous, value)) {
|
|
22680
22732
|
focusNextInput(widgetId);
|
|
22681
22733
|
}
|
|
22682
22734
|
};
|
|
22735
|
+
const handleInputFocus = (key2) => {
|
|
22736
|
+
for (const pendingKey of [...pendingRef.current.keys()]) {
|
|
22737
|
+
if (pendingKey !== key2) finishPending(pendingKey);
|
|
22738
|
+
}
|
|
22739
|
+
setFocusedInputId(key2);
|
|
22740
|
+
};
|
|
22683
22741
|
const handleSubmit = () => {
|
|
22684
22742
|
runSubmit();
|
|
22685
22743
|
};
|
|
@@ -22812,7 +22870,7 @@ const ExerciseRenderer = React.forwardRef(({
|
|
|
22812
22870
|
if (setSquareWidget) add2("set-square-1", "set-square", Math.round(getSize(setSquareWidget) * 160));
|
|
22813
22871
|
return { tools: list, toolsMinHeight: maxH };
|
|
22814
22872
|
}, [rulerWidget, protractorWidget, setSquareWidget]);
|
|
22815
|
-
return /* @__PURE__ */ jsxRuntime.jsx(SoundPlayerProvider, { play: playSound, children: /* @__PURE__ */ jsxRuntime.jsx(ExerciseLockProvider, { locked, children: /* @__PURE__ */ jsxRuntime.jsx(ExerciseDndProvider, { droppedSources, exerciseKey: loadedValuesSignature, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: rootRef, className: `math-exercise-renderer ${revealed ? "revealed" : ""} ${locked ? "locked" : ""} ${className}`, style: themeStyle, children: [
|
|
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: [
|
|
22816
22874
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
22817
22875
|
"div",
|
|
22818
22876
|
{
|
|
@@ -22837,7 +22895,7 @@ const ExerciseRenderer = React.forwardRef(({
|
|
|
22837
22895
|
droppedInputs,
|
|
22838
22896
|
validationDetails,
|
|
22839
22897
|
onChange: handleChange,
|
|
22840
|
-
onInputFocus:
|
|
22898
|
+
onInputFocus: handleInputFocus,
|
|
22841
22899
|
resetKey
|
|
22842
22900
|
},
|
|
22843
22901
|
item.id
|
|
@@ -22853,7 +22911,7 @@ const ExerciseRenderer = React.forwardRef(({
|
|
|
22853
22911
|
droppedInputs,
|
|
22854
22912
|
validationDetails,
|
|
22855
22913
|
onChange: handleChange,
|
|
22856
|
-
onInputFocus:
|
|
22914
|
+
onInputFocus: handleInputFocus,
|
|
22857
22915
|
resetKey
|
|
22858
22916
|
},
|
|
22859
22917
|
item.id
|
|
@@ -22870,7 +22928,7 @@ const ExerciseRenderer = React.forwardRef(({
|
|
|
22870
22928
|
droppedInputs,
|
|
22871
22929
|
validationDetails,
|
|
22872
22930
|
onChange: handleChange,
|
|
22873
|
-
onInputFocus:
|
|
22931
|
+
onInputFocus: handleInputFocus,
|
|
22874
22932
|
resetKey
|
|
22875
22933
|
},
|
|
22876
22934
|
item.id
|
|
@@ -22888,9 +22946,10 @@ const ExerciseRenderer = React.forwardRef(({
|
|
|
22888
22946
|
onChange: () => {
|
|
22889
22947
|
},
|
|
22890
22948
|
onKeypadChange: (newValue) => {
|
|
22891
|
-
if (focusedInputId)
|
|
22892
|
-
|
|
22893
|
-
|
|
22949
|
+
if (!focusedInputId) return;
|
|
22950
|
+
const maxLength = getMaxInputLength(json, focusedInputId);
|
|
22951
|
+
if (maxLength !== void 0 && newValue.length > maxLength) return;
|
|
22952
|
+
handleChange(focusedInputId, newValue, false);
|
|
22894
22953
|
}
|
|
22895
22954
|
}
|
|
22896
22955
|
) }),
|
|
@@ -22923,7 +22982,7 @@ const ExerciseRenderer = React.forwardRef(({
|
|
|
22923
22982
|
},
|
|
22924
22983
|
tutorialRun.run
|
|
22925
22984
|
)
|
|
22926
|
-
] }) }) }) });
|
|
22985
|
+
] }) }) }) }) });
|
|
22927
22986
|
});
|
|
22928
22987
|
ExerciseRenderer.displayName = "ExerciseRenderer";
|
|
22929
22988
|
function renderExercise(options) {
|
|
@@ -23267,4 +23326,4 @@ exports.validateUniqueIds = validateUniqueIds;
|
|
|
23267
23326
|
exports.validateVisualFractionCapacity = validateVisualFractionCapacity;
|
|
23268
23327
|
exports.validateWidgetJSON = validateWidgetJSON;
|
|
23269
23328
|
exports.validateWidgetJSONSafe = validateWidgetJSONSafe;
|
|
23270
|
-
//# sourceMappingURL=index-
|
|
23329
|
+
//# sourceMappingURL=index-CbKXPRsS.js.map
|