@mathscan/math-exercise-engine 1.0.16 → 1.0.17
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 +228 -0
- package/dist/index.js +84 -35
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +84 -35
- package/dist/index.mjs.map +1 -1
- package/dist/style.css +15 -12
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -160,6 +160,7 @@ const SIDE_PAD = 7;
|
|
|
160
160
|
const CSS_VAR_SIDE_PAD = "--nl-side-pad";
|
|
161
161
|
const NUMBER_LINE_LABELS = ["all", "extremes", "min-only", "max-only", "none"];
|
|
162
162
|
const NUMBER_LINE_HIGHLIGHT_COLORS = ["blue", "red", "green", "orange", "purple", "yellow"];
|
|
163
|
+
const NUMBER_LINE_DRAG_MODES = ["axis", "inputs"];
|
|
163
164
|
function getFractionKey(widgetId, part) {
|
|
164
165
|
return `${widgetId}__${part}`;
|
|
165
166
|
}
|
|
@@ -1156,7 +1157,8 @@ const FractionInlineSchema = zod.z.object({
|
|
|
1156
1157
|
numerator: zod.z.number().optional(),
|
|
1157
1158
|
denominator: zod.z.number().optional(),
|
|
1158
1159
|
answer: zod.z.string().optional(),
|
|
1159
|
-
simplified: zod.z.boolean().default(true)
|
|
1160
|
+
simplified: zod.z.boolean().default(true),
|
|
1161
|
+
placeholder: zod.z.string().max(32).optional()
|
|
1160
1162
|
});
|
|
1161
1163
|
const IMAGE_SRC_SCHEME_RE$1 = /^(https?:\/\/|data:image\/)/i;
|
|
1162
1164
|
const ImageInlineSchema = zod.z.object({
|
|
@@ -1438,6 +1440,11 @@ const NumberLineBlockSchema = zod.z.object({
|
|
|
1438
1440
|
}).optional(),
|
|
1439
1441
|
counter: NumberLineCounterSchema.optional(),
|
|
1440
1442
|
highlights: zod.z.array(NumberLineHighlightSchema).optional(),
|
|
1443
|
+
dragMode: zod.z.enum(NUMBER_LINE_DRAG_MODES, {
|
|
1444
|
+
errorMap: () => ({
|
|
1445
|
+
message: `NUMBER_LINE: dragMode must be one of ${NUMBER_LINE_DRAG_MODES.join(", ")}`
|
|
1446
|
+
})
|
|
1447
|
+
}).optional(),
|
|
1441
1448
|
positions: zod.z.array(NumberLinePositionSchema)
|
|
1442
1449
|
}).refine((data) => data.min < data.max, {
|
|
1443
1450
|
message: "NUMBER_LINE: min must be less than max"
|
|
@@ -1475,7 +1482,7 @@ const NumberLineBlockSchema = zod.z.object({
|
|
|
1475
1482
|
}
|
|
1476
1483
|
}
|
|
1477
1484
|
});
|
|
1478
|
-
function createNumberLineBlock(min, max, positions, step, minorTicks, style, labelVisibility, counter, highlights) {
|
|
1485
|
+
function createNumberLineBlock(min, max, positions, step, minorTicks, style, labelVisibility, counter, highlights, dragMode) {
|
|
1479
1486
|
return NumberLineBlockSchema.parse({
|
|
1480
1487
|
type: AST_TYPES.NUMBER_LINE_BLOCK,
|
|
1481
1488
|
min,
|
|
@@ -1486,6 +1493,7 @@ function createNumberLineBlock(min, max, positions, step, minorTicks, style, lab
|
|
|
1486
1493
|
labelVisibility,
|
|
1487
1494
|
counter,
|
|
1488
1495
|
highlights,
|
|
1496
|
+
dragMode,
|
|
1489
1497
|
positions
|
|
1490
1498
|
});
|
|
1491
1499
|
}
|
|
@@ -1850,6 +1858,7 @@ class NaturalDSLVisitor extends BaseCstVisitor {
|
|
|
1850
1858
|
const minorTicks = params.minorTicks !== void 0 ? Number(params.minorTicks) : void 0;
|
|
1851
1859
|
const style = params.style;
|
|
1852
1860
|
const labelVisibility = params.labelVisibility;
|
|
1861
|
+
const dragMode = params.dragMode;
|
|
1853
1862
|
const positions = ctx.position ? ctx.position.map((p) => this.visit(p)) : [];
|
|
1854
1863
|
const rawCounters = ctx.counter;
|
|
1855
1864
|
if (rawCounters && rawCounters.length > 1) {
|
|
@@ -1857,7 +1866,7 @@ class NaturalDSLVisitor extends BaseCstVisitor {
|
|
|
1857
1866
|
}
|
|
1858
1867
|
const counter = rawCounters ? this.visit(rawCounters) : void 0;
|
|
1859
1868
|
const highlights = ctx.highlight ? ctx.highlight.map((h) => this.visit(h)) : [];
|
|
1860
|
-
return createNumberLineBlock(min, max, positions, step, minorTicks, style, labelVisibility, counter, highlights);
|
|
1869
|
+
return createNumberLineBlock(min, max, positions, step, minorTicks, style, labelVisibility, counter, highlights, dragMode);
|
|
1861
1870
|
}
|
|
1862
1871
|
position(ctx) {
|
|
1863
1872
|
const params = this.visit(ctx.parameterList);
|
|
@@ -2050,7 +2059,8 @@ class NaturalDSLVisitor extends BaseCstVisitor {
|
|
|
2050
2059
|
const denominator = params.denominator;
|
|
2051
2060
|
const answer = params.answer;
|
|
2052
2061
|
const simplified = params.simplified !== void 0 ? params.simplified : void 0;
|
|
2053
|
-
|
|
2062
|
+
const placeholder = params.placeholder;
|
|
2063
|
+
return createFraction(name, { numerator, denominator, answer, simplified, placeholder });
|
|
2054
2064
|
}
|
|
2055
2065
|
image(ctx) {
|
|
2056
2066
|
const params = this.visit(ctx.parameterList);
|
|
@@ -2334,6 +2344,7 @@ const NumberLineItemSchema = zod.z.object({
|
|
|
2334
2344
|
labelVisibility: zod.z.enum(NUMBER_LINE_LABELS).optional(),
|
|
2335
2345
|
counter: NumberLineCounterItemSchema.optional(),
|
|
2336
2346
|
highlights: zod.z.array(NumberLineHighlightItemSchema).optional(),
|
|
2347
|
+
dragMode: zod.z.enum(NUMBER_LINE_DRAG_MODES).optional(),
|
|
2337
2348
|
positions: zod.z.array(NumberLinePositionItemSchema)
|
|
2338
2349
|
});
|
|
2339
2350
|
const MatcherItemContentSchema = zod.z.object({
|
|
@@ -2383,7 +2394,8 @@ const FractionJSONSchema = zod.z.object({
|
|
|
2383
2394
|
type: zod.z.literal(JSON_TYPES.FRACTION),
|
|
2384
2395
|
numerator: zod.z.number().nullable(),
|
|
2385
2396
|
denominator: zod.z.number().nullable(),
|
|
2386
|
-
simplified: zod.z.boolean()
|
|
2397
|
+
simplified: zod.z.boolean(),
|
|
2398
|
+
placeholder: zod.z.string().max(32).optional()
|
|
2387
2399
|
});
|
|
2388
2400
|
const MatcherWidgetJSONSchema = zod.z.object({
|
|
2389
2401
|
type: zod.z.literal(JSON_TYPES.MATCHER),
|
|
@@ -2601,7 +2613,8 @@ function toJSON(ast) {
|
|
|
2601
2613
|
type: JSON_TYPES.FRACTION,
|
|
2602
2614
|
numerator: inline.numerator ?? null,
|
|
2603
2615
|
denominator: inline.denominator ?? null,
|
|
2604
|
-
simplified: inline.simplified ?? true
|
|
2616
|
+
simplified: inline.simplified ?? true,
|
|
2617
|
+
placeholder: inline.placeholder
|
|
2605
2618
|
};
|
|
2606
2619
|
if (inline.answer !== void 0) {
|
|
2607
2620
|
answers[inline.name] = inline.answer;
|
|
@@ -2732,6 +2745,7 @@ function transformNumberLine(numberLine, idGenerator) {
|
|
|
2732
2745
|
...numberLine.labelVisibility !== void 0 && { labelVisibility: numberLine.labelVisibility },
|
|
2733
2746
|
...counterItem !== void 0 && { counter: counterItem },
|
|
2734
2747
|
...highlights !== void 0 && { highlights },
|
|
2748
|
+
...numberLine.dragMode !== void 0 && { dragMode: numberLine.dragMode },
|
|
2735
2749
|
positions
|
|
2736
2750
|
};
|
|
2737
2751
|
return item;
|
|
@@ -13092,8 +13106,11 @@ const FractionInput = ({
|
|
|
13092
13106
|
values,
|
|
13093
13107
|
droppedInputs,
|
|
13094
13108
|
validationStatus = null,
|
|
13109
|
+
numValidationStatus = null,
|
|
13110
|
+
denValidationStatus = null,
|
|
13095
13111
|
onChange,
|
|
13096
|
-
onFocus
|
|
13112
|
+
onFocus,
|
|
13113
|
+
placeholder
|
|
13097
13114
|
}) => {
|
|
13098
13115
|
const numKey = getFractionKey(widgetId, "num");
|
|
13099
13116
|
const denKey = getFractionKey(widgetId, "den");
|
|
@@ -13106,11 +13123,12 @@ const FractionInput = ({
|
|
|
13106
13123
|
onChange(subKey, data.value, true, String(item.id));
|
|
13107
13124
|
}
|
|
13108
13125
|
}, [onChange]);
|
|
13109
|
-
const renderSlot = (subKey, fixedValue, currentValue, label) => {
|
|
13126
|
+
const renderSlot = (subKey, fixedValue, currentValue, label, slotStatus) => {
|
|
13110
13127
|
if (fixedValue !== null) {
|
|
13111
13128
|
return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "fraction-fixed", children: fixedValue });
|
|
13112
13129
|
}
|
|
13113
13130
|
const isDroppedHere = droppedInputs.has(subKey);
|
|
13131
|
+
const slotClass = slotStatus ? ` widget--${slotStatus}` : statusClass;
|
|
13114
13132
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
13115
13133
|
Droppable,
|
|
13116
13134
|
{
|
|
@@ -13121,7 +13139,7 @@ const FractionInput = ({
|
|
|
13121
13139
|
children: isDroppedHere && currentValue !== "" ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
13122
13140
|
"button",
|
|
13123
13141
|
{
|
|
13124
|
-
className: `inline-input-chip fraction-chip${
|
|
13142
|
+
className: `inline-input-chip fraction-chip${slotClass}`,
|
|
13125
13143
|
onClick: () => onChange(subKey, "", false),
|
|
13126
13144
|
title: "Click to remove",
|
|
13127
13145
|
children: currentValue
|
|
@@ -13131,21 +13149,23 @@ const FractionInput = ({
|
|
|
13131
13149
|
{
|
|
13132
13150
|
id: `${subKey}-input`,
|
|
13133
13151
|
type: "text",
|
|
13134
|
-
className: `inline-input fraction-input${
|
|
13152
|
+
className: `inline-input fraction-input${slotClass}`,
|
|
13135
13153
|
value: currentValue,
|
|
13136
13154
|
onChange: (e) => onChange(subKey, e.target.value, false),
|
|
13137
13155
|
onFocus: () => onFocus?.(subKey),
|
|
13138
|
-
maxLength: 4,
|
|
13139
|
-
|
|
13156
|
+
maxLength: placeholder ? Math.max(20, placeholder.length) : 4,
|
|
13157
|
+
size: placeholder ? placeholder.length : void 0,
|
|
13158
|
+
"aria-label": label,
|
|
13159
|
+
placeholder
|
|
13140
13160
|
}
|
|
13141
13161
|
)
|
|
13142
13162
|
}
|
|
13143
13163
|
);
|
|
13144
13164
|
};
|
|
13145
13165
|
return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "fraction", role: "math", "aria-label": `fraction ${widgetId}`, children: [
|
|
13146
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "fraction-numerator", children: renderSlot(numKey, numerator, numValue, "numerator") }),
|
|
13166
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "fraction-numerator", children: renderSlot(numKey, numerator, numValue, "numerator", numValidationStatus) }),
|
|
13147
13167
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "fraction-bar", "aria-hidden": "true" }),
|
|
13148
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "fraction-denominator", children: renderSlot(denKey, denominator, denValue, "denominator") })
|
|
13168
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "fraction-denominator", children: renderSlot(denKey, denominator, denValue, "denominator", denValidationStatus) })
|
|
13149
13169
|
] });
|
|
13150
13170
|
};
|
|
13151
13171
|
function isLightColor(hex) {
|
|
@@ -13417,7 +13437,9 @@ const GridShape = ({
|
|
|
13417
13437
|
className: "visual-fraction-widget__parts visual-fraction-widget__parts--horizontal",
|
|
13418
13438
|
style: {
|
|
13419
13439
|
"--visual-fraction-divisions": divisions,
|
|
13420
|
-
"--visual-fraction-grid-cols": getGridColumnCount(divisions)
|
|
13440
|
+
"--visual-fraction-grid-cols": getGridColumnCount(divisions),
|
|
13441
|
+
// Safe integer division: zod schema enforces divisions = perfect square.
|
|
13442
|
+
"--visual-fraction-grid-rows": divisions / getGridColumnCount(divisions)
|
|
13421
13443
|
},
|
|
13422
13444
|
children: parts.map((part) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
13423
13445
|
"div",
|
|
@@ -13567,7 +13589,7 @@ const VisualFraction = ({
|
|
|
13567
13589
|
[divisions, filledCount, filledPartGroups, partAssignments, answer, filledPalette, answerPalette]
|
|
13568
13590
|
);
|
|
13569
13591
|
const statusClass = validationStatus ? ` visual-fraction-widget--${validationStatus} widget--${validationStatus}` : "";
|
|
13570
|
-
const rootStyle = React.useMemo(() => getSizeStyle(widget.size
|
|
13592
|
+
const rootStyle = React.useMemo(() => getSizeStyle(widget.size), [widget.size, shape]);
|
|
13571
13593
|
const ShapeComponent = SHAPE_COMPONENTS[shape];
|
|
13572
13594
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
13573
13595
|
"div",
|
|
@@ -13592,15 +13614,10 @@ const VisualFraction = ({
|
|
|
13592
13614
|
);
|
|
13593
13615
|
};
|
|
13594
13616
|
VisualFraction.displayName = "VisualFraction";
|
|
13595
|
-
function getSizeStyle(size,
|
|
13617
|
+
function getSizeStyle(size, _shape) {
|
|
13596
13618
|
if (size === void 0) return void 0;
|
|
13597
|
-
if (shape === "circle" || shape === "triangle") {
|
|
13598
|
-
return {
|
|
13599
|
-
"--visual-fraction-size": `${size * VISUAL_FRACTION_SVG_SIZE_SCALE}rem`
|
|
13600
|
-
};
|
|
13601
|
-
}
|
|
13602
13619
|
return {
|
|
13603
|
-
"--visual-fraction-
|
|
13620
|
+
"--visual-fraction-size": `${size * VISUAL_FRACTION_SVG_SIZE_SCALE}rem`
|
|
13604
13621
|
};
|
|
13605
13622
|
}
|
|
13606
13623
|
const RadioOptions = ({
|
|
@@ -13802,6 +13819,8 @@ const WidgetRenderer = ({
|
|
|
13802
13819
|
values = {},
|
|
13803
13820
|
isDropped = false,
|
|
13804
13821
|
validationStatus = null,
|
|
13822
|
+
numValidationStatus = null,
|
|
13823
|
+
denValidationStatus = null,
|
|
13805
13824
|
droppedInputs = /* @__PURE__ */ new Set(),
|
|
13806
13825
|
widgets,
|
|
13807
13826
|
onChange,
|
|
@@ -13880,8 +13899,11 @@ const WidgetRenderer = ({
|
|
|
13880
13899
|
values,
|
|
13881
13900
|
droppedInputs,
|
|
13882
13901
|
validationStatus,
|
|
13902
|
+
numValidationStatus,
|
|
13903
|
+
denValidationStatus,
|
|
13883
13904
|
onChange,
|
|
13884
|
-
onFocus: onInputFocus
|
|
13905
|
+
onFocus: onInputFocus,
|
|
13906
|
+
placeholder: widget.placeholder
|
|
13885
13907
|
}
|
|
13886
13908
|
);
|
|
13887
13909
|
}
|
|
@@ -13931,7 +13953,7 @@ const getMajorTicks = (min, max, step) => {
|
|
|
13931
13953
|
const count = Math.floor((max - min) / step);
|
|
13932
13954
|
const ticks = Array.from({ length: count + 1 }, (_, i) => cleanFloat(min + i * step));
|
|
13933
13955
|
const lastTick = ticks[ticks.length - 1];
|
|
13934
|
-
if (
|
|
13956
|
+
if (roundTick(lastTick) !== roundTick(max)) {
|
|
13935
13957
|
ticks.push(max);
|
|
13936
13958
|
} else {
|
|
13937
13959
|
ticks[ticks.length - 1] = max;
|
|
@@ -14542,7 +14564,7 @@ const NumberLineRenderer = ({
|
|
|
14542
14564
|
return step;
|
|
14543
14565
|
}, [counter, counterCount, min, max, step]);
|
|
14544
14566
|
const majorTicks = React.useMemo(
|
|
14545
|
-
() => getMajorTicks(min, max, effectiveStep).map(roundTick),
|
|
14567
|
+
() => [...new Set(getMajorTicks(min, max, effectiveStep).map(roundTick))],
|
|
14546
14568
|
[min, max, effectiveStep]
|
|
14547
14569
|
);
|
|
14548
14570
|
const minorValues = React.useMemo(
|
|
@@ -14551,16 +14573,17 @@ const NumberLineRenderer = ({
|
|
|
14551
14573
|
);
|
|
14552
14574
|
const showArrowRight = lineStyle === "arrow-right" || lineStyle === "arrow-both";
|
|
14553
14575
|
const showArrowLeft = lineStyle === "arrow-left" || lineStyle === "arrow-both";
|
|
14554
|
-
const
|
|
14576
|
+
const hasDragOptions = Boolean(widgets[DRAG_OPTIONS_WIDGET_ID]);
|
|
14577
|
+
const isAxisDragMode = hasDragOptions && item.dragMode === "axis";
|
|
14555
14578
|
const inputTickSet = React.useMemo(() => {
|
|
14556
|
-
if (
|
|
14579
|
+
if (isAxisDragMode) return /* @__PURE__ */ new Set();
|
|
14557
14580
|
return new Set(
|
|
14558
14581
|
positions.filter((pos) => getPositionWidgetId(pos.content) !== null).map((pos) => roundTick(pos.value))
|
|
14559
14582
|
);
|
|
14560
|
-
}, [
|
|
14583
|
+
}, [isAxisDragMode, positions]);
|
|
14561
14584
|
const visiblePositions = React.useMemo(
|
|
14562
|
-
() => positions.filter((pos) => !
|
|
14563
|
-
[
|
|
14585
|
+
() => positions.filter((pos) => !isAxisDragMode || getPositionWidgetId(pos.content) === null),
|
|
14586
|
+
[isAxisDragMode, positions]
|
|
14564
14587
|
);
|
|
14565
14588
|
const visiblePositionIds = React.useMemo(
|
|
14566
14589
|
() => visiblePositions.map((pos) => pos.id),
|
|
@@ -14570,7 +14593,7 @@ const NumberLineRenderer = ({
|
|
|
14570
14593
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
14571
14594
|
"div",
|
|
14572
14595
|
{
|
|
14573
|
-
className: `number-line${
|
|
14596
|
+
className: `number-line${isAxisDragMode ? " number-line--drag-mode" : ""}${lineStyle === "line" ? " number-line--style-line" : ""}`,
|
|
14574
14597
|
style: {
|
|
14575
14598
|
[CSS_VAR_SIDE_PAD]: `${SIDE_PAD}%`,
|
|
14576
14599
|
...positionContentHeight && { "--nl-position-content-height": positionContentHeight }
|
|
@@ -14600,7 +14623,7 @@ const NumberLineRenderer = ({
|
|
|
14600
14623
|
},
|
|
14601
14624
|
pos.id
|
|
14602
14625
|
)) }),
|
|
14603
|
-
|
|
14626
|
+
isAxisDragMode ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
14604
14627
|
FreeDragAxis,
|
|
14605
14628
|
{
|
|
14606
14629
|
axisDropId: `${item.id}--nl-axis`,
|
|
@@ -14711,6 +14734,7 @@ function groupLineItems(items) {
|
|
|
14711
14734
|
}
|
|
14712
14735
|
return groups;
|
|
14713
14736
|
}
|
|
14737
|
+
const toValidationStatus = (v) => v === true ? "correct" : v === false ? "incorrect" : null;
|
|
14714
14738
|
const LayoutRenderer = ({
|
|
14715
14739
|
item,
|
|
14716
14740
|
widgets,
|
|
@@ -14726,8 +14750,10 @@ const LayoutRenderer = ({
|
|
|
14726
14750
|
const hasArabic = isArabic(item.value);
|
|
14727
14751
|
return /* @__PURE__ */ jsxRuntime.jsx("span", { className: `question-text ${hasArabic ? "question-text-arabic" : ""}`, children: item.value });
|
|
14728
14752
|
}
|
|
14729
|
-
case JSON_TYPES.TEXT:
|
|
14730
|
-
|
|
14753
|
+
case JSON_TYPES.TEXT: {
|
|
14754
|
+
const isNum = /^[\d.,\s]+$/.test(item.value);
|
|
14755
|
+
return /* @__PURE__ */ jsxRuntime.jsx("span", { className: `text-item ${isNum ? "text-item-number" : ""} ${isArabic(item.value) ? "text-item-arabic" : "text-item-not-arabic"}`, children: item.value });
|
|
14756
|
+
}
|
|
14731
14757
|
case JSON_TYPES.HIGHLIGHTED_TEXT: {
|
|
14732
14758
|
const hasArabic = isArabic(item.value);
|
|
14733
14759
|
const dirAttr = !hasArabic ? "ltr" : void 0;
|
|
@@ -14794,6 +14820,8 @@ const LayoutRenderer = ({
|
|
|
14794
14820
|
values,
|
|
14795
14821
|
isDropped: droppedInputs.has(item.widgetId),
|
|
14796
14822
|
validationStatus,
|
|
14823
|
+
numValidationStatus: toValidationStatus(validationDetails?.[`${item.widgetId}__num`]),
|
|
14824
|
+
denValidationStatus: toValidationStatus(validationDetails?.[`${item.widgetId}__den`]),
|
|
14797
14825
|
droppedInputs,
|
|
14798
14826
|
widgets,
|
|
14799
14827
|
onChange,
|
|
@@ -14899,6 +14927,27 @@ const ExerciseRenderer = React.forwardRef(({
|
|
|
14899
14927
|
for (const [id, v] of Object.entries(result.details)) {
|
|
14900
14928
|
details[id] = v.isCorrect;
|
|
14901
14929
|
}
|
|
14930
|
+
for (const [widgetId, widget] of Object.entries(json.widgets)) {
|
|
14931
|
+
if (widget.type !== JSON_TYPES.FRACTION) continue;
|
|
14932
|
+
if (!(widgetId in details)) continue;
|
|
14933
|
+
const answer = json.validation?.answers?.[widgetId];
|
|
14934
|
+
if (!answer) continue;
|
|
14935
|
+
const [expectedNum, expectedDen] = answer.split("/", 2);
|
|
14936
|
+
if (!expectedNum || !expectedDen) continue;
|
|
14937
|
+
if (details[widgetId]) {
|
|
14938
|
+
if (widget.numerator === null) details[`${widgetId}__num`] = true;
|
|
14939
|
+
if (widget.denominator === null) details[`${widgetId}__den`] = true;
|
|
14940
|
+
} else {
|
|
14941
|
+
if (widget.numerator === null) {
|
|
14942
|
+
const u = values[`${widgetId}__num`];
|
|
14943
|
+
details[`${widgetId}__num`] = typeof u === "string" && u !== "" && normalize(u) === normalize(expectedNum);
|
|
14944
|
+
}
|
|
14945
|
+
if (widget.denominator === null) {
|
|
14946
|
+
const u = values[`${widgetId}__den`];
|
|
14947
|
+
details[`${widgetId}__den`] = typeof u === "string" && u !== "" && normalize(u) === normalize(expectedDen);
|
|
14948
|
+
}
|
|
14949
|
+
}
|
|
14950
|
+
}
|
|
14902
14951
|
setValidationDetails(details);
|
|
14903
14952
|
onSubmit?.(values);
|
|
14904
14953
|
return result;
|