@mathscan/math-exercise-engine 1.1.11 → 1.1.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +2 -0
- package/dist/index.js +93 -16
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +93 -16
- package/dist/index.mjs.map +1 -1
- package/dist/style.css +22 -0
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -17674,26 +17674,42 @@ const NumberLineRenderer = ({
|
|
|
17674
17674
|
positions,
|
|
17675
17675
|
cursors
|
|
17676
17676
|
} = item;
|
|
17677
|
-
const [counterCount, setCounterCount] = useState(
|
|
17678
|
-
counter
|
|
17679
|
-
|
|
17677
|
+
const [counterCount, setCounterCount] = useState(() => {
|
|
17678
|
+
if (counter === void 0) return 0;
|
|
17679
|
+
const raw = values[counter.id];
|
|
17680
|
+
const n = typeof raw === "string" && raw !== "" ? Number(raw) : NaN;
|
|
17681
|
+
return Number.isFinite(n) ? Math.min(counter.max, Math.max(counter.min, n)) : counter.default ?? counter.min;
|
|
17682
|
+
});
|
|
17680
17683
|
const defaultCounterCount = counter !== void 0 ? counter.default ?? counter.min : 0;
|
|
17684
|
+
const prevCounterKey = useRef(null);
|
|
17681
17685
|
useEffect(() => {
|
|
17682
|
-
|
|
17683
|
-
|
|
17686
|
+
const key2 = counter ? `${counter.id}:${defaultCounterCount}` : "";
|
|
17687
|
+
const changed = prevCounterKey.current !== null && prevCounterKey.current !== key2;
|
|
17688
|
+
prevCounterKey.current = key2;
|
|
17689
|
+
if (changed) setCounterCount(defaultCounterCount);
|
|
17690
|
+
}, [counter, defaultCounterCount]);
|
|
17684
17691
|
const onChangeRef = useRef(onChange);
|
|
17685
17692
|
onChangeRef.current = onChange;
|
|
17693
|
+
const valuesRef = useRef(values);
|
|
17694
|
+
valuesRef.current = values;
|
|
17686
17695
|
useEffect(() => {
|
|
17687
|
-
if (counter
|
|
17688
|
-
|
|
17696
|
+
if (counter === void 0) return;
|
|
17697
|
+
const next = String(counterCount);
|
|
17698
|
+
if (valuesRef.current[counter.id] !== next) {
|
|
17699
|
+
onChangeRef.current(counter.id, next);
|
|
17689
17700
|
}
|
|
17690
17701
|
}, [counterCount, counter?.id]);
|
|
17691
17702
|
const cursorKeys = useMemo(
|
|
17692
17703
|
() => (cursors ?? []).map((c) => `${c.id}:${c.value}`).join(","),
|
|
17693
17704
|
[cursors]
|
|
17694
17705
|
);
|
|
17706
|
+
const prevCursorKeys = useRef(null);
|
|
17695
17707
|
useEffect(() => {
|
|
17708
|
+
const isDefinitionChange = prevCursorKeys.current !== null && prevCursorKeys.current !== cursorKeys;
|
|
17709
|
+
prevCursorKeys.current = cursorKeys;
|
|
17696
17710
|
for (const cursor of cursors ?? []) {
|
|
17711
|
+
const existing = valuesRef.current[cursor.id];
|
|
17712
|
+
if (!isDefinitionChange && existing !== void 0 && existing !== "") continue;
|
|
17697
17713
|
onChangeRef.current(cursor.id, String(cursor.value));
|
|
17698
17714
|
}
|
|
17699
17715
|
}, [cursorKeys]);
|
|
@@ -18445,6 +18461,44 @@ function buildInitialValues(json) {
|
|
|
18445
18461
|
}
|
|
18446
18462
|
return initial;
|
|
18447
18463
|
}
|
|
18464
|
+
function buildRevealedValues(json) {
|
|
18465
|
+
const revealed = {};
|
|
18466
|
+
const validation = json.validation;
|
|
18467
|
+
if (!validation) return revealed;
|
|
18468
|
+
const answers = validation.answers ?? {};
|
|
18469
|
+
for (const [widgetId, expected] of Object.entries(answers)) {
|
|
18470
|
+
const widget = json.widgets[widgetId];
|
|
18471
|
+
if (widget?.type === JSON_TYPES.FRACTION && typeof expected === "string") {
|
|
18472
|
+
const [num, den] = expected.split("/", 2);
|
|
18473
|
+
if (widget.numerator === null && num !== void 0) revealed[getFractionKey(widgetId, "num")] = num;
|
|
18474
|
+
if (widget.denominator === null && den !== void 0) revealed[getFractionKey(widgetId, "den")] = den;
|
|
18475
|
+
continue;
|
|
18476
|
+
}
|
|
18477
|
+
revealed[widgetId] = typeof expected === "number" ? String(expected) : expected;
|
|
18478
|
+
}
|
|
18479
|
+
if ((validation.mode === VALIDATION_MODES.EXPRESSION || validation.mode === VALIDATION_MODES.MIXED) && validation.expression) {
|
|
18480
|
+
const vars = validation.variables ?? [];
|
|
18481
|
+
const freeVars = vars.filter((v) => !(v in answers));
|
|
18482
|
+
if (freeVars.length > 0) {
|
|
18483
|
+
const ranges = {};
|
|
18484
|
+
for (const v of vars) {
|
|
18485
|
+
if (v in answers) {
|
|
18486
|
+
const pinned = Number(answers[v]);
|
|
18487
|
+
if (Number.isFinite(pinned)) ranges[v] = { min: pinned, max: pinned, step: 1 };
|
|
18488
|
+
} else {
|
|
18489
|
+
ranges[v] = { min: 1, max: 100, step: 1 };
|
|
18490
|
+
}
|
|
18491
|
+
}
|
|
18492
|
+
const combo = findValidCombination(validation.expression, ranges);
|
|
18493
|
+
if (combo) {
|
|
18494
|
+
for (const v of freeVars) {
|
|
18495
|
+
if (combo[v] !== void 0) revealed[v] = String(combo[v]);
|
|
18496
|
+
}
|
|
18497
|
+
}
|
|
18498
|
+
}
|
|
18499
|
+
}
|
|
18500
|
+
return revealed;
|
|
18501
|
+
}
|
|
18448
18502
|
const ExerciseRenderer = forwardRef(({
|
|
18449
18503
|
json,
|
|
18450
18504
|
onSubmit,
|
|
@@ -18455,10 +18509,13 @@ const ExerciseRenderer = forwardRef(({
|
|
|
18455
18509
|
sounds
|
|
18456
18510
|
}, ref) => {
|
|
18457
18511
|
const [values, setValues] = useState(() => buildInitialValues(json));
|
|
18512
|
+
const valuesRef = useRef(values);
|
|
18513
|
+
valuesRef.current = values;
|
|
18458
18514
|
const [droppedSources, setDroppedSources] = useState(/* @__PURE__ */ new Map());
|
|
18459
18515
|
const [focusedInputId, setFocusedInputId] = useState(null);
|
|
18460
18516
|
const [validationDetails, setValidationDetails] = useState(null);
|
|
18461
18517
|
const [resetKey, setResetKey] = useState(0);
|
|
18518
|
+
const [revealed, setRevealed] = useState(false);
|
|
18462
18519
|
const playSound = useSoundPlayer(soundEnabled, sounds);
|
|
18463
18520
|
useImperativeHandle(ref, () => ({
|
|
18464
18521
|
submit: () => {
|
|
@@ -18492,7 +18549,7 @@ const ExerciseRenderer = forwardRef(({
|
|
|
18492
18549
|
getValues: () => values,
|
|
18493
18550
|
getCompletion: () => {
|
|
18494
18551
|
const answerable = getAnswerableInputs(json);
|
|
18495
|
-
const missing = getMissingInputs(json,
|
|
18552
|
+
const missing = getMissingInputs(json, valuesRef.current);
|
|
18496
18553
|
const missingSet = new Set(missing);
|
|
18497
18554
|
const filledIds = answerable.filter((id) => !missingSet.has(id));
|
|
18498
18555
|
return {
|
|
@@ -18505,11 +18562,28 @@ const ExerciseRenderer = forwardRef(({
|
|
|
18505
18562
|
};
|
|
18506
18563
|
},
|
|
18507
18564
|
reset: () => {
|
|
18508
|
-
|
|
18565
|
+
const initial = buildInitialValues(json);
|
|
18566
|
+
valuesRef.current = initial;
|
|
18567
|
+
setValues(initial);
|
|
18509
18568
|
setDroppedSources(/* @__PURE__ */ new Map());
|
|
18510
18569
|
setFocusedInputId(null);
|
|
18511
18570
|
setValidationDetails(null);
|
|
18512
18571
|
setResetKey((k) => k + 1);
|
|
18572
|
+
setRevealed(false);
|
|
18573
|
+
},
|
|
18574
|
+
revealAnswers: () => {
|
|
18575
|
+
const revealedValues = buildRevealedValues(json);
|
|
18576
|
+
const details = {};
|
|
18577
|
+
for (const [widgetId, userValue] of Object.entries(revealedValues)) {
|
|
18578
|
+
details[widgetId] = { widgetId, userValue, isCorrect: true };
|
|
18579
|
+
}
|
|
18580
|
+
const next = { ...valuesRef.current, ...revealedValues };
|
|
18581
|
+
valuesRef.current = next;
|
|
18582
|
+
setValues(next);
|
|
18583
|
+
setDroppedSources(/* @__PURE__ */ new Map());
|
|
18584
|
+
setValidationDetails(details);
|
|
18585
|
+
setRevealed(true);
|
|
18586
|
+
setResetKey((k) => k + 1);
|
|
18513
18587
|
},
|
|
18514
18588
|
focusFirstInput: () => {
|
|
18515
18589
|
const container = document.querySelector(".math-exercise-renderer");
|
|
@@ -18520,17 +18594,20 @@ const ExerciseRenderer = forwardRef(({
|
|
|
18520
18594
|
}
|
|
18521
18595
|
}), [json, values, onSubmit, playSound]);
|
|
18522
18596
|
const handleChange = (widgetId, value, fromDrop = false, sourceOptionId) => {
|
|
18523
|
-
|
|
18597
|
+
setRevealed(false);
|
|
18598
|
+
const next = { ...valuesRef.current, [widgetId]: value };
|
|
18599
|
+
valuesRef.current = next;
|
|
18600
|
+
setValues(next);
|
|
18524
18601
|
setDroppedSources((prev) => {
|
|
18525
|
-
const
|
|
18602
|
+
const next2 = new Map(prev);
|
|
18526
18603
|
if (fromDrop && typeof value === "string" && value !== "" && sourceOptionId) {
|
|
18527
|
-
|
|
18604
|
+
next2.set(widgetId, sourceOptionId);
|
|
18528
18605
|
} else {
|
|
18529
|
-
|
|
18606
|
+
next2.delete(widgetId);
|
|
18530
18607
|
}
|
|
18531
|
-
return
|
|
18608
|
+
return next2;
|
|
18532
18609
|
});
|
|
18533
|
-
onChange?.(
|
|
18610
|
+
onChange?.(next);
|
|
18534
18611
|
};
|
|
18535
18612
|
const handleSubmit = () => {
|
|
18536
18613
|
onSubmit?.(values);
|
|
@@ -18648,7 +18725,7 @@ const ExerciseRenderer = forwardRef(({
|
|
|
18648
18725
|
if (setSquareWidget) add2("set-square-1", "set-square", Math.round(getSize(setSquareWidget) * 160));
|
|
18649
18726
|
return { tools: list, toolsMinHeight: maxH };
|
|
18650
18727
|
}, [rulerWidget, protractorWidget, setSquareWidget]);
|
|
18651
|
-
return /* @__PURE__ */ jsx(SoundPlayerProvider, { play: playSound, children: /* @__PURE__ */ jsx(ExerciseDndProvider, { droppedSources, children: /* @__PURE__ */ jsxs("div", { className: `math-exercise-renderer ${className}`, children: [
|
|
18728
|
+
return /* @__PURE__ */ jsx(SoundPlayerProvider, { play: playSound, children: /* @__PURE__ */ jsx(ExerciseDndProvider, { droppedSources, children: /* @__PURE__ */ jsxs("div", { className: `math-exercise-renderer ${revealed ? "revealed" : ""} ${className}`, children: [
|
|
18652
18729
|
/* @__PURE__ */ jsxs(
|
|
18653
18730
|
"div",
|
|
18654
18731
|
{
|