@mathscan/math-exercise-engine 1.1.2 → 1.1.3
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 +7579 -3151
- package/dist/index.js +958 -83
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +958 -83
- package/dist/index.mjs.map +1 -1
- package/dist/style.css +144 -8
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -23,6 +23,8 @@ const AST_TYPES = {
|
|
|
23
23
|
BOLD_TEXT: "BoldText",
|
|
24
24
|
/** Inline input widget (string or numeric) */
|
|
25
25
|
INLINE_INPUT: "InlineInput",
|
|
26
|
+
/** Single inline draggable option (appears in-flow with text) */
|
|
27
|
+
INLINE_DRAG_OPTION: "InlineDragOption",
|
|
26
28
|
/** Inline fraction widget (numerator/denominator with optional inputs) */
|
|
27
29
|
FRACTION: "Fraction",
|
|
28
30
|
/** Inline image element */
|
|
@@ -40,6 +42,16 @@ const AST_TYPES = {
|
|
|
40
42
|
TABLE_ROW: "TableRow",
|
|
41
43
|
/** Cell within a table row */
|
|
42
44
|
TABLE_CELL: "TableCell",
|
|
45
|
+
/** Vertical operation (column arithmetic) block */
|
|
46
|
+
VERTICAL_OPERATION_BLOCK: "VerticalOperationBlock",
|
|
47
|
+
/** Row within a vertical operation */
|
|
48
|
+
VOP_ROW: "VopRow",
|
|
49
|
+
/** Cell within a vertical-operation row */
|
|
50
|
+
VOP_CELL: "VopCell",
|
|
51
|
+
/** Separator line within a vertical operation */
|
|
52
|
+
VOP_SEPARATOR: "VopSeparator",
|
|
53
|
+
/** Carry row within a vertical operation */
|
|
54
|
+
VOP_CARRY_ROW: "VopCarryRow",
|
|
43
55
|
/** Number line layout block */
|
|
44
56
|
NUMBER_LINE_BLOCK: "NumberLineBlock",
|
|
45
57
|
/** A position marker on the number line */
|
|
@@ -114,6 +126,16 @@ const JSON_TYPES = {
|
|
|
114
126
|
TABLE_ROW: "table-row",
|
|
115
127
|
/** Cell within a table */
|
|
116
128
|
TABLE_CELL: "table-cell",
|
|
129
|
+
/** Vertical operation (column arithmetic) block */
|
|
130
|
+
VERTICAL_OPERATION: "vertical-operation",
|
|
131
|
+
/** Row within a vertical operation */
|
|
132
|
+
VOP_ROW: "vop-row",
|
|
133
|
+
/** Cell within a vertical-operation row */
|
|
134
|
+
VOP_CELL: "vop-cell",
|
|
135
|
+
/** Separator within a vertical operation */
|
|
136
|
+
VOP_SEPARATOR: "vop-separator",
|
|
137
|
+
/** Carry row within a vertical operation */
|
|
138
|
+
VOP_CARRY_ROW: "vop-carry-row",
|
|
117
139
|
/** Number line layout block */
|
|
118
140
|
NUMBER_LINE: "number-line",
|
|
119
141
|
/** Position marker within a number line */
|
|
@@ -137,6 +159,8 @@ const JSON_TYPES = {
|
|
|
137
159
|
INLINE_INPUT: "inline-input",
|
|
138
160
|
/** Drag-and-drop options widget */
|
|
139
161
|
DRAG_OPTIONS: "drag-options",
|
|
162
|
+
/** Single inline draggable option (appears in-flow with text) */
|
|
163
|
+
INLINE_DRAG_OPTION: "inline-drag-option",
|
|
140
164
|
/** Radio button options widget */
|
|
141
165
|
RADIO_OPTIONS: "radio-options",
|
|
142
166
|
/** Numeric keypad widget */
|
|
@@ -152,6 +176,13 @@ const STATEFUL_WIDGET_TYPES = /* @__PURE__ */ new Set([
|
|
|
152
176
|
JSON_TYPES.PAIR_MATCHER,
|
|
153
177
|
JSON_TYPES.VISUAL_FRACTION
|
|
154
178
|
]);
|
|
179
|
+
const INLINE_DRAG_OPT_PREFIX = "_inline_drag_opt_";
|
|
180
|
+
const RTL_INLINE_WIDGET_TYPES = /* @__PURE__ */ new Set([
|
|
181
|
+
JSON_TYPES.INLINE_INPUT,
|
|
182
|
+
JSON_TYPES.INLINE_DRAG_OPTION,
|
|
183
|
+
JSON_TYPES.FRACTION,
|
|
184
|
+
JSON_TYPES.VISUAL_FRACTION
|
|
185
|
+
]);
|
|
155
186
|
const VALIDATION_MODES = {
|
|
156
187
|
/** Simple answer matching validation */
|
|
157
188
|
SIMPLE: "simple",
|
|
@@ -234,6 +265,10 @@ const STRING_INPUT = chevrotain.createToken({
|
|
|
234
265
|
name: "STRING_INPUT",
|
|
235
266
|
pattern: /STRING_INPUT/
|
|
236
267
|
});
|
|
268
|
+
const INLINE_DRAG_OPTION = chevrotain.createToken({
|
|
269
|
+
name: "INLINE_DRAG_OPTION",
|
|
270
|
+
pattern: /INLINE_DRAG_OPTION/
|
|
271
|
+
});
|
|
237
272
|
const DRAGABLE_BUTTONS_OPTIONS = chevrotain.createToken({
|
|
238
273
|
name: "DRAGABLE_BUTTONS_OPTIONS",
|
|
239
274
|
pattern: /DRAGABLE_BUTTONS_OPTIONS/
|
|
@@ -322,6 +357,26 @@ const IMAGE = chevrotain.createToken({
|
|
|
322
357
|
name: "IMAGE",
|
|
323
358
|
pattern: /IMAGE/
|
|
324
359
|
});
|
|
360
|
+
const VERTICAL_OPERATION = chevrotain.createToken({
|
|
361
|
+
name: "VERTICAL_OPERATION",
|
|
362
|
+
pattern: /VERTICAL_OPERATION/
|
|
363
|
+
});
|
|
364
|
+
const SEP = chevrotain.createToken({
|
|
365
|
+
name: "SEP",
|
|
366
|
+
pattern: /SEP(?![a-zA-Z0-9_])/
|
|
367
|
+
});
|
|
368
|
+
const CARRY = chevrotain.createToken({
|
|
369
|
+
name: "CARRY",
|
|
370
|
+
pattern: /CARRY(?![a-zA-Z0-9_])/
|
|
371
|
+
});
|
|
372
|
+
const INPUT = chevrotain.createToken({
|
|
373
|
+
name: "INPUT",
|
|
374
|
+
pattern: /INPUT(?=\s*\[)/
|
|
375
|
+
});
|
|
376
|
+
const Underscore = chevrotain.createToken({
|
|
377
|
+
name: "Underscore",
|
|
378
|
+
pattern: /_(?![a-zA-Z0-9_])/
|
|
379
|
+
});
|
|
325
380
|
const NumberLiteral = chevrotain.createToken({
|
|
326
381
|
name: "NumberLiteral",
|
|
327
382
|
pattern: /-?\d+(\.\d+)?/
|
|
@@ -418,6 +473,7 @@ const allTokens = [
|
|
|
418
473
|
HIGHLIGHT_TEXT,
|
|
419
474
|
W_S_I,
|
|
420
475
|
STRING_INPUT,
|
|
476
|
+
INLINE_DRAG_OPTION,
|
|
421
477
|
DRAGABLE_BUTTONS_OPTIONS,
|
|
422
478
|
RADIO_BUTTONS_OPTIONS,
|
|
423
479
|
NUMERIC_KEYPAD,
|
|
@@ -440,6 +496,11 @@ const allTokens = [
|
|
|
440
496
|
ITEM,
|
|
441
497
|
ANSWERS,
|
|
442
498
|
IMAGE,
|
|
499
|
+
VERTICAL_OPERATION,
|
|
500
|
+
SEP,
|
|
501
|
+
CARRY,
|
|
502
|
+
INPUT,
|
|
503
|
+
Underscore,
|
|
443
504
|
// Literals
|
|
444
505
|
NumberLiteral,
|
|
445
506
|
StringLiteral,
|
|
@@ -597,6 +658,10 @@ class MathExerciseParser extends chevrotain.CstParser {
|
|
|
597
658
|
GATE: this.BACKTRACK(this.matcher),
|
|
598
659
|
ALT: () => this.SUBRULE(this.matcher)
|
|
599
660
|
},
|
|
661
|
+
{
|
|
662
|
+
GATE: this.BACKTRACK(this.verticalOperation),
|
|
663
|
+
ALT: () => this.SUBRULE(this.verticalOperation)
|
|
664
|
+
},
|
|
600
665
|
{ ALT: () => this.SUBRULE(this.line) }
|
|
601
666
|
]);
|
|
602
667
|
});
|
|
@@ -917,6 +982,130 @@ class MathExerciseParser extends chevrotain.CstParser {
|
|
|
917
982
|
this.OR4([{ ALT: () => this.CONSUME4(Space) }, { ALT: () => this.CONSUME4(Newline) }]);
|
|
918
983
|
});
|
|
919
984
|
});
|
|
985
|
+
this.verticalOperation = this.RULE("verticalOperation", () => {
|
|
986
|
+
this.MANY(() => {
|
|
987
|
+
this.OR([{ ALT: () => this.CONSUME(Space) }, { ALT: () => this.CONSUME(Newline) }]);
|
|
988
|
+
});
|
|
989
|
+
this.CONSUME(VERTICAL_OPERATION);
|
|
990
|
+
this.OPTION(() => {
|
|
991
|
+
this.CONSUME(LBracket);
|
|
992
|
+
this.SUBRULE(this.parameterList);
|
|
993
|
+
this.CONSUME(RBracket);
|
|
994
|
+
});
|
|
995
|
+
this.MANY2(() => {
|
|
996
|
+
this.OR2([{ ALT: () => this.CONSUME2(Space) }, { ALT: () => this.CONSUME2(Newline) }]);
|
|
997
|
+
});
|
|
998
|
+
this.CONSUME(LBrace);
|
|
999
|
+
this.MANY3(() => {
|
|
1000
|
+
this.OR3([{ ALT: () => this.CONSUME3(Space) }, { ALT: () => this.CONSUME3(Newline) }]);
|
|
1001
|
+
});
|
|
1002
|
+
this.AT_LEAST_ONE(() => {
|
|
1003
|
+
this.OR4([
|
|
1004
|
+
{ ALT: () => this.SUBRULE(this.vopRow) },
|
|
1005
|
+
{ ALT: () => this.SUBRULE(this.vopCarry) },
|
|
1006
|
+
{ ALT: () => this.SUBRULE(this.vopSep) }
|
|
1007
|
+
]);
|
|
1008
|
+
});
|
|
1009
|
+
this.CONSUME(RBrace);
|
|
1010
|
+
this.MANY4(() => {
|
|
1011
|
+
this.OR5([{ ALT: () => this.CONSUME4(Newline) }, { ALT: () => this.CONSUME4(Space) }]);
|
|
1012
|
+
});
|
|
1013
|
+
});
|
|
1014
|
+
this.vopRow = this.RULE("vopRow", () => {
|
|
1015
|
+
this.CONSUME(ROW);
|
|
1016
|
+
this.OPTION(() => {
|
|
1017
|
+
this.CONSUME(LBracket);
|
|
1018
|
+
this.SUBRULE(this.parameterList);
|
|
1019
|
+
this.CONSUME(RBracket);
|
|
1020
|
+
});
|
|
1021
|
+
this.MANY(() => {
|
|
1022
|
+
this.OR([{ ALT: () => this.CONSUME(Space) }, { ALT: () => this.CONSUME(Newline) }]);
|
|
1023
|
+
});
|
|
1024
|
+
this.CONSUME(LBrace);
|
|
1025
|
+
this.MANY2(() => {
|
|
1026
|
+
this.OR2([{ ALT: () => this.CONSUME2(Space) }, { ALT: () => this.CONSUME2(Newline) }]);
|
|
1027
|
+
});
|
|
1028
|
+
this.SUBRULE(this.vopCell);
|
|
1029
|
+
this.MANY3(() => {
|
|
1030
|
+
this.MANY4(() => {
|
|
1031
|
+
this.OR3([{ ALT: () => this.CONSUME3(Space) }, { ALT: () => this.CONSUME3(Newline) }]);
|
|
1032
|
+
});
|
|
1033
|
+
this.CONSUME(Comma);
|
|
1034
|
+
this.MANY5(() => {
|
|
1035
|
+
this.OR4([{ ALT: () => this.CONSUME4(Space) }, { ALT: () => this.CONSUME4(Newline) }]);
|
|
1036
|
+
});
|
|
1037
|
+
this.SUBRULE2(this.vopCell);
|
|
1038
|
+
});
|
|
1039
|
+
this.MANY6(() => {
|
|
1040
|
+
this.OR5([{ ALT: () => this.CONSUME5(Space) }, { ALT: () => this.CONSUME5(Newline) }]);
|
|
1041
|
+
});
|
|
1042
|
+
this.CONSUME(RBrace);
|
|
1043
|
+
this.MANY7(() => {
|
|
1044
|
+
this.OR6([{ ALT: () => this.CONSUME6(Space) }, { ALT: () => this.CONSUME6(Newline) }]);
|
|
1045
|
+
});
|
|
1046
|
+
});
|
|
1047
|
+
this.vopCell = this.RULE("vopCell", () => {
|
|
1048
|
+
this.OR([
|
|
1049
|
+
{ ALT: () => this.CONSUME(NumberLiteral) },
|
|
1050
|
+
{ ALT: () => this.CONSUME(StringLiteral) },
|
|
1051
|
+
{ ALT: () => this.CONSUME(Underscore) },
|
|
1052
|
+
{
|
|
1053
|
+
ALT: () => {
|
|
1054
|
+
this.CONSUME(INPUT);
|
|
1055
|
+
this.CONSUME(LBracket);
|
|
1056
|
+
this.SUBRULE(this.parameterList);
|
|
1057
|
+
this.CONSUME(RBracket);
|
|
1058
|
+
}
|
|
1059
|
+
},
|
|
1060
|
+
{ ALT: () => this.SUBRULE(this.colorText) },
|
|
1061
|
+
{ ALT: () => this.SUBRULE(this.boldText) },
|
|
1062
|
+
{ ALT: () => this.SUBRULE(this.textHighlighter) }
|
|
1063
|
+
]);
|
|
1064
|
+
});
|
|
1065
|
+
this.vopCarry = this.RULE("vopCarry", () => {
|
|
1066
|
+
this.CONSUME(CARRY);
|
|
1067
|
+
this.MANY(() => {
|
|
1068
|
+
this.OR([{ ALT: () => this.CONSUME(Space) }, { ALT: () => this.CONSUME(Newline) }]);
|
|
1069
|
+
});
|
|
1070
|
+
this.CONSUME(LBrace);
|
|
1071
|
+
this.MANY2(() => {
|
|
1072
|
+
this.OR2([{ ALT: () => this.CONSUME2(Space) }, { ALT: () => this.CONSUME2(Newline) }]);
|
|
1073
|
+
});
|
|
1074
|
+
this.SUBRULE(this.vopCell);
|
|
1075
|
+
this.MANY3(() => {
|
|
1076
|
+
this.MANY4(() => {
|
|
1077
|
+
this.OR3([{ ALT: () => this.CONSUME3(Space) }, { ALT: () => this.CONSUME3(Newline) }]);
|
|
1078
|
+
});
|
|
1079
|
+
this.CONSUME(Comma);
|
|
1080
|
+
this.MANY5(() => {
|
|
1081
|
+
this.OR4([{ ALT: () => this.CONSUME4(Space) }, { ALT: () => this.CONSUME4(Newline) }]);
|
|
1082
|
+
});
|
|
1083
|
+
this.SUBRULE2(this.vopCell);
|
|
1084
|
+
});
|
|
1085
|
+
this.MANY6(() => {
|
|
1086
|
+
this.OR5([{ ALT: () => this.CONSUME5(Space) }, { ALT: () => this.CONSUME5(Newline) }]);
|
|
1087
|
+
});
|
|
1088
|
+
this.CONSUME(RBrace);
|
|
1089
|
+
this.MANY7(() => {
|
|
1090
|
+
this.OR6([{ ALT: () => this.CONSUME6(Newline) }, { ALT: () => this.CONSUME6(Space) }]);
|
|
1091
|
+
});
|
|
1092
|
+
});
|
|
1093
|
+
this.vopSep = this.RULE("vopSep", () => {
|
|
1094
|
+
this.CONSUME(SEP);
|
|
1095
|
+
this.OPTION(() => {
|
|
1096
|
+
this.MANY(() => {
|
|
1097
|
+
this.OR([{ ALT: () => this.CONSUME(Space) }, { ALT: () => this.CONSUME(Newline) }]);
|
|
1098
|
+
});
|
|
1099
|
+
this.CONSUME(LBrace);
|
|
1100
|
+
this.MANY2(() => {
|
|
1101
|
+
this.OR2([{ ALT: () => this.CONSUME2(Space) }, { ALT: () => this.CONSUME2(Newline) }]);
|
|
1102
|
+
});
|
|
1103
|
+
this.CONSUME(RBrace);
|
|
1104
|
+
});
|
|
1105
|
+
this.MANY3(() => {
|
|
1106
|
+
this.OR3([{ ALT: () => this.CONSUME3(Space) }, { ALT: () => this.CONSUME3(Newline) }]);
|
|
1107
|
+
});
|
|
1108
|
+
});
|
|
920
1109
|
this.line = this.RULE("line", () => {
|
|
921
1110
|
this.MANY(() => {
|
|
922
1111
|
this.CONSUME(Space);
|
|
@@ -970,7 +1159,9 @@ class MathExerciseParser extends chevrotain.CstParser {
|
|
|
970
1159
|
// VISUAL_FRACTION — always allowed; display-only when answer=[] or absence of answer parameter, interactive when answer=[...] with values.
|
|
971
1160
|
{ ALT: () => this.SUBRULE(this.visualFraction) },
|
|
972
1161
|
// W_S_I (inline input widget) — excluded in display-only contexts (e.g. MATCHER ITEM)
|
|
973
|
-
{ GATE: () => !displayOnly, ALT: () => this.SUBRULE(this.inlineInput) }
|
|
1162
|
+
{ GATE: () => !displayOnly, ALT: () => this.SUBRULE(this.inlineInput) },
|
|
1163
|
+
// INLINE_DRAG_OPTION (single draggable option inline with text) — excluded in display-only
|
|
1164
|
+
{ GATE: () => !displayOnly, ALT: () => this.SUBRULE(this.inlineDragOption) }
|
|
974
1165
|
]);
|
|
975
1166
|
});
|
|
976
1167
|
this.textHighlighter = this.RULE("textHighlighter", () => {
|
|
@@ -1052,6 +1243,12 @@ class MathExerciseParser extends chevrotain.CstParser {
|
|
|
1052
1243
|
this.SUBRULE(this.parameterList);
|
|
1053
1244
|
this.CONSUME(RBracket);
|
|
1054
1245
|
});
|
|
1246
|
+
this.inlineDragOption = this.RULE("inlineDragOption", () => {
|
|
1247
|
+
this.CONSUME(INLINE_DRAG_OPTION);
|
|
1248
|
+
this.CONSUME(LBracket);
|
|
1249
|
+
this.CONSUME(StringLiteral);
|
|
1250
|
+
this.CONSUME(RBracket);
|
|
1251
|
+
});
|
|
1055
1252
|
this.tool = this.RULE("tool", () => {
|
|
1056
1253
|
this.OR([
|
|
1057
1254
|
{ ALT: () => this.SUBRULE(this.dragableButtons) },
|
|
@@ -1320,6 +1517,10 @@ function isPerfectSquare(value) {
|
|
|
1320
1517
|
const root = Math.sqrt(value);
|
|
1321
1518
|
return Number.isInteger(root);
|
|
1322
1519
|
}
|
|
1520
|
+
const InlineDragOptionSchema = zod.z.object({
|
|
1521
|
+
type: zod.z.literal(AST_TYPES.INLINE_DRAG_OPTION),
|
|
1522
|
+
value: zod.z.string().min(1)
|
|
1523
|
+
});
|
|
1323
1524
|
const InlineNodeSchema = zod.z.union([
|
|
1324
1525
|
FreeTextInlineSchema,
|
|
1325
1526
|
SpaceInlineSchema,
|
|
@@ -1329,7 +1530,8 @@ const InlineNodeSchema = zod.z.union([
|
|
|
1329
1530
|
InlineInputWidgetSchema,
|
|
1330
1531
|
FractionInlineSchema,
|
|
1331
1532
|
ImageInlineSchema,
|
|
1332
|
-
VisualFractionInlineSchema
|
|
1533
|
+
VisualFractionInlineSchema,
|
|
1534
|
+
InlineDragOptionSchema
|
|
1333
1535
|
]);
|
|
1334
1536
|
function createFreeText(value) {
|
|
1335
1537
|
return FreeTextInlineSchema.parse({
|
|
@@ -1365,24 +1567,32 @@ function createBoldText(value, highlight) {
|
|
|
1365
1567
|
});
|
|
1366
1568
|
}
|
|
1367
1569
|
function createStringInput(name, length, answer, placeholder) {
|
|
1368
|
-
return parseOrThrow(
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1570
|
+
return parseOrThrow(
|
|
1571
|
+
InlineInputWidgetSchema,
|
|
1572
|
+
{
|
|
1573
|
+
type: AST_TYPES.INLINE_INPUT,
|
|
1574
|
+
name,
|
|
1575
|
+
inputType: INPUT_TYPES.STRING,
|
|
1576
|
+
length,
|
|
1577
|
+
placeholder,
|
|
1578
|
+
answer
|
|
1579
|
+
},
|
|
1580
|
+
"STRING_INPUT"
|
|
1581
|
+
);
|
|
1376
1582
|
}
|
|
1377
1583
|
function createNumericInput(name, digits, answer, placeholder) {
|
|
1378
|
-
return parseOrThrow(
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1584
|
+
return parseOrThrow(
|
|
1585
|
+
InlineInputWidgetSchema,
|
|
1586
|
+
{
|
|
1587
|
+
type: AST_TYPES.INLINE_INPUT,
|
|
1588
|
+
name,
|
|
1589
|
+
inputType: INPUT_TYPES.NUMERIC,
|
|
1590
|
+
digits,
|
|
1591
|
+
placeholder,
|
|
1592
|
+
answer
|
|
1593
|
+
},
|
|
1594
|
+
"NUMERIC_INPUT"
|
|
1595
|
+
);
|
|
1386
1596
|
}
|
|
1387
1597
|
function createImage(name, src, alt, options = {}) {
|
|
1388
1598
|
return ImageInlineSchema.parse({
|
|
@@ -1408,6 +1618,12 @@ function createVisualFraction(name, divisions, options = {}) {
|
|
|
1408
1618
|
...options
|
|
1409
1619
|
});
|
|
1410
1620
|
}
|
|
1621
|
+
function createInlineDragOption(value) {
|
|
1622
|
+
return InlineDragOptionSchema.parse({
|
|
1623
|
+
type: AST_TYPES.INLINE_DRAG_OPTION,
|
|
1624
|
+
value
|
|
1625
|
+
});
|
|
1626
|
+
}
|
|
1411
1627
|
function isFreeText(value) {
|
|
1412
1628
|
return FreeTextInlineSchema.safeParse(value).success;
|
|
1413
1629
|
}
|
|
@@ -1429,6 +1645,9 @@ function isInlineNode(value) {
|
|
|
1429
1645
|
function isVisualFractionInline(value) {
|
|
1430
1646
|
return VisualFractionInlineSchema.safeParse(value).success;
|
|
1431
1647
|
}
|
|
1648
|
+
function isInlineDragOption(value) {
|
|
1649
|
+
return InlineDragOptionSchema.safeParse(value).success;
|
|
1650
|
+
}
|
|
1432
1651
|
const TextBlockSchema = zod.z.object({
|
|
1433
1652
|
type: zod.z.literal(AST_TYPES.TEXT_BLOCK),
|
|
1434
1653
|
value: zod.z.string()
|
|
@@ -1482,17 +1701,19 @@ const NumberLineCounterSchema = zod.z.object({
|
|
|
1482
1701
|
min: zod.z.number().int().min(1, { message: "COUNTER: min must be >= 1" }),
|
|
1483
1702
|
max: zod.z.number().int().positive({ message: "COUNTER: max must be > 0" }),
|
|
1484
1703
|
default: zod.z.number().int().nonnegative().optional(),
|
|
1485
|
-
name: zod.z.string().optional()
|
|
1704
|
+
name: zod.z.string().optional(),
|
|
1705
|
+
answer: zod.z.number().int().positive({ message: "COUNTER: answer must be a positive integer" }).optional()
|
|
1486
1706
|
}).refine((d) => d.min <= d.max, {
|
|
1487
1707
|
message: "COUNTER: min must be <= max"
|
|
1488
1708
|
});
|
|
1489
|
-
function createNumberLineCounter(max, min, defaultValue2, name) {
|
|
1709
|
+
function createNumberLineCounter(max, min, defaultValue2, name, answer) {
|
|
1490
1710
|
return NumberLineCounterSchema.parse({
|
|
1491
1711
|
type: AST_TYPES.NUMBER_LINE_COUNTER,
|
|
1492
1712
|
min: min ?? 1,
|
|
1493
1713
|
max,
|
|
1494
1714
|
...defaultValue2 !== void 0 && { default: defaultValue2 },
|
|
1495
|
-
...name !== void 0 && { name }
|
|
1715
|
+
...name !== void 0 && { name },
|
|
1716
|
+
...answer !== void 0 && { answer }
|
|
1496
1717
|
});
|
|
1497
1718
|
}
|
|
1498
1719
|
const NumberLineHighlightSchema = zod.z.object({
|
|
@@ -1626,6 +1847,146 @@ function createTableBlock(cols, rows) {
|
|
|
1626
1847
|
rows
|
|
1627
1848
|
});
|
|
1628
1849
|
}
|
|
1850
|
+
const VOP_OPERATORS = ["+", "-", "×", "÷", "="];
|
|
1851
|
+
function normalizeVopOperator(raw) {
|
|
1852
|
+
const map = {
|
|
1853
|
+
"+": "+",
|
|
1854
|
+
"-": "-",
|
|
1855
|
+
"−": "-",
|
|
1856
|
+
"*": "×",
|
|
1857
|
+
"x": "×",
|
|
1858
|
+
"X": "×",
|
|
1859
|
+
"×": "×",
|
|
1860
|
+
"/": "÷",
|
|
1861
|
+
"÷": "÷",
|
|
1862
|
+
"=": "="
|
|
1863
|
+
};
|
|
1864
|
+
const op = map[raw];
|
|
1865
|
+
if (!op) throw new Error(`VERTICAL_OPERATION: unsupported op "${raw}"`);
|
|
1866
|
+
return op;
|
|
1867
|
+
}
|
|
1868
|
+
const VopDigitCellSchema = zod.z.object({
|
|
1869
|
+
type: zod.z.literal(AST_TYPES.VOP_CELL),
|
|
1870
|
+
kind: zod.z.literal("digit"),
|
|
1871
|
+
value: zod.z.string()
|
|
1872
|
+
});
|
|
1873
|
+
const VopLabelCellSchema = zod.z.object({
|
|
1874
|
+
type: zod.z.literal(AST_TYPES.VOP_CELL),
|
|
1875
|
+
kind: zod.z.literal("label"),
|
|
1876
|
+
value: zod.z.string()
|
|
1877
|
+
});
|
|
1878
|
+
const VopInputCellSchema = zod.z.object({
|
|
1879
|
+
type: zod.z.literal(AST_TYPES.VOP_CELL),
|
|
1880
|
+
kind: zod.z.literal("input"),
|
|
1881
|
+
name: zod.z.string().min(1),
|
|
1882
|
+
digits: zod.z.number().int().positive().default(1),
|
|
1883
|
+
answer: zod.z.union([zod.z.string(), zod.z.number()]).optional()
|
|
1884
|
+
});
|
|
1885
|
+
const VopEmptyCellSchema = zod.z.object({
|
|
1886
|
+
type: zod.z.literal(AST_TYPES.VOP_CELL),
|
|
1887
|
+
kind: zod.z.literal("empty")
|
|
1888
|
+
});
|
|
1889
|
+
const VopColorCellSchema = zod.z.object({
|
|
1890
|
+
type: zod.z.literal(AST_TYPES.VOP_CELL),
|
|
1891
|
+
kind: zod.z.literal("color"),
|
|
1892
|
+
value: zod.z.string(),
|
|
1893
|
+
color: zod.z.string()
|
|
1894
|
+
});
|
|
1895
|
+
const VopBoldCellSchema = zod.z.object({
|
|
1896
|
+
type: zod.z.literal(AST_TYPES.VOP_CELL),
|
|
1897
|
+
kind: zod.z.literal("bold"),
|
|
1898
|
+
value: zod.z.string()
|
|
1899
|
+
});
|
|
1900
|
+
const VopHighlightCellSchema = zod.z.object({
|
|
1901
|
+
type: zod.z.literal(AST_TYPES.VOP_CELL),
|
|
1902
|
+
kind: zod.z.literal("highlight"),
|
|
1903
|
+
value: zod.z.string()
|
|
1904
|
+
});
|
|
1905
|
+
const VopCellSchema = zod.z.discriminatedUnion("kind", [
|
|
1906
|
+
VopDigitCellSchema,
|
|
1907
|
+
VopLabelCellSchema,
|
|
1908
|
+
VopColorCellSchema,
|
|
1909
|
+
VopBoldCellSchema,
|
|
1910
|
+
VopHighlightCellSchema,
|
|
1911
|
+
VopInputCellSchema,
|
|
1912
|
+
VopEmptyCellSchema
|
|
1913
|
+
]);
|
|
1914
|
+
function createVopDigitCell(value) {
|
|
1915
|
+
return VopDigitCellSchema.parse({ type: AST_TYPES.VOP_CELL, kind: "digit", value });
|
|
1916
|
+
}
|
|
1917
|
+
function createVopInputCell(name, digits = 1, answer) {
|
|
1918
|
+
return VopInputCellSchema.parse({
|
|
1919
|
+
type: AST_TYPES.VOP_CELL,
|
|
1920
|
+
kind: "input",
|
|
1921
|
+
name,
|
|
1922
|
+
digits,
|
|
1923
|
+
answer
|
|
1924
|
+
});
|
|
1925
|
+
}
|
|
1926
|
+
function createVopLabelCell(value) {
|
|
1927
|
+
return VopLabelCellSchema.parse({ type: AST_TYPES.VOP_CELL, kind: "label", value });
|
|
1928
|
+
}
|
|
1929
|
+
function createVopEmptyCell() {
|
|
1930
|
+
return VopEmptyCellSchema.parse({ type: AST_TYPES.VOP_CELL, kind: "empty" });
|
|
1931
|
+
}
|
|
1932
|
+
function createVopColorCell(value, color) {
|
|
1933
|
+
return VopColorCellSchema.parse({ type: AST_TYPES.VOP_CELL, kind: "color", value, color });
|
|
1934
|
+
}
|
|
1935
|
+
function createVopBoldCell(value) {
|
|
1936
|
+
return VopBoldCellSchema.parse({ type: AST_TYPES.VOP_CELL, kind: "bold", value });
|
|
1937
|
+
}
|
|
1938
|
+
function createVopHighlightCell(value) {
|
|
1939
|
+
return VopHighlightCellSchema.parse({ type: AST_TYPES.VOP_CELL, kind: "highlight", value });
|
|
1940
|
+
}
|
|
1941
|
+
const VopRowSchema = zod.z.object({
|
|
1942
|
+
type: zod.z.literal(AST_TYPES.VOP_ROW),
|
|
1943
|
+
cells: zod.z.array(VopCellSchema).min(1),
|
|
1944
|
+
op: zod.z.enum(VOP_OPERATORS).optional()
|
|
1945
|
+
});
|
|
1946
|
+
function createVopRow(cells, op) {
|
|
1947
|
+
return VopRowSchema.parse({ type: AST_TYPES.VOP_ROW, cells, op });
|
|
1948
|
+
}
|
|
1949
|
+
const VopCarryRowSchema = zod.z.object({
|
|
1950
|
+
type: zod.z.literal(AST_TYPES.VOP_CARRY_ROW),
|
|
1951
|
+
cells: zod.z.array(VopCellSchema).min(1)
|
|
1952
|
+
});
|
|
1953
|
+
function createVopCarryRow(cells) {
|
|
1954
|
+
return VopCarryRowSchema.parse({ type: AST_TYPES.VOP_CARRY_ROW, cells });
|
|
1955
|
+
}
|
|
1956
|
+
const VopSeparatorSchema = zod.z.object({
|
|
1957
|
+
type: zod.z.literal(AST_TYPES.VOP_SEPARATOR)
|
|
1958
|
+
});
|
|
1959
|
+
function createVopSeparator() {
|
|
1960
|
+
return VopSeparatorSchema.parse({ type: AST_TYPES.VOP_SEPARATOR });
|
|
1961
|
+
}
|
|
1962
|
+
const VopBlockItemSchema = zod.z.union([VopRowSchema, VopCarryRowSchema, VopSeparatorSchema]);
|
|
1963
|
+
const VerticalOperationBlockSchema = zod.z.object({
|
|
1964
|
+
type: zod.z.literal(AST_TYPES.VERTICAL_OPERATION_BLOCK),
|
|
1965
|
+
op: zod.z.enum(VOP_OPERATORS).optional(),
|
|
1966
|
+
items: zod.z.array(VopBlockItemSchema).min(1)
|
|
1967
|
+
});
|
|
1968
|
+
function createVerticalOperationBlock(op, items) {
|
|
1969
|
+
return VerticalOperationBlockSchema.parse({
|
|
1970
|
+
type: AST_TYPES.VERTICAL_OPERATION_BLOCK,
|
|
1971
|
+
op,
|
|
1972
|
+
items
|
|
1973
|
+
});
|
|
1974
|
+
}
|
|
1975
|
+
function isVopCell(value) {
|
|
1976
|
+
return VopCellSchema.safeParse(value).success;
|
|
1977
|
+
}
|
|
1978
|
+
function isVopRow(value) {
|
|
1979
|
+
return VopRowSchema.safeParse(value).success;
|
|
1980
|
+
}
|
|
1981
|
+
function isVopCarryRow(value) {
|
|
1982
|
+
return VopCarryRowSchema.safeParse(value).success;
|
|
1983
|
+
}
|
|
1984
|
+
function isVopSeparator(value) {
|
|
1985
|
+
return VopSeparatorSchema.safeParse(value).success;
|
|
1986
|
+
}
|
|
1987
|
+
function isVerticalOperationBlock(value) {
|
|
1988
|
+
return VerticalOperationBlockSchema.safeParse(value).success;
|
|
1989
|
+
}
|
|
1629
1990
|
const DisplayFractionInlineSchema = FractionInlineSchema.refine(
|
|
1630
1991
|
(f) => f.numerator !== void 0 && f.denominator !== void 0,
|
|
1631
1992
|
{ message: "FRACTION inside MATCHER ITEM must be in display mode (both numerator and denominator required)" }
|
|
@@ -1912,7 +2273,7 @@ function isMixedValidation(value) {
|
|
|
1912
2273
|
function isValidationNode(value) {
|
|
1913
2274
|
return ValidationNodeSchema.safeParse(value).success;
|
|
1914
2275
|
}
|
|
1915
|
-
const ContentBlockSchema = zod.z.union([LineBlockSchema, TableBlockSchema, RadioOptionsBlockSchema, NumberLineBlockSchema, MatcherBlockSchema, PairMatcherBlockSchema]);
|
|
2276
|
+
const ContentBlockSchema = zod.z.union([LineBlockSchema, TableBlockSchema, RadioOptionsBlockSchema, NumberLineBlockSchema, MatcherBlockSchema, PairMatcherBlockSchema, VerticalOperationBlockSchema]);
|
|
1916
2277
|
const ExerciseDocumentSchema = zod.z.object({
|
|
1917
2278
|
type: zod.z.literal(AST_TYPES.EXERCISE_DOCUMENT),
|
|
1918
2279
|
question: TextBlockSchema.optional(),
|
|
@@ -2016,6 +2377,7 @@ class NaturalDSLVisitor extends BaseCstVisitor {
|
|
|
2016
2377
|
if (ctx.numberLine) return this.visit(ctx.numberLine);
|
|
2017
2378
|
if (ctx.pairMatcher) return this.visit(ctx.pairMatcher);
|
|
2018
2379
|
if (ctx.matcher) return this.visit(ctx.matcher);
|
|
2380
|
+
if (ctx.verticalOperation) return this.visit(ctx.verticalOperation);
|
|
2019
2381
|
if (ctx.line) return this.visit(ctx.line);
|
|
2020
2382
|
throw new Error("Unknown content block");
|
|
2021
2383
|
}
|
|
@@ -2087,7 +2449,8 @@ class NaturalDSLVisitor extends BaseCstVisitor {
|
|
|
2087
2449
|
const min = params.min !== void 0 ? Number(params.min) : void 0;
|
|
2088
2450
|
const defaultValue2 = params.default !== void 0 ? Number(params.default) : void 0;
|
|
2089
2451
|
const name = params.name;
|
|
2090
|
-
|
|
2452
|
+
const answer = params.answer !== void 0 ? Number(params.answer) : void 0;
|
|
2453
|
+
return createNumberLineCounter(max, min, defaultValue2, name, answer);
|
|
2091
2454
|
}
|
|
2092
2455
|
highlight(ctx) {
|
|
2093
2456
|
const params = this.visit(ctx.parameterList);
|
|
@@ -2154,6 +2517,76 @@ class NaturalDSLVisitor extends BaseCstVisitor {
|
|
|
2154
2517
|
return ctx.matcherItem ? ctx.matcherItem.map((i) => this.visit(i)) : [];
|
|
2155
2518
|
}
|
|
2156
2519
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
2520
|
+
// Vertical Operation Block
|
|
2521
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
2522
|
+
verticalOperation(ctx) {
|
|
2523
|
+
const params = ctx.parameterList ? this.visit(ctx.parameterList) : {};
|
|
2524
|
+
const op = params.op !== void 0 ? normalizeVopOperator(String(params.op)) : void 0;
|
|
2525
|
+
const tagged = [];
|
|
2526
|
+
ctx.vopRow?.forEach((cstNode, i) => {
|
|
2527
|
+
tagged.push({ offset: cstNode.location?.startOffset ?? i, node: this.visit(cstNode) });
|
|
2528
|
+
});
|
|
2529
|
+
ctx.vopCarry?.forEach((cstNode, i) => {
|
|
2530
|
+
tagged.push({ offset: cstNode.location?.startOffset ?? i, node: this.visit(cstNode) });
|
|
2531
|
+
});
|
|
2532
|
+
ctx.vopSep?.forEach((cstNode, i) => {
|
|
2533
|
+
tagged.push({ offset: cstNode.location?.startOffset ?? i, node: this.visit(cstNode) });
|
|
2534
|
+
});
|
|
2535
|
+
tagged.sort((a, b) => a.offset - b.offset);
|
|
2536
|
+
const items = tagged.map((t) => t.node);
|
|
2537
|
+
return createVerticalOperationBlock(op, items);
|
|
2538
|
+
}
|
|
2539
|
+
vopRow(ctx) {
|
|
2540
|
+
const cells = ctx.vopCell ? ctx.vopCell.map((c) => this.visit(c)) : [];
|
|
2541
|
+
const rowParams = ctx.parameterList ? this.visit(ctx.parameterList) : {};
|
|
2542
|
+
const op = rowParams.op !== void 0 ? normalizeVopOperator(String(rowParams.op)) : void 0;
|
|
2543
|
+
return createVopRow(cells, op);
|
|
2544
|
+
}
|
|
2545
|
+
vopCarry(ctx) {
|
|
2546
|
+
const cells = ctx.vopCell ? ctx.vopCell.map((c) => this.visit(c)) : [];
|
|
2547
|
+
return createVopCarryRow(cells);
|
|
2548
|
+
}
|
|
2549
|
+
vopSep(_ctx) {
|
|
2550
|
+
return createVopSeparator();
|
|
2551
|
+
}
|
|
2552
|
+
vopCell(ctx) {
|
|
2553
|
+
if (ctx.NumberLiteral) {
|
|
2554
|
+
const token = ctx.NumberLiteral[0];
|
|
2555
|
+
return createVopDigitCell(token.image);
|
|
2556
|
+
}
|
|
2557
|
+
if (ctx.StringLiteral) {
|
|
2558
|
+
const token = ctx.StringLiteral[0];
|
|
2559
|
+
const value = token.image.slice(1, -1);
|
|
2560
|
+
return createVopLabelCell(value);
|
|
2561
|
+
}
|
|
2562
|
+
if (ctx.Underscore) {
|
|
2563
|
+
return createVopEmptyCell();
|
|
2564
|
+
}
|
|
2565
|
+
if (ctx.INPUT) {
|
|
2566
|
+
const params = this.visit(ctx.parameterList);
|
|
2567
|
+
const name = params.name;
|
|
2568
|
+
if (!name) {
|
|
2569
|
+
throw new Error("INPUT inside VERTICAL_OPERATION requires parameter: name");
|
|
2570
|
+
}
|
|
2571
|
+
const digits = params.digits !== void 0 ? Number(params.digits) : 1;
|
|
2572
|
+
const answer = params.answer;
|
|
2573
|
+
return createVopInputCell(name, digits, answer);
|
|
2574
|
+
}
|
|
2575
|
+
if (ctx.colorText) {
|
|
2576
|
+
const inline = this.visit(ctx.colorText);
|
|
2577
|
+
return createVopColorCell(inline.value, inline.color);
|
|
2578
|
+
}
|
|
2579
|
+
if (ctx.boldText) {
|
|
2580
|
+
const inline = this.visit(ctx.boldText);
|
|
2581
|
+
return createVopBoldCell(inline.value);
|
|
2582
|
+
}
|
|
2583
|
+
if (ctx.textHighlighter) {
|
|
2584
|
+
const inline = this.visit(ctx.textHighlighter);
|
|
2585
|
+
return createVopHighlightCell(inline.value);
|
|
2586
|
+
}
|
|
2587
|
+
throw new Error("Unknown vopCell");
|
|
2588
|
+
}
|
|
2589
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
2157
2590
|
// Line Block
|
|
2158
2591
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
2159
2592
|
line(ctx) {
|
|
@@ -2249,6 +2682,9 @@ class NaturalDSLVisitor extends BaseCstVisitor {
|
|
|
2249
2682
|
if (ctx.inlineInput) {
|
|
2250
2683
|
return this.visit(ctx.inlineInput);
|
|
2251
2684
|
}
|
|
2685
|
+
if (ctx.inlineDragOption) {
|
|
2686
|
+
return this.visit(ctx.inlineDragOption);
|
|
2687
|
+
}
|
|
2252
2688
|
throw new Error("Unknown inline element");
|
|
2253
2689
|
}
|
|
2254
2690
|
textHighlighter(ctx) {
|
|
@@ -2325,6 +2761,11 @@ class NaturalDSLVisitor extends BaseCstVisitor {
|
|
|
2325
2761
|
const digits = params.digits;
|
|
2326
2762
|
return createNumericInput(name, digits, answer, placeholder);
|
|
2327
2763
|
}
|
|
2764
|
+
inlineDragOption(ctx) {
|
|
2765
|
+
const valueToken = ctx.StringLiteral[0];
|
|
2766
|
+
const value = this.extractString(valueToken.image);
|
|
2767
|
+
return createInlineDragOption(value);
|
|
2768
|
+
}
|
|
2328
2769
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
2329
2770
|
// Tools
|
|
2330
2771
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -2519,7 +2960,8 @@ const LayoutItemSchema = zod.z.lazy(
|
|
|
2519
2960
|
GroupItemSchema,
|
|
2520
2961
|
OperatorItemSchema,
|
|
2521
2962
|
TableItemSchema,
|
|
2522
|
-
NumberLineItemSchema
|
|
2963
|
+
NumberLineItemSchema,
|
|
2964
|
+
VerticalOperationItemSchema
|
|
2523
2965
|
])
|
|
2524
2966
|
);
|
|
2525
2967
|
const GroupItemSchema = zod.z.object({
|
|
@@ -2556,7 +2998,8 @@ const NumberLineCounterItemSchema = zod.z.object({
|
|
|
2556
2998
|
min: zod.z.number().int().min(1),
|
|
2557
2999
|
max: zod.z.number().int().positive(),
|
|
2558
3000
|
default: zod.z.number().int().nonnegative().optional(),
|
|
2559
|
-
name: zod.z.string().optional()
|
|
3001
|
+
name: zod.z.string().optional(),
|
|
3002
|
+
answer: zod.z.number().int().positive().optional()
|
|
2560
3003
|
});
|
|
2561
3004
|
const NumberLineHighlightItemSchema = zod.z.object({
|
|
2562
3005
|
id: zod.z.string(),
|
|
@@ -2593,6 +3036,80 @@ const MatcherRowItemSchema = zod.z.object({
|
|
|
2593
3036
|
type: zod.z.literal(JSON_TYPES.MATCHER_ROW),
|
|
2594
3037
|
items: zod.z.array(MatcherItemContentSchema).min(1)
|
|
2595
3038
|
});
|
|
3039
|
+
const VopOperatorJSONSchema = zod.z.enum(["+", "-", "×", "÷", "="]);
|
|
3040
|
+
const VopDigitCellItemSchema = zod.z.object({
|
|
3041
|
+
id: zod.z.string(),
|
|
3042
|
+
type: zod.z.literal(JSON_TYPES.VOP_CELL),
|
|
3043
|
+
kind: zod.z.literal("digit"),
|
|
3044
|
+
value: zod.z.string()
|
|
3045
|
+
});
|
|
3046
|
+
const VopInputCellItemSchema = zod.z.object({
|
|
3047
|
+
id: zod.z.string(),
|
|
3048
|
+
type: zod.z.literal(JSON_TYPES.VOP_CELL),
|
|
3049
|
+
kind: zod.z.literal("input"),
|
|
3050
|
+
widgetId: zod.z.string()
|
|
3051
|
+
});
|
|
3052
|
+
const VopLabelCellItemSchema = zod.z.object({
|
|
3053
|
+
id: zod.z.string(),
|
|
3054
|
+
type: zod.z.literal(JSON_TYPES.VOP_CELL),
|
|
3055
|
+
kind: zod.z.literal("label"),
|
|
3056
|
+
value: zod.z.string()
|
|
3057
|
+
});
|
|
3058
|
+
const VopEmptyCellItemSchema = zod.z.object({
|
|
3059
|
+
id: zod.z.string(),
|
|
3060
|
+
type: zod.z.literal(JSON_TYPES.VOP_CELL),
|
|
3061
|
+
kind: zod.z.literal("empty")
|
|
3062
|
+
});
|
|
3063
|
+
const VopColorCellItemSchema = zod.z.object({
|
|
3064
|
+
id: zod.z.string(),
|
|
3065
|
+
type: zod.z.literal(JSON_TYPES.VOP_CELL),
|
|
3066
|
+
kind: zod.z.literal("color"),
|
|
3067
|
+
value: zod.z.string(),
|
|
3068
|
+
color: zod.z.string()
|
|
3069
|
+
});
|
|
3070
|
+
const VopBoldCellItemSchema = zod.z.object({
|
|
3071
|
+
id: zod.z.string(),
|
|
3072
|
+
type: zod.z.literal(JSON_TYPES.VOP_CELL),
|
|
3073
|
+
kind: zod.z.literal("bold"),
|
|
3074
|
+
value: zod.z.string()
|
|
3075
|
+
});
|
|
3076
|
+
const VopHighlightCellItemSchema = zod.z.object({
|
|
3077
|
+
id: zod.z.string(),
|
|
3078
|
+
type: zod.z.literal(JSON_TYPES.VOP_CELL),
|
|
3079
|
+
kind: zod.z.literal("highlight"),
|
|
3080
|
+
value: zod.z.string()
|
|
3081
|
+
});
|
|
3082
|
+
const VopCellItemSchema = zod.z.discriminatedUnion("kind", [
|
|
3083
|
+
VopDigitCellItemSchema,
|
|
3084
|
+
VopLabelCellItemSchema,
|
|
3085
|
+
VopColorCellItemSchema,
|
|
3086
|
+
VopBoldCellItemSchema,
|
|
3087
|
+
VopHighlightCellItemSchema,
|
|
3088
|
+
VopInputCellItemSchema,
|
|
3089
|
+
VopEmptyCellItemSchema
|
|
3090
|
+
]);
|
|
3091
|
+
const VopRowItemSchema = zod.z.object({
|
|
3092
|
+
id: zod.z.string(),
|
|
3093
|
+
type: zod.z.literal(JSON_TYPES.VOP_ROW),
|
|
3094
|
+
cells: zod.z.array(VopCellItemSchema),
|
|
3095
|
+
op: VopOperatorJSONSchema.optional()
|
|
3096
|
+
});
|
|
3097
|
+
const VopCarryRowItemSchema = zod.z.object({
|
|
3098
|
+
id: zod.z.string(),
|
|
3099
|
+
type: zod.z.literal(JSON_TYPES.VOP_CARRY_ROW),
|
|
3100
|
+
cells: zod.z.array(VopCellItemSchema)
|
|
3101
|
+
});
|
|
3102
|
+
const VopSeparatorItemSchema = zod.z.object({
|
|
3103
|
+
id: zod.z.string(),
|
|
3104
|
+
type: zod.z.literal(JSON_TYPES.VOP_SEPARATOR)
|
|
3105
|
+
});
|
|
3106
|
+
const VerticalOperationItemSchema = zod.z.object({
|
|
3107
|
+
id: zod.z.string(),
|
|
3108
|
+
type: zod.z.literal(JSON_TYPES.VERTICAL_OPERATION),
|
|
3109
|
+
op: VopOperatorJSONSchema.optional(),
|
|
3110
|
+
cols: zod.z.number().int().positive(),
|
|
3111
|
+
items: zod.z.array(zod.z.union([VopRowItemSchema, VopCarryRowItemSchema, VopSeparatorItemSchema]))
|
|
3112
|
+
});
|
|
2596
3113
|
const StringInputJSONSchema = zod.z.object({
|
|
2597
3114
|
type: zod.z.literal(JSON_TYPES.INLINE_INPUT),
|
|
2598
3115
|
inputType: zod.z.literal(INPUT_TYPES.STRING),
|
|
@@ -2613,6 +3130,10 @@ const DragOptionsJSONSchema = zod.z.object({
|
|
|
2613
3130
|
type: zod.z.literal(JSON_TYPES.DRAG_OPTIONS),
|
|
2614
3131
|
options: zod.z.array(zod.z.string())
|
|
2615
3132
|
});
|
|
3133
|
+
const InlineDragOptionJSONSchema = zod.z.object({
|
|
3134
|
+
type: zod.z.literal(JSON_TYPES.INLINE_DRAG_OPTION),
|
|
3135
|
+
value: zod.z.string().min(1)
|
|
3136
|
+
});
|
|
2616
3137
|
const RadioOptionsJSONSchema = zod.z.object({
|
|
2617
3138
|
type: zod.z.literal(JSON_TYPES.RADIO_OPTIONS),
|
|
2618
3139
|
options: zod.z.array(zod.z.string())
|
|
@@ -2663,6 +3184,7 @@ const VisualFractionJSONSchema = zod.z.object({
|
|
|
2663
3184
|
const WidgetJSONSchema = zod.z.union([
|
|
2664
3185
|
InlineInputJSONSchema,
|
|
2665
3186
|
DragOptionsJSONSchema,
|
|
3187
|
+
InlineDragOptionJSONSchema,
|
|
2666
3188
|
RadioOptionsJSONSchema,
|
|
2667
3189
|
NumericKeypadJSONSchema,
|
|
2668
3190
|
FractionJSONSchema,
|
|
@@ -2755,12 +3277,18 @@ function isTableRowItem(item) {
|
|
|
2755
3277
|
function isTableCellItem(item) {
|
|
2756
3278
|
return item.type === JSON_TYPES.TABLE_CELL;
|
|
2757
3279
|
}
|
|
3280
|
+
function isVerticalOperationItem(item) {
|
|
3281
|
+
return item.type === JSON_TYPES.VERTICAL_OPERATION;
|
|
3282
|
+
}
|
|
2758
3283
|
function isInlineInputJSON(widget) {
|
|
2759
3284
|
return widget.type === JSON_TYPES.INLINE_INPUT;
|
|
2760
3285
|
}
|
|
2761
3286
|
function isDragOptionsJSON(widget) {
|
|
2762
3287
|
return widget.type === JSON_TYPES.DRAG_OPTIONS;
|
|
2763
3288
|
}
|
|
3289
|
+
function isInlineDragOptionJSON(widget) {
|
|
3290
|
+
return widget.type === JSON_TYPES.INLINE_DRAG_OPTION;
|
|
3291
|
+
}
|
|
2764
3292
|
function isRadioOptionsJSON(widget) {
|
|
2765
3293
|
return widget.type === JSON_TYPES.RADIO_OPTIONS;
|
|
2766
3294
|
}
|
|
@@ -2783,6 +3311,18 @@ class LayoutIdGenerator {
|
|
|
2783
3311
|
}
|
|
2784
3312
|
function toJSON(ast) {
|
|
2785
3313
|
const idGenerator = new LayoutIdGenerator();
|
|
3314
|
+
const widgets = {};
|
|
3315
|
+
const answers = {};
|
|
3316
|
+
const allInlines = collectAllInlines(ast.lines);
|
|
3317
|
+
const inlineDragIds = /* @__PURE__ */ new Map();
|
|
3318
|
+
{
|
|
3319
|
+
let counter = 0;
|
|
3320
|
+
for (const inline of allInlines) {
|
|
3321
|
+
if (inline.type === AST_TYPES.INLINE_DRAG_OPTION) {
|
|
3322
|
+
inlineDragIds.set(inline, `${INLINE_DRAG_OPT_PREFIX}${counter++}`);
|
|
3323
|
+
}
|
|
3324
|
+
}
|
|
3325
|
+
}
|
|
2786
3326
|
const layout = [];
|
|
2787
3327
|
if (ast.question) {
|
|
2788
3328
|
layout.push({
|
|
@@ -2793,9 +3333,13 @@ function toJSON(ast) {
|
|
|
2793
3333
|
}
|
|
2794
3334
|
for (const block of ast.lines) {
|
|
2795
3335
|
if (block.type === AST_TYPES.TABLE_BLOCK) {
|
|
2796
|
-
layout.push(transformTable(block, idGenerator));
|
|
3336
|
+
layout.push(transformTable(block, idGenerator, inlineDragIds));
|
|
2797
3337
|
} else if (block.type === AST_TYPES.NUMBER_LINE_BLOCK) {
|
|
2798
|
-
|
|
3338
|
+
const nlItem = transformNumberLine(block, idGenerator, inlineDragIds);
|
|
3339
|
+
layout.push(nlItem);
|
|
3340
|
+
if (nlItem.counter?.answer !== void 0) {
|
|
3341
|
+
answers[nlItem.counter.id] = nlItem.counter.answer;
|
|
3342
|
+
}
|
|
2799
3343
|
} else if (block.type === AST_TYPES.MATCHER_BLOCK) {
|
|
2800
3344
|
const matcherBlock = block;
|
|
2801
3345
|
layout.push({
|
|
@@ -2818,6 +3362,8 @@ function toJSON(ast) {
|
|
|
2818
3362
|
id: idGenerator.generate(JSON_TYPES.LINEBREAK),
|
|
2819
3363
|
type: JSON_TYPES.LINEBREAK
|
|
2820
3364
|
});
|
|
3365
|
+
} else if (block.type === AST_TYPES.VERTICAL_OPERATION_BLOCK) {
|
|
3366
|
+
layout.push(transformVerticalOperation(block, idGenerator));
|
|
2821
3367
|
} else if (block.type === AST_TYPES.RADIO_OPTIONS) {
|
|
2822
3368
|
const radioBlock = block;
|
|
2823
3369
|
layout.push({
|
|
@@ -2830,13 +3376,10 @@ function toJSON(ast) {
|
|
|
2830
3376
|
type: JSON_TYPES.LINEBREAK
|
|
2831
3377
|
});
|
|
2832
3378
|
} else {
|
|
2833
|
-
const lineItems = transformLine(block, idGenerator);
|
|
3379
|
+
const lineItems = transformLine(block, idGenerator, inlineDragIds);
|
|
2834
3380
|
layout.push(...lineItems);
|
|
2835
3381
|
}
|
|
2836
3382
|
}
|
|
2837
|
-
const widgets = {};
|
|
2838
|
-
const answers = {};
|
|
2839
|
-
const allInlines = collectAllInlines(ast.lines);
|
|
2840
3383
|
for (const inline of allInlines) {
|
|
2841
3384
|
if (inline.type === AST_TYPES.INLINE_INPUT) {
|
|
2842
3385
|
if (inline.inputType === INPUT_TYPES.STRING) {
|
|
@@ -2870,6 +3413,13 @@ function toJSON(ast) {
|
|
|
2870
3413
|
answers[inline.name] = inline.answer;
|
|
2871
3414
|
}
|
|
2872
3415
|
}
|
|
3416
|
+
if (inline.type === AST_TYPES.INLINE_DRAG_OPTION) {
|
|
3417
|
+
const widgetId = inlineDragIds.get(inline);
|
|
3418
|
+
widgets[widgetId] = {
|
|
3419
|
+
type: JSON_TYPES.INLINE_DRAG_OPTION,
|
|
3420
|
+
value: inline.value
|
|
3421
|
+
};
|
|
3422
|
+
}
|
|
2873
3423
|
if (inline.type === AST_TYPES.VISUAL_FRACTION) {
|
|
2874
3424
|
const answerArr = inline.answer ?? [];
|
|
2875
3425
|
widgets[inline.name] = {
|
|
@@ -2887,6 +3437,27 @@ function toJSON(ast) {
|
|
|
2887
3437
|
}
|
|
2888
3438
|
}
|
|
2889
3439
|
}
|
|
3440
|
+
for (const block of ast.lines) {
|
|
3441
|
+
if (block.type !== AST_TYPES.VERTICAL_OPERATION_BLOCK) continue;
|
|
3442
|
+
const vop = block;
|
|
3443
|
+
for (const item of vop.items) {
|
|
3444
|
+
if (item.type !== AST_TYPES.VOP_ROW && item.type !== AST_TYPES.VOP_CARRY_ROW) continue;
|
|
3445
|
+
for (const cell of item.cells) {
|
|
3446
|
+
if (cell.kind !== "input") continue;
|
|
3447
|
+
if (widgets[cell.name]) {
|
|
3448
|
+
throw new Error(`VERTICAL_OPERATION: duplicate INPUT name "${cell.name}"`);
|
|
3449
|
+
}
|
|
3450
|
+
widgets[cell.name] = {
|
|
3451
|
+
type: JSON_TYPES.INLINE_INPUT,
|
|
3452
|
+
inputType: INPUT_TYPES.NUMERIC,
|
|
3453
|
+
digits: cell.digits
|
|
3454
|
+
};
|
|
3455
|
+
if (cell.answer !== void 0) {
|
|
3456
|
+
answers[cell.name] = cell.answer;
|
|
3457
|
+
}
|
|
3458
|
+
}
|
|
3459
|
+
}
|
|
3460
|
+
}
|
|
2890
3461
|
for (const block of ast.lines) {
|
|
2891
3462
|
if (block.type === AST_TYPES.RADIO_OPTIONS) {
|
|
2892
3463
|
const radioBlock = block;
|
|
@@ -2897,11 +3468,11 @@ function toJSON(ast) {
|
|
|
2897
3468
|
answers[radioBlock.name] = radioBlock.answer;
|
|
2898
3469
|
} else if (block.type === AST_TYPES.MATCHER_BLOCK) {
|
|
2899
3470
|
const matcherBlock = block;
|
|
2900
|
-
widgets[matcherBlock.name] = buildMatcherWidget(matcherBlock, idGenerator);
|
|
3471
|
+
widgets[matcherBlock.name] = buildMatcherWidget(matcherBlock, idGenerator, inlineDragIds);
|
|
2901
3472
|
answers[matcherBlock.name] = matcherBlock.answers;
|
|
2902
3473
|
} else if (block.type === AST_TYPES.PAIR_MATCHER_BLOCK) {
|
|
2903
3474
|
const pmBlock = block;
|
|
2904
|
-
widgets[pmBlock.name] = buildPairMatcherWidget(pmBlock, idGenerator);
|
|
3475
|
+
widgets[pmBlock.name] = buildPairMatcherWidget(pmBlock, idGenerator, inlineDragIds);
|
|
2905
3476
|
answers[pmBlock.name] = pmBlock.answers;
|
|
2906
3477
|
}
|
|
2907
3478
|
}
|
|
@@ -2960,9 +3531,9 @@ function collectAllInlines(blocks) {
|
|
|
2960
3531
|
}
|
|
2961
3532
|
return result;
|
|
2962
3533
|
}
|
|
2963
|
-
function transformNumberLine(numberLine, idGenerator) {
|
|
3534
|
+
function transformNumberLine(numberLine, idGenerator, inlineDragIds) {
|
|
2964
3535
|
const positions = numberLine.positions.map((pos) => {
|
|
2965
|
-
const content = pos.inlines.map((inline) => transformInline(inline, idGenerator));
|
|
3536
|
+
const content = pos.inlines.map((inline) => transformInline(inline, idGenerator, inlineDragIds));
|
|
2966
3537
|
return {
|
|
2967
3538
|
id: idGenerator.generate(JSON_TYPES.NUMBER_LINE_POSITION),
|
|
2968
3539
|
type: JSON_TYPES.NUMBER_LINE_POSITION,
|
|
@@ -2979,7 +3550,8 @@ function transformNumberLine(numberLine, idGenerator) {
|
|
|
2979
3550
|
min: c.min,
|
|
2980
3551
|
max: c.max,
|
|
2981
3552
|
...c.default !== void 0 && { default: c.default },
|
|
2982
|
-
...c.name !== void 0 && { name: c.name }
|
|
3553
|
+
...c.name !== void 0 && { name: c.name },
|
|
3554
|
+
...c.answer !== void 0 && { answer: c.answer }
|
|
2983
3555
|
};
|
|
2984
3556
|
}
|
|
2985
3557
|
let highlights;
|
|
@@ -3009,19 +3581,19 @@ function transformNumberLine(numberLine, idGenerator) {
|
|
|
3009
3581
|
};
|
|
3010
3582
|
return item;
|
|
3011
3583
|
}
|
|
3012
|
-
function mapMatcherItemList(items, idGenerator) {
|
|
3584
|
+
function mapMatcherItemList(items, idGenerator, inlineDragIds) {
|
|
3013
3585
|
return items.map((item) => ({
|
|
3014
3586
|
id: idGenerator.generate(JSON_TYPES.MATCHER_ITEM),
|
|
3015
3587
|
type: JSON_TYPES.MATCHER_ITEM,
|
|
3016
3588
|
itemId: item.itemId,
|
|
3017
|
-
content: item.inlines.map((inline) => transformInline(inline, idGenerator))
|
|
3589
|
+
content: item.inlines.map((inline) => transformInline(inline, idGenerator, inlineDragIds))
|
|
3018
3590
|
}));
|
|
3019
3591
|
}
|
|
3020
|
-
function buildMatcherWidget(matcher, idGenerator) {
|
|
3592
|
+
function buildMatcherWidget(matcher, idGenerator, inlineDragIds) {
|
|
3021
3593
|
const rows = matcher.rows.map((row) => ({
|
|
3022
3594
|
id: idGenerator.generate(JSON_TYPES.MATCHER_ROW),
|
|
3023
3595
|
type: JSON_TYPES.MATCHER_ROW,
|
|
3024
|
-
items: mapMatcherItemList(row.items, idGenerator)
|
|
3596
|
+
items: mapMatcherItemList(row.items, idGenerator, inlineDragIds)
|
|
3025
3597
|
}));
|
|
3026
3598
|
const result = {
|
|
3027
3599
|
type: JSON_TYPES.MATCHER,
|
|
@@ -3033,17 +3605,72 @@ function buildMatcherWidget(matcher, idGenerator) {
|
|
|
3033
3605
|
}
|
|
3034
3606
|
return result;
|
|
3035
3607
|
}
|
|
3036
|
-
function buildPairMatcherWidget(pm, idGenerator) {
|
|
3608
|
+
function buildPairMatcherWidget(pm, idGenerator, inlineDragIds) {
|
|
3037
3609
|
return {
|
|
3038
3610
|
type: JSON_TYPES.PAIR_MATCHER,
|
|
3039
|
-
topItems: mapMatcherItemList(pm.topItems, idGenerator),
|
|
3040
|
-
bottomItems: mapMatcherItemList(pm.bottomItems, idGenerator)
|
|
3611
|
+
topItems: mapMatcherItemList(pm.topItems, idGenerator, inlineDragIds),
|
|
3612
|
+
bottomItems: mapMatcherItemList(pm.bottomItems, idGenerator, inlineDragIds)
|
|
3041
3613
|
};
|
|
3042
3614
|
}
|
|
3043
|
-
function
|
|
3615
|
+
function transformVerticalOperation(vop, idGenerator) {
|
|
3616
|
+
const anyRowHasOp = vop.items.some(
|
|
3617
|
+
(item) => item.type === AST_TYPES.VOP_ROW && item.op !== void 0
|
|
3618
|
+
);
|
|
3619
|
+
if (!anyRowHasOp && vop.op === void 0) {
|
|
3620
|
+
throw new Error("VERTICAL_OPERATION: at least one op is required (set op on the block or on individual ROWs)");
|
|
3621
|
+
}
|
|
3622
|
+
let cols = 1;
|
|
3623
|
+
for (const item of vop.items) {
|
|
3624
|
+
if (item.type === AST_TYPES.VOP_ROW || item.type === AST_TYPES.VOP_CARRY_ROW) {
|
|
3625
|
+
cols = Math.max(cols, item.cells.length);
|
|
3626
|
+
}
|
|
3627
|
+
}
|
|
3628
|
+
function buildCells(row) {
|
|
3629
|
+
return row.cells.map((cell) => {
|
|
3630
|
+
const id = idGenerator.generate(JSON_TYPES.VOP_CELL);
|
|
3631
|
+
if (cell.kind === "digit") return { id, type: JSON_TYPES.VOP_CELL, kind: "digit", value: cell.value };
|
|
3632
|
+
if (cell.kind === "label") return { id, type: JSON_TYPES.VOP_CELL, kind: "label", value: cell.value };
|
|
3633
|
+
if (cell.kind === "color") return { id, type: JSON_TYPES.VOP_CELL, kind: "color", value: cell.value, color: cell.color };
|
|
3634
|
+
if (cell.kind === "bold") return { id, type: JSON_TYPES.VOP_CELL, kind: "bold", value: cell.value };
|
|
3635
|
+
if (cell.kind === "highlight") return { id, type: JSON_TYPES.VOP_CELL, kind: "highlight", value: cell.value };
|
|
3636
|
+
if (cell.kind === "input") return { id, type: JSON_TYPES.VOP_CELL, kind: "input", widgetId: cell.name };
|
|
3637
|
+
return { id, type: JSON_TYPES.VOP_CELL, kind: "empty" };
|
|
3638
|
+
});
|
|
3639
|
+
}
|
|
3640
|
+
const items = vop.items.map((item) => {
|
|
3641
|
+
if (item.type === AST_TYPES.VOP_SEPARATOR) {
|
|
3642
|
+
return {
|
|
3643
|
+
id: idGenerator.generate(JSON_TYPES.VOP_SEPARATOR),
|
|
3644
|
+
type: JSON_TYPES.VOP_SEPARATOR
|
|
3645
|
+
};
|
|
3646
|
+
}
|
|
3647
|
+
if (item.type === AST_TYPES.VOP_CARRY_ROW) {
|
|
3648
|
+
return {
|
|
3649
|
+
id: idGenerator.generate(JSON_TYPES.VOP_CARRY_ROW),
|
|
3650
|
+
type: JSON_TYPES.VOP_CARRY_ROW,
|
|
3651
|
+
cells: buildCells(item)
|
|
3652
|
+
};
|
|
3653
|
+
}
|
|
3654
|
+
const row = item;
|
|
3655
|
+
return {
|
|
3656
|
+
id: idGenerator.generate(JSON_TYPES.VOP_ROW),
|
|
3657
|
+
type: JSON_TYPES.VOP_ROW,
|
|
3658
|
+
cells: buildCells(row),
|
|
3659
|
+
...row.op !== void 0 && { op: row.op }
|
|
3660
|
+
};
|
|
3661
|
+
});
|
|
3662
|
+
return {
|
|
3663
|
+
id: idGenerator.generate(JSON_TYPES.VERTICAL_OPERATION),
|
|
3664
|
+
type: JSON_TYPES.VERTICAL_OPERATION,
|
|
3665
|
+
op: vop.op,
|
|
3666
|
+
cols,
|
|
3667
|
+
items
|
|
3668
|
+
};
|
|
3669
|
+
}
|
|
3670
|
+
function transformTable(table, idGenerator, inlineDragIds) {
|
|
3044
3671
|
const rows = table.rows.map((row) => {
|
|
3045
3672
|
const cells = row.cells.map((cell) => {
|
|
3046
|
-
const items = cell.inlines.map((inline) => transformInline(inline, idGenerator));
|
|
3673
|
+
const items = cell.inlines.map((inline) => transformInline(inline, idGenerator, inlineDragIds));
|
|
3047
3674
|
const cellItem = {
|
|
3048
3675
|
id: idGenerator.generate(JSON_TYPES.TABLE_CELL),
|
|
3049
3676
|
type: JSON_TYPES.TABLE_CELL,
|
|
@@ -3067,10 +3694,10 @@ function transformTable(table, idGenerator) {
|
|
|
3067
3694
|
rows
|
|
3068
3695
|
};
|
|
3069
3696
|
}
|
|
3070
|
-
function transformLine(line, idGenerator) {
|
|
3697
|
+
function transformLine(line, idGenerator, inlineDragIds) {
|
|
3071
3698
|
const items = [];
|
|
3072
3699
|
for (const inline of line.inlines) {
|
|
3073
|
-
items.push(transformInline(inline, idGenerator));
|
|
3700
|
+
items.push(transformInline(inline, idGenerator, inlineDragIds));
|
|
3074
3701
|
}
|
|
3075
3702
|
items.push({
|
|
3076
3703
|
id: idGenerator.generate(JSON_TYPES.LINEBREAK),
|
|
@@ -3078,7 +3705,7 @@ function transformLine(line, idGenerator) {
|
|
|
3078
3705
|
});
|
|
3079
3706
|
return items;
|
|
3080
3707
|
}
|
|
3081
|
-
function transformInline(inline, idGenerator) {
|
|
3708
|
+
function transformInline(inline, idGenerator, inlineDragIds) {
|
|
3082
3709
|
switch (inline.type) {
|
|
3083
3710
|
case AST_TYPES.FREE_TEXT:
|
|
3084
3711
|
const trimmed = inline.value.trim();
|
|
@@ -3150,6 +3777,12 @@ function transformInline(inline, idGenerator) {
|
|
|
3150
3777
|
type: JSON_TYPES.WIDGET_REF,
|
|
3151
3778
|
widgetId: inline.name
|
|
3152
3779
|
};
|
|
3780
|
+
case AST_TYPES.INLINE_DRAG_OPTION:
|
|
3781
|
+
return {
|
|
3782
|
+
id: idGenerator.generate(JSON_TYPES.WIDGET_REF),
|
|
3783
|
+
type: JSON_TYPES.WIDGET_REF,
|
|
3784
|
+
widgetId: inlineDragIds.get(inline)
|
|
3785
|
+
};
|
|
3153
3786
|
default:
|
|
3154
3787
|
throw new Error(`Unknown inline type: ${inline.type}`);
|
|
3155
3788
|
}
|
|
@@ -13314,7 +13947,7 @@ const InlineInput = ({
|
|
|
13314
13947
|
id: widgetId,
|
|
13315
13948
|
accepts: ["drag-option"],
|
|
13316
13949
|
onDrop: handleDrop,
|
|
13317
|
-
className: "inline-input-droppable",
|
|
13950
|
+
className: "inline-widget inline-input-droppable",
|
|
13318
13951
|
children: isDropped && value !== "" ? (
|
|
13319
13952
|
// Show as chip only when value came from drag-drop
|
|
13320
13953
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -13346,6 +13979,32 @@ const InlineInput = ({
|
|
|
13346
13979
|
}
|
|
13347
13980
|
);
|
|
13348
13981
|
};
|
|
13982
|
+
const SingleDragOption = ({
|
|
13983
|
+
optionId,
|
|
13984
|
+
value,
|
|
13985
|
+
onOptionClick,
|
|
13986
|
+
className = ""
|
|
13987
|
+
}) => {
|
|
13988
|
+
const { usedOptionIds } = useExerciseDnd();
|
|
13989
|
+
const isUsed = usedOptionIds.has(optionId);
|
|
13990
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `drag-option-slot ${className}`.trim(), children: isUsed ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "drag-option-empty", "aria-hidden": "true" }) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
13991
|
+
Draggable,
|
|
13992
|
+
{
|
|
13993
|
+
id: optionId,
|
|
13994
|
+
type: "drag-option",
|
|
13995
|
+
data: { value, label: value },
|
|
13996
|
+
className: "drag-option-wrapper",
|
|
13997
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
13998
|
+
"button",
|
|
13999
|
+
{
|
|
14000
|
+
className: "drag-option",
|
|
14001
|
+
onClick: () => onOptionClick?.(value),
|
|
14002
|
+
children: value
|
|
14003
|
+
}
|
|
14004
|
+
)
|
|
14005
|
+
}
|
|
14006
|
+
) });
|
|
14007
|
+
};
|
|
13349
14008
|
const PillBackground = ({
|
|
13350
14009
|
width = "100%",
|
|
13351
14010
|
minHeight = "72px",
|
|
@@ -13418,36 +14077,32 @@ const DragOptions = ({
|
|
|
13418
14077
|
options,
|
|
13419
14078
|
onOptionClick
|
|
13420
14079
|
}) => {
|
|
13421
|
-
const { usedOptionIds } = useExerciseDnd();
|
|
13422
14080
|
return /* @__PURE__ */ jsxRuntime.jsx(PillBackground, { children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "drag-options", children: options.map((option, idx) => {
|
|
13423
14081
|
const optionId = `drag-option-${idx}`;
|
|
13424
14082
|
const optionValue = String(option);
|
|
13425
|
-
|
|
13426
|
-
|
|
13427
|
-
|
|
13428
|
-
|
|
13429
|
-
|
|
13430
|
-
|
|
13431
|
-
|
|
13432
|
-
|
|
13433
|
-
|
|
13434
|
-
id: optionId,
|
|
13435
|
-
type: "drag-option",
|
|
13436
|
-
data: { value: optionValue, label: optionValue },
|
|
13437
|
-
className: "drag-option-wrapper",
|
|
13438
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
13439
|
-
"button",
|
|
13440
|
-
{
|
|
13441
|
-
className: "drag-option",
|
|
13442
|
-
onClick: () => onOptionClick?.(optionValue),
|
|
13443
|
-
children: optionValue
|
|
13444
|
-
}
|
|
13445
|
-
)
|
|
13446
|
-
}
|
|
13447
|
-
)
|
|
13448
|
-
) }, optionId);
|
|
14083
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
14084
|
+
SingleDragOption,
|
|
14085
|
+
{
|
|
14086
|
+
optionId,
|
|
14087
|
+
value: optionValue,
|
|
14088
|
+
onOptionClick
|
|
14089
|
+
},
|
|
14090
|
+
optionId
|
|
14091
|
+
);
|
|
13449
14092
|
}) }) });
|
|
13450
14093
|
};
|
|
14094
|
+
const InlineDragOption = ({
|
|
14095
|
+
widgetId,
|
|
14096
|
+
widget
|
|
14097
|
+
}) => {
|
|
14098
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "inline-widget", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
14099
|
+
SingleDragOption,
|
|
14100
|
+
{
|
|
14101
|
+
optionId: widgetId,
|
|
14102
|
+
value: widget.value
|
|
14103
|
+
}
|
|
14104
|
+
) });
|
|
14105
|
+
};
|
|
13451
14106
|
const NumericKeypad = ({
|
|
13452
14107
|
value,
|
|
13453
14108
|
includeOperators = false,
|
|
@@ -13542,7 +14197,7 @@ const FractionInput = ({
|
|
|
13542
14197
|
}
|
|
13543
14198
|
);
|
|
13544
14199
|
};
|
|
13545
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "fraction", role: "math", "aria-label": `fraction ${widgetId}`, children: [
|
|
14200
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-widget fraction", role: "math", "aria-label": `fraction ${widgetId}`, children: [
|
|
13546
14201
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "fraction-numerator", children: renderSlot(numKey, numerator, numValue, "numerator", numValidationStatus) }),
|
|
13547
14202
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "fraction-bar", "aria-hidden": "true" }),
|
|
13548
14203
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "fraction-denominator", children: renderSlot(denKey, denominator, denValue, "denominator", denValidationStatus) })
|
|
@@ -13974,7 +14629,7 @@ const VisualFraction = ({
|
|
|
13974
14629
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
13975
14630
|
"div",
|
|
13976
14631
|
{
|
|
13977
|
-
className: `visual-fraction-widget visual-fraction-widget--${shape}${statusClass}`,
|
|
14632
|
+
className: `inline-widget visual-fraction-widget visual-fraction-widget--${shape}${statusClass}`,
|
|
13978
14633
|
style: rootStyle,
|
|
13979
14634
|
role: "group",
|
|
13980
14635
|
"aria-label": `${divisions} part ${shape} fraction`,
|
|
@@ -14595,6 +15250,9 @@ function isInlineInputWidget(widget) {
|
|
|
14595
15250
|
function isDragOptionsWidget(widget) {
|
|
14596
15251
|
return widget.type === "drag-options";
|
|
14597
15252
|
}
|
|
15253
|
+
function isInlineDragOptionWidget(widget) {
|
|
15254
|
+
return widget.type === "inline-drag-option";
|
|
15255
|
+
}
|
|
14598
15256
|
function isRadioOptionsWidget(widget) {
|
|
14599
15257
|
return widget.type === "radio-options";
|
|
14600
15258
|
}
|
|
@@ -14657,6 +15315,15 @@ const WidgetRenderer = ({
|
|
|
14657
15315
|
}
|
|
14658
15316
|
);
|
|
14659
15317
|
}
|
|
15318
|
+
if (isInlineDragOptionWidget(widget)) {
|
|
15319
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
15320
|
+
InlineDragOption,
|
|
15321
|
+
{
|
|
15322
|
+
widgetId,
|
|
15323
|
+
widget
|
|
15324
|
+
}
|
|
15325
|
+
);
|
|
15326
|
+
}
|
|
14660
15327
|
if (isRadioOptionsWidget(widget)) {
|
|
14661
15328
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
14662
15329
|
RadioOptions,
|
|
@@ -15373,6 +16040,13 @@ const NumberLineRenderer = ({
|
|
|
15373
16040
|
React.useEffect(() => {
|
|
15374
16041
|
setCounterCount(defaultCounterCount);
|
|
15375
16042
|
}, [counter]);
|
|
16043
|
+
const onChangeRef = React.useRef(onChange);
|
|
16044
|
+
onChangeRef.current = onChange;
|
|
16045
|
+
React.useEffect(() => {
|
|
16046
|
+
if (counter !== void 0) {
|
|
16047
|
+
onChangeRef.current(counter.id, String(counterCount));
|
|
16048
|
+
}
|
|
16049
|
+
}, [counterCount, counter?.id]);
|
|
15376
16050
|
const effectiveStep = React.useMemo(() => {
|
|
15377
16051
|
if (counter !== void 0 && counterCount > 0) {
|
|
15378
16052
|
return cleanFloat((max - min) / counterCount);
|
|
@@ -15406,10 +16080,17 @@ const NumberLineRenderer = ({
|
|
|
15406
16080
|
[visiblePositions]
|
|
15407
16081
|
);
|
|
15408
16082
|
const { positionContentHeight, getPositionContentRef } = useMeasuredPositionContentHeight(visiblePositionIds);
|
|
16083
|
+
const nlValidationStatus = React.useMemo(() => {
|
|
16084
|
+
if (!validationDetails || counter === void 0) return null;
|
|
16085
|
+
if (counter.answer === void 0) return null;
|
|
16086
|
+
const counterValidation = validationDetails[counter.id];
|
|
16087
|
+
if (counterValidation === void 0) return null;
|
|
16088
|
+
return counterValidation.isCorrect ? "correct" : "incorrect";
|
|
16089
|
+
}, [validationDetails, counter]);
|
|
15409
16090
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
15410
16091
|
"div",
|
|
15411
16092
|
{
|
|
15412
|
-
className: `number-line${isAxisDragMode ? " number-line--drag-mode" : ""}${lineStyle === "line" ? " number-line--style-line" : ""}`,
|
|
16093
|
+
className: `number-line${isAxisDragMode ? " number-line--drag-mode" : ""}${lineStyle === "line" ? " number-line--style-line" : ""}${nlValidationStatus ? ` number-line--${nlValidationStatus}` : ""}`,
|
|
15413
16094
|
style: {
|
|
15414
16095
|
[CSS_VAR_SIDE_PAD]: `${SIDE_PAD}%`,
|
|
15415
16096
|
...positionContentHeight && { "--nl-position-content-height": positionContentHeight }
|
|
@@ -15676,6 +16357,126 @@ const LayoutRenderer = ({
|
|
|
15676
16357
|
},
|
|
15677
16358
|
resetKey
|
|
15678
16359
|
);
|
|
16360
|
+
case JSON_TYPES.VERTICAL_OPERATION: {
|
|
16361
|
+
const vop = item;
|
|
16362
|
+
const anyRowHasOp = vop.items.some(
|
|
16363
|
+
(x) => x.type === JSON_TYPES.VOP_ROW && x.op !== void 0
|
|
16364
|
+
);
|
|
16365
|
+
let operatorRowIdx = -1;
|
|
16366
|
+
if (!anyRowHasOp && vop.op !== void 0) {
|
|
16367
|
+
const firstSepIdx = vop.items.findIndex((x) => x.type === JSON_TYPES.VOP_SEPARATOR);
|
|
16368
|
+
for (let i = (firstSepIdx === -1 ? vop.items.length : firstSepIdx) - 1; i >= 0; i--) {
|
|
16369
|
+
const curr = vop.items[i];
|
|
16370
|
+
if (curr && curr.type === JSON_TYPES.VOP_ROW) {
|
|
16371
|
+
operatorRowIdx = i;
|
|
16372
|
+
break;
|
|
16373
|
+
}
|
|
16374
|
+
}
|
|
16375
|
+
}
|
|
16376
|
+
const totalCols = vop.cols + 1;
|
|
16377
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
16378
|
+
"div",
|
|
16379
|
+
{
|
|
16380
|
+
className: "vop",
|
|
16381
|
+
style: { "--vop-cols": totalCols },
|
|
16382
|
+
dir: "ltr",
|
|
16383
|
+
children: vop.items.map((child, idx) => {
|
|
16384
|
+
if (child.type === JSON_TYPES.VOP_SEPARATOR) {
|
|
16385
|
+
const sep = child;
|
|
16386
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "vop-sep" }, sep.id);
|
|
16387
|
+
}
|
|
16388
|
+
if (child.type === JSON_TYPES.VOP_CARRY_ROW) {
|
|
16389
|
+
const carry = child;
|
|
16390
|
+
const carryLeadingEmpty = Math.max(0, vop.cols - carry.cells.length);
|
|
16391
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "vop-row vop-carry-row", children: [
|
|
16392
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "vop-op-cell" }),
|
|
16393
|
+
Array.from({ length: carryLeadingEmpty }).map((_, i) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "vop-cell vop-carry-cell-empty" }, `cpad-${i}`)),
|
|
16394
|
+
carry.cells.map((cell) => {
|
|
16395
|
+
if (cell.kind === "digit") {
|
|
16396
|
+
return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "vop-cell vop-carry-cell-digit", children: cell.value }, cell.id);
|
|
16397
|
+
}
|
|
16398
|
+
if (cell.kind === "label") {
|
|
16399
|
+
return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "vop-cell vop-carry-cell-label", children: cell.value }, cell.id);
|
|
16400
|
+
}
|
|
16401
|
+
if (cell.kind === "color") {
|
|
16402
|
+
return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "vop-cell vop-carry-cell-color", style: { color: cell.color }, children: cell.value }, cell.id);
|
|
16403
|
+
}
|
|
16404
|
+
if (cell.kind === "bold") {
|
|
16405
|
+
return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "vop-cell vop-carry-cell-bold", children: cell.value }, cell.id);
|
|
16406
|
+
}
|
|
16407
|
+
if (cell.kind === "highlight") {
|
|
16408
|
+
return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "vop-cell vop-carry-cell-highlight", children: /* @__PURE__ */ jsxRuntime.jsx("mark", { children: cell.value }) }, cell.id);
|
|
16409
|
+
}
|
|
16410
|
+
if (cell.kind === "empty") {
|
|
16411
|
+
return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "vop-cell vop-carry-cell-empty" }, cell.id);
|
|
16412
|
+
}
|
|
16413
|
+
const widget = widgets[cell.widgetId];
|
|
16414
|
+
if (!widget) return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "vop-cell" }, cell.id);
|
|
16415
|
+
const isCorrect = validationDetails?.[cell.widgetId]?.isCorrect;
|
|
16416
|
+
const validationStatus = isCorrect === true ? "correct" : isCorrect === false ? "incorrect" : null;
|
|
16417
|
+
return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "vop-cell vop-carry-cell-input", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
16418
|
+
WidgetRenderer,
|
|
16419
|
+
{
|
|
16420
|
+
widget,
|
|
16421
|
+
widgetId: cell.widgetId,
|
|
16422
|
+
value: values[cell.widgetId] || "",
|
|
16423
|
+
isDropped: droppedInputs.has(cell.widgetId),
|
|
16424
|
+
validationStatus,
|
|
16425
|
+
onChange,
|
|
16426
|
+
onInputFocus
|
|
16427
|
+
}
|
|
16428
|
+
) }, cell.id);
|
|
16429
|
+
})
|
|
16430
|
+
] }, carry.id);
|
|
16431
|
+
}
|
|
16432
|
+
const row = child;
|
|
16433
|
+
const showOperator = row.op !== void 0 || idx === operatorRowIdx;
|
|
16434
|
+
const displayOp = row.op ?? vop.op;
|
|
16435
|
+
const leadingEmpty = vop.cols - row.cells.length;
|
|
16436
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "vop-row", children: [
|
|
16437
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "vop-op-cell", children: showOperator ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "operator vop-op", children: displayOp }) : null }),
|
|
16438
|
+
Array.from({ length: leadingEmpty }).map((_, i) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "vop-cell vop-cell-empty" }, `pad-${i}`)),
|
|
16439
|
+
row.cells.map((cell) => {
|
|
16440
|
+
if (cell.kind === "digit") {
|
|
16441
|
+
return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "vop-cell vop-cell-digit", children: cell.value }, cell.id);
|
|
16442
|
+
}
|
|
16443
|
+
if (cell.kind === "label") {
|
|
16444
|
+
return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "vop-cell vop-cell-label", children: cell.value }, cell.id);
|
|
16445
|
+
}
|
|
16446
|
+
if (cell.kind === "color") {
|
|
16447
|
+
return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "vop-cell vop-cell-color", style: { color: cell.color }, children: cell.value }, cell.id);
|
|
16448
|
+
}
|
|
16449
|
+
if (cell.kind === "bold") {
|
|
16450
|
+
return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "vop-cell vop-cell-bold", children: cell.value }, cell.id);
|
|
16451
|
+
}
|
|
16452
|
+
if (cell.kind === "highlight") {
|
|
16453
|
+
return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "vop-cell vop-cell-highlight", children: /* @__PURE__ */ jsxRuntime.jsx("mark", { children: cell.value }) }, cell.id);
|
|
16454
|
+
}
|
|
16455
|
+
if (cell.kind === "empty") {
|
|
16456
|
+
return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "vop-cell vop-cell-empty" }, cell.id);
|
|
16457
|
+
}
|
|
16458
|
+
const widget = widgets[cell.widgetId];
|
|
16459
|
+
if (!widget) return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "vop-cell" }, cell.id);
|
|
16460
|
+
const isCorrect = validationDetails?.[cell.widgetId]?.isCorrect;
|
|
16461
|
+
const validationStatus = isCorrect === true ? "correct" : isCorrect === false ? "incorrect" : null;
|
|
16462
|
+
return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "vop-cell vop-cell-input", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
16463
|
+
WidgetRenderer,
|
|
16464
|
+
{
|
|
16465
|
+
widget,
|
|
16466
|
+
widgetId: cell.widgetId,
|
|
16467
|
+
value: values[cell.widgetId] || "",
|
|
16468
|
+
isDropped: droppedInputs.has(cell.widgetId),
|
|
16469
|
+
validationStatus,
|
|
16470
|
+
onChange,
|
|
16471
|
+
onInputFocus
|
|
16472
|
+
}
|
|
16473
|
+
) }, cell.id);
|
|
16474
|
+
})
|
|
16475
|
+
] }, row.id);
|
|
16476
|
+
})
|
|
16477
|
+
}
|
|
16478
|
+
);
|
|
16479
|
+
}
|
|
15679
16480
|
default:
|
|
15680
16481
|
return null;
|
|
15681
16482
|
}
|
|
@@ -15769,6 +16570,13 @@ const ExerciseRenderer = React.forwardRef(({
|
|
|
15769
16570
|
onSubmit?.(values);
|
|
15770
16571
|
};
|
|
15771
16572
|
const lineGroups = React.useMemo(() => {
|
|
16573
|
+
const isRTLInlineWidget = (item) => {
|
|
16574
|
+
if (item.type === JSON_TYPES.WIDGET_REF) {
|
|
16575
|
+
const widget = json.widgets[item.widgetId];
|
|
16576
|
+
return !!widget && RTL_INLINE_WIDGET_TYPES.has(widget.type);
|
|
16577
|
+
}
|
|
16578
|
+
return RTL_INLINE_WIDGET_TYPES.has(item.type);
|
|
16579
|
+
};
|
|
15772
16580
|
const groups = [];
|
|
15773
16581
|
let currentGroup = [];
|
|
15774
16582
|
for (const item of json.layout) {
|
|
@@ -15776,6 +16584,7 @@ const ExerciseRenderer = React.forwardRef(({
|
|
|
15776
16584
|
groups.push({
|
|
15777
16585
|
items: [item],
|
|
15778
16586
|
hasArabic: itemContainsArabic(item),
|
|
16587
|
+
hasRTLInlineWidget: false,
|
|
15779
16588
|
subGroups: []
|
|
15780
16589
|
// Question text doesn't need sub-grouping
|
|
15781
16590
|
});
|
|
@@ -15784,13 +16593,15 @@ const ExerciseRenderer = React.forwardRef(({
|
|
|
15784
16593
|
if (item.type === JSON_TYPES.TABLE) {
|
|
15785
16594
|
if (currentGroup.length > 0) {
|
|
15786
16595
|
const hasArabic = currentGroup.some((item2) => itemContainsArabic(item2));
|
|
16596
|
+
const hasRTLInlineWidget = currentGroup.some(isRTLInlineWidget);
|
|
15787
16597
|
const subGroups = groupLineItems(currentGroup);
|
|
15788
|
-
groups.push({ items: currentGroup, hasArabic, subGroups });
|
|
16598
|
+
groups.push({ items: currentGroup, hasArabic, hasRTLInlineWidget, subGroups });
|
|
15789
16599
|
currentGroup = [];
|
|
15790
16600
|
}
|
|
15791
16601
|
groups.push({
|
|
15792
16602
|
items: [item],
|
|
15793
16603
|
hasArabic: false,
|
|
16604
|
+
hasRTLInlineWidget: false,
|
|
15794
16605
|
subGroups: []
|
|
15795
16606
|
});
|
|
15796
16607
|
continue;
|
|
@@ -15798,13 +16609,31 @@ const ExerciseRenderer = React.forwardRef(({
|
|
|
15798
16609
|
if (item.type === JSON_TYPES.NUMBER_LINE) {
|
|
15799
16610
|
if (currentGroup.length > 0) {
|
|
15800
16611
|
const hasArabic = currentGroup.some((item2) => itemContainsArabic(item2));
|
|
16612
|
+
const hasRTLInlineWidget = currentGroup.some(isRTLInlineWidget);
|
|
16613
|
+
const subGroups = groupLineItems(currentGroup);
|
|
16614
|
+
groups.push({ items: currentGroup, hasArabic, hasRTLInlineWidget, subGroups });
|
|
16615
|
+
currentGroup = [];
|
|
16616
|
+
}
|
|
16617
|
+
groups.push({
|
|
16618
|
+
items: [item],
|
|
16619
|
+
hasArabic: false,
|
|
16620
|
+
hasRTLInlineWidget: false,
|
|
16621
|
+
subGroups: []
|
|
16622
|
+
});
|
|
16623
|
+
continue;
|
|
16624
|
+
}
|
|
16625
|
+
if (item.type === JSON_TYPES.VERTICAL_OPERATION) {
|
|
16626
|
+
if (currentGroup.length > 0) {
|
|
16627
|
+
const hasArabic = currentGroup.some((item2) => itemContainsArabic(item2));
|
|
16628
|
+
const hasRTLInlineWidget = currentGroup.some(isRTLInlineWidget);
|
|
15801
16629
|
const subGroups = groupLineItems(currentGroup);
|
|
15802
|
-
groups.push({ items: currentGroup, hasArabic, subGroups });
|
|
16630
|
+
groups.push({ items: currentGroup, hasArabic, hasRTLInlineWidget, subGroups });
|
|
15803
16631
|
currentGroup = [];
|
|
15804
16632
|
}
|
|
15805
16633
|
groups.push({
|
|
15806
16634
|
items: [item],
|
|
15807
16635
|
hasArabic: false,
|
|
16636
|
+
hasRTLInlineWidget: false,
|
|
15808
16637
|
subGroups: []
|
|
15809
16638
|
});
|
|
15810
16639
|
continue;
|
|
@@ -15812,8 +16641,9 @@ const ExerciseRenderer = React.forwardRef(({
|
|
|
15812
16641
|
if (item.type === JSON_TYPES.LINEBREAK) {
|
|
15813
16642
|
if (currentGroup.length > 0) {
|
|
15814
16643
|
const hasArabic = currentGroup.some((item2) => itemContainsArabic(item2));
|
|
16644
|
+
const hasRTLInlineWidget = currentGroup.some(isRTLInlineWidget);
|
|
15815
16645
|
const subGroups = groupLineItems(currentGroup);
|
|
15816
|
-
groups.push({ items: currentGroup, hasArabic, subGroups });
|
|
16646
|
+
groups.push({ items: currentGroup, hasArabic, hasRTLInlineWidget, subGroups });
|
|
15817
16647
|
currentGroup = [];
|
|
15818
16648
|
}
|
|
15819
16649
|
} else {
|
|
@@ -15822,11 +16652,12 @@ const ExerciseRenderer = React.forwardRef(({
|
|
|
15822
16652
|
}
|
|
15823
16653
|
if (currentGroup.length > 0) {
|
|
15824
16654
|
const hasArabic = currentGroup.some((item) => itemContainsArabic(item));
|
|
16655
|
+
const hasRTLInlineWidget = currentGroup.some(isRTLInlineWidget);
|
|
15825
16656
|
const subGroups = groupLineItems(currentGroup);
|
|
15826
|
-
groups.push({ items: currentGroup, hasArabic, subGroups });
|
|
16657
|
+
groups.push({ items: currentGroup, hasArabic, hasRTLInlineWidget, subGroups });
|
|
15827
16658
|
}
|
|
15828
16659
|
return groups;
|
|
15829
|
-
}, [json.layout]);
|
|
16660
|
+
}, [json.layout, json.widgets]);
|
|
15830
16661
|
const droppedInputs = React.useMemo(() => new Set(droppedSources.keys()), [droppedSources]);
|
|
15831
16662
|
const dragOptionsWidget = json.widgets["_drag_options"];
|
|
15832
16663
|
const rulerWidget = json.widgets["_ruler"];
|
|
@@ -15844,7 +16675,8 @@ const ExerciseRenderer = React.forwardRef(({
|
|
|
15844
16675
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "exercise-layout", children: lineGroups.map((group, groupIdx) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
15845
16676
|
"div",
|
|
15846
16677
|
{
|
|
15847
|
-
className:
|
|
16678
|
+
className: "exercise-line",
|
|
16679
|
+
dir: group.hasArabic && group.hasRTLInlineWidget ? "rtl" : void 0,
|
|
15848
16680
|
children: group.subGroups.length > 0 ? (
|
|
15849
16681
|
// Render sub-groups for regular lines
|
|
15850
16682
|
group.subGroups.map((subGroup, subIdx) => subGroup.isTextGroup ? (
|
|
@@ -15988,6 +16820,7 @@ exports.HighlightedTextInlineSchema = HighlightedTextInlineSchema;
|
|
|
15988
16820
|
exports.HighlightedTextSchema = HighlightedTextSchema;
|
|
15989
16821
|
exports.ImageInlineSchema = ImageInlineSchema;
|
|
15990
16822
|
exports.ImageItemSchema = ImageItemSchema;
|
|
16823
|
+
exports.InlineDragOptionSchema = InlineDragOptionSchema;
|
|
15991
16824
|
exports.InlineInputJSONSchema = InlineInputJSONSchema;
|
|
15992
16825
|
exports.InlineInputWidgetSchema = InlineInputWidgetSchema;
|
|
15993
16826
|
exports.InlineNodeSchema = InlineNodeSchema;
|
|
@@ -16029,10 +16862,31 @@ exports.VISUAL_FRACTION_DEFAULT_SIZE = VISUAL_FRACTION_DEFAULT_SIZE;
|
|
|
16029
16862
|
exports.VISUAL_FRACTION_MAX_DIVISIONS = VISUAL_FRACTION_MAX_DIVISIONS;
|
|
16030
16863
|
exports.VISUAL_FRACTION_SHAPE = VISUAL_FRACTION_SHAPE;
|
|
16031
16864
|
exports.VISUAL_FRACTION_SHAPES = VISUAL_FRACTION_SHAPES;
|
|
16865
|
+
exports.VOP_OPERATORS = VOP_OPERATORS;
|
|
16032
16866
|
exports.ValidationError = ValidationError;
|
|
16033
16867
|
exports.ValidationJSONSchema = ValidationJSONSchema;
|
|
16034
16868
|
exports.ValidationNodeSchema = ValidationNodeSchema;
|
|
16869
|
+
exports.VerticalOperationBlockSchema = VerticalOperationBlockSchema;
|
|
16870
|
+
exports.VerticalOperationItemSchema = VerticalOperationItemSchema;
|
|
16035
16871
|
exports.VisualFractionInlineSchema = VisualFractionInlineSchema;
|
|
16872
|
+
exports.VopBoldCellItemSchema = VopBoldCellItemSchema;
|
|
16873
|
+
exports.VopBoldCellSchema = VopBoldCellSchema;
|
|
16874
|
+
exports.VopCarryRowItemSchema = VopCarryRowItemSchema;
|
|
16875
|
+
exports.VopCarryRowSchema = VopCarryRowSchema;
|
|
16876
|
+
exports.VopCellItemSchema = VopCellItemSchema;
|
|
16877
|
+
exports.VopCellSchema = VopCellSchema;
|
|
16878
|
+
exports.VopColorCellItemSchema = VopColorCellItemSchema;
|
|
16879
|
+
exports.VopColorCellSchema = VopColorCellSchema;
|
|
16880
|
+
exports.VopDigitCellSchema = VopDigitCellSchema;
|
|
16881
|
+
exports.VopEmptyCellSchema = VopEmptyCellSchema;
|
|
16882
|
+
exports.VopHighlightCellItemSchema = VopHighlightCellItemSchema;
|
|
16883
|
+
exports.VopHighlightCellSchema = VopHighlightCellSchema;
|
|
16884
|
+
exports.VopInputCellSchema = VopInputCellSchema;
|
|
16885
|
+
exports.VopLabelCellSchema = VopLabelCellSchema;
|
|
16886
|
+
exports.VopRowItemSchema = VopRowItemSchema;
|
|
16887
|
+
exports.VopRowSchema = VopRowSchema;
|
|
16888
|
+
exports.VopSeparatorItemSchema = VopSeparatorItemSchema;
|
|
16889
|
+
exports.VopSeparatorSchema = VopSeparatorSchema;
|
|
16036
16890
|
exports.WidgetJSONSchema = WidgetJSONSchema;
|
|
16037
16891
|
exports.WidgetRefSchema = WidgetRefSchema;
|
|
16038
16892
|
exports.WidgetRenderer = WidgetRenderer;
|
|
@@ -16047,6 +16901,7 @@ exports.createFraction = createFraction;
|
|
|
16047
16901
|
exports.createFreeText = createFreeText;
|
|
16048
16902
|
exports.createHighlightedText = createHighlightedText;
|
|
16049
16903
|
exports.createImage = createImage;
|
|
16904
|
+
exports.createInlineDragOption = createInlineDragOption;
|
|
16050
16905
|
exports.createLineBlock = createLineBlock;
|
|
16051
16906
|
exports.createMatcherBlock = createMatcherBlock;
|
|
16052
16907
|
exports.createMatcherItem = createMatcherItem;
|
|
@@ -16059,7 +16914,18 @@ exports.createSimpleValidation = createSimpleValidation;
|
|
|
16059
16914
|
exports.createSpace = createSpace;
|
|
16060
16915
|
exports.createStringInput = createStringInput;
|
|
16061
16916
|
exports.createTextBlock = createTextBlock;
|
|
16917
|
+
exports.createVerticalOperationBlock = createVerticalOperationBlock;
|
|
16062
16918
|
exports.createVisualFraction = createVisualFraction;
|
|
16919
|
+
exports.createVopBoldCell = createVopBoldCell;
|
|
16920
|
+
exports.createVopCarryRow = createVopCarryRow;
|
|
16921
|
+
exports.createVopColorCell = createVopColorCell;
|
|
16922
|
+
exports.createVopDigitCell = createVopDigitCell;
|
|
16923
|
+
exports.createVopEmptyCell = createVopEmptyCell;
|
|
16924
|
+
exports.createVopHighlightCell = createVopHighlightCell;
|
|
16925
|
+
exports.createVopInputCell = createVopInputCell;
|
|
16926
|
+
exports.createVopLabelCell = createVopLabelCell;
|
|
16927
|
+
exports.createVopRow = createVopRow;
|
|
16928
|
+
exports.createVopSeparator = createVopSeparator;
|
|
16063
16929
|
exports.evaluate = evaluate;
|
|
16064
16930
|
exports.extractExpressionVariables = extractVariables;
|
|
16065
16931
|
exports.findValidCombination = findValidCombination;
|
|
@@ -16079,6 +16945,8 @@ exports.isGroupItem = isGroupItem;
|
|
|
16079
16945
|
exports.isHighlightedText = isHighlightedText;
|
|
16080
16946
|
exports.isImage = isImage;
|
|
16081
16947
|
exports.isImageItem = isImageItem;
|
|
16948
|
+
exports.isInlineDragOption = isInlineDragOption;
|
|
16949
|
+
exports.isInlineDragOptionJSON = isInlineDragOptionJSON;
|
|
16082
16950
|
exports.isInlineInput = isInlineInput;
|
|
16083
16951
|
exports.isInlineInputJSON = isInlineInputJSON;
|
|
16084
16952
|
exports.isInlineNode = isInlineNode;
|
|
@@ -16107,8 +16975,15 @@ exports.isTextItem = isTextItem$1;
|
|
|
16107
16975
|
exports.isToolNode = isToolNode;
|
|
16108
16976
|
exports.isValidExpression = isValidExpression;
|
|
16109
16977
|
exports.isValidationNode = isValidationNode;
|
|
16978
|
+
exports.isVerticalOperationBlock = isVerticalOperationBlock;
|
|
16979
|
+
exports.isVerticalOperationItem = isVerticalOperationItem;
|
|
16110
16980
|
exports.isVisualFractionInline = isVisualFractionInline;
|
|
16981
|
+
exports.isVopCarryRow = isVopCarryRow;
|
|
16982
|
+
exports.isVopCell = isVopCell;
|
|
16983
|
+
exports.isVopRow = isVopRow;
|
|
16984
|
+
exports.isVopSeparator = isVopSeparator;
|
|
16111
16985
|
exports.isWidgetRef = isWidgetRef;
|
|
16986
|
+
exports.normalizeVopOperator = normalizeVopOperator;
|
|
16112
16987
|
exports.parse = parse;
|
|
16113
16988
|
exports.parseFractionKey = parseFractionKey;
|
|
16114
16989
|
exports.parseSafe = parseSafe;
|