@mathscan/math-exercise-engine 1.1.16 → 1.1.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 +596 -194
- package/dist/index.js +494 -11
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +494 -11
- package/dist/index.mjs.map +1 -1
- package/dist/style.css +95 -0
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
-
const chevrotain = require("chevrotain");
|
|
4
3
|
const zod = require("zod");
|
|
4
|
+
const chevrotain = require("chevrotain");
|
|
5
5
|
const mathjs = require("mathjs");
|
|
6
6
|
const jsxRuntime = require("react/jsx-runtime");
|
|
7
7
|
const React = require("react");
|
|
@@ -56,6 +56,8 @@ const AST_TYPES = {
|
|
|
56
56
|
VOP_CARRY_ROW: "VopCarryRow",
|
|
57
57
|
/** Number line layout block */
|
|
58
58
|
NUMBER_LINE_BLOCK: "NumberLineBlock",
|
|
59
|
+
/** Symmetry exercise on a square grid */
|
|
60
|
+
GRID_SYMMETRY_BLOCK: "GridSymmetryBlock",
|
|
59
61
|
/** A position marker on the number line */
|
|
60
62
|
NUMBER_LINE_POSITION: "NumberLinePosition",
|
|
61
63
|
/** A draggable cursor on the number line */
|
|
@@ -187,6 +189,8 @@ const JSON_TYPES = {
|
|
|
187
189
|
FRACTION: "fraction",
|
|
188
190
|
/** Visual fraction widget */
|
|
189
191
|
VISUAL_FRACTION: "visual-fraction",
|
|
192
|
+
/** Interactive symmetry exercise on a grid */
|
|
193
|
+
GRID_SYMMETRY: "grid-symmetry",
|
|
190
194
|
/** Ruler geometry tool widget */
|
|
191
195
|
RULER: "ruler",
|
|
192
196
|
/** Protractor geometry tool widget */
|
|
@@ -201,9 +205,11 @@ const SET_SQUARE_WIDGET_ID = "_set_square";
|
|
|
201
205
|
const STATEFUL_WIDGET_TYPES = /* @__PURE__ */ new Set([
|
|
202
206
|
JSON_TYPES.MATCHER,
|
|
203
207
|
JSON_TYPES.PAIR_MATCHER,
|
|
204
|
-
JSON_TYPES.VISUAL_FRACTION
|
|
208
|
+
JSON_TYPES.VISUAL_FRACTION,
|
|
209
|
+
JSON_TYPES.GRID_SYMMETRY
|
|
205
210
|
]);
|
|
206
211
|
const INLINE_DRAG_OPT_PREFIX = "_inline_drag_opt_";
|
|
212
|
+
const GRID_SYMMETRY_WIDGET_PREFIX = "_grid_symmetry_";
|
|
207
213
|
const RTL_INLINE_WIDGET_TYPES = /* @__PURE__ */ new Set([
|
|
208
214
|
JSON_TYPES.INLINE_INPUT,
|
|
209
215
|
JSON_TYPES.INLINE_DRAG_OPTION,
|
|
@@ -246,6 +252,11 @@ function parseFractionKey(key2) {
|
|
|
246
252
|
if (!match) return null;
|
|
247
253
|
return { widgetId: match[1], part: match[2] };
|
|
248
254
|
}
|
|
255
|
+
const GridCellSchema = zod.z.tuple([
|
|
256
|
+
zod.z.number().int().positive(),
|
|
257
|
+
zod.z.number().int().positive()
|
|
258
|
+
]);
|
|
259
|
+
const gridCellKey = ([row, col]) => `${row},${col}`;
|
|
249
260
|
const Newline = chevrotain.createToken({
|
|
250
261
|
name: "Newline",
|
|
251
262
|
pattern: /\r?\n/,
|
|
@@ -327,6 +338,10 @@ const NUMBER_LINE = chevrotain.createToken({
|
|
|
327
338
|
name: "NUMBER_LINE",
|
|
328
339
|
pattern: /NUMBER_LINE/
|
|
329
340
|
});
|
|
341
|
+
const GRID_SYMMETRY = chevrotain.createToken({
|
|
342
|
+
name: "GRID_SYMMETRY",
|
|
343
|
+
pattern: /GRID_SYMMETRY/
|
|
344
|
+
});
|
|
330
345
|
const POSITION = chevrotain.createToken({
|
|
331
346
|
name: "POSITION",
|
|
332
347
|
pattern: /POSITION/
|
|
@@ -535,6 +550,7 @@ const allTokens = [
|
|
|
535
550
|
W_N_I,
|
|
536
551
|
NUMERIC_INPUT,
|
|
537
552
|
NUMBER_LINE,
|
|
553
|
+
GRID_SYMMETRY,
|
|
538
554
|
POSITION,
|
|
539
555
|
CURSOR,
|
|
540
556
|
ARROW,
|
|
@@ -710,6 +726,10 @@ class MathExerciseParser extends chevrotain.CstParser {
|
|
|
710
726
|
GATE: this.BACKTRACK(this.numberLine),
|
|
711
727
|
ALT: () => this.SUBRULE(this.numberLine)
|
|
712
728
|
},
|
|
729
|
+
{
|
|
730
|
+
GATE: this.BACKTRACK(this.gridSymmetry),
|
|
731
|
+
ALT: () => this.SUBRULE(this.gridSymmetry)
|
|
732
|
+
},
|
|
713
733
|
{
|
|
714
734
|
GATE: this.BACKTRACK(this.pairMatcher),
|
|
715
735
|
ALT: () => this.SUBRULE(this.pairMatcher)
|
|
@@ -725,6 +745,18 @@ class MathExerciseParser extends chevrotain.CstParser {
|
|
|
725
745
|
{ ALT: () => this.SUBRULE(this.line) }
|
|
726
746
|
]);
|
|
727
747
|
});
|
|
748
|
+
this.gridSymmetry = this.RULE("gridSymmetry", () => {
|
|
749
|
+
this.MANY(() => {
|
|
750
|
+
this.OR([{ ALT: () => this.CONSUME(Space) }, { ALT: () => this.CONSUME(Newline) }]);
|
|
751
|
+
});
|
|
752
|
+
this.CONSUME(GRID_SYMMETRY);
|
|
753
|
+
this.CONSUME(LBracket);
|
|
754
|
+
this.SUBRULE(this.parameterList);
|
|
755
|
+
this.CONSUME(RBracket);
|
|
756
|
+
this.MANY2(() => {
|
|
757
|
+
this.OR2([{ ALT: () => this.CONSUME2(Space) }, { ALT: () => this.CONSUME2(Newline) }]);
|
|
758
|
+
});
|
|
759
|
+
});
|
|
728
760
|
this.table = this.RULE("table", () => {
|
|
729
761
|
this.MANY(() => {
|
|
730
762
|
this.OR2([{ ALT: () => this.CONSUME(Space) }, { ALT: () => this.CONSUME(Newline) }]);
|
|
@@ -1438,27 +1470,51 @@ class MathExerciseParser extends chevrotain.CstParser {
|
|
|
1438
1470
|
});
|
|
1439
1471
|
this.parameterList = this.RULE("parameterList", () => {
|
|
1440
1472
|
this.MANY(() => {
|
|
1441
|
-
this.
|
|
1473
|
+
this.OR([
|
|
1474
|
+
{ ALT: () => this.CONSUME(Space) },
|
|
1475
|
+
{ ALT: () => this.CONSUME(Newline) }
|
|
1476
|
+
]);
|
|
1442
1477
|
});
|
|
1443
1478
|
this.SUBRULE(this.parameter);
|
|
1444
1479
|
this.MANY2(() => {
|
|
1445
1480
|
this.MANY3(() => {
|
|
1446
|
-
this.
|
|
1481
|
+
this.OR2([
|
|
1482
|
+
{ ALT: () => this.CONSUME2(Space) },
|
|
1483
|
+
{ ALT: () => this.CONSUME2(Newline) }
|
|
1484
|
+
]);
|
|
1447
1485
|
});
|
|
1448
1486
|
this.CONSUME(Comma);
|
|
1449
1487
|
this.MANY4(() => {
|
|
1450
|
-
this.
|
|
1488
|
+
this.OR3([
|
|
1489
|
+
{ ALT: () => this.CONSUME3(Space) },
|
|
1490
|
+
{ ALT: () => this.CONSUME3(Newline) }
|
|
1491
|
+
]);
|
|
1451
1492
|
});
|
|
1452
1493
|
this.SUBRULE2(this.parameter);
|
|
1453
1494
|
});
|
|
1454
1495
|
this.MANY5(() => {
|
|
1455
|
-
this.
|
|
1496
|
+
this.OR4([
|
|
1497
|
+
{ ALT: () => this.CONSUME4(Space) },
|
|
1498
|
+
{ ALT: () => this.CONSUME4(Newline) }
|
|
1499
|
+
]);
|
|
1456
1500
|
});
|
|
1457
1501
|
});
|
|
1458
1502
|
this.parameter = this.RULE("parameter", () => {
|
|
1459
1503
|
this.CONSUME(Identifier, { LABEL: "key" });
|
|
1504
|
+
this.MANY(() => {
|
|
1505
|
+
this.OR([
|
|
1506
|
+
{ ALT: () => this.CONSUME(Space) },
|
|
1507
|
+
{ ALT: () => this.CONSUME(Newline) }
|
|
1508
|
+
]);
|
|
1509
|
+
});
|
|
1460
1510
|
this.CONSUME(Equals);
|
|
1461
|
-
this.
|
|
1511
|
+
this.MANY2(() => {
|
|
1512
|
+
this.OR2([
|
|
1513
|
+
{ ALT: () => this.CONSUME2(Space) },
|
|
1514
|
+
{ ALT: () => this.CONSUME2(Newline) }
|
|
1515
|
+
]);
|
|
1516
|
+
});
|
|
1517
|
+
this.OR3([
|
|
1462
1518
|
// String value
|
|
1463
1519
|
{ ALT: () => this.CONSUME(StringLiteral, { LABEL: "value" }) },
|
|
1464
1520
|
// Number value
|
|
@@ -1467,6 +1523,11 @@ class MathExerciseParser extends chevrotain.CstParser {
|
|
|
1467
1523
|
{ ALT: () => this.CONSUME2(Identifier, { LABEL: "value" }) },
|
|
1468
1524
|
// Tuple value: (start, end)
|
|
1469
1525
|
{ ALT: () => this.SUBRULE(this.tuple, { LABEL: "value" }) },
|
|
1526
|
+
// Number-pair list enclosed in braces: {[1, 2], [3, 4]}
|
|
1527
|
+
{
|
|
1528
|
+
GATE: () => this.isBracedNumberPairListAhead(),
|
|
1529
|
+
ALT: () => this.SUBRULE(this.bracedNumberPairList, { LABEL: "value" })
|
|
1530
|
+
},
|
|
1470
1531
|
// Curly-brace string list: {"a", "b", "c"}
|
|
1471
1532
|
{ ALT: () => this.SUBRULE(this.curlyStringList, { LABEL: "value" }) },
|
|
1472
1533
|
// Square-bracket number array: [1, 2, 3] — used by VISUAL_FRACTION filled/answer
|
|
@@ -1522,8 +1583,52 @@ class MathExerciseParser extends chevrotain.CstParser {
|
|
|
1522
1583
|
this.SUBRULE(this.stringList);
|
|
1523
1584
|
this.CONSUME(RBrace);
|
|
1524
1585
|
});
|
|
1586
|
+
this.bracedNumberPairList = this.RULE("bracedNumberPairList", () => {
|
|
1587
|
+
this.CONSUME(LBrace);
|
|
1588
|
+
this.MANY(() => {
|
|
1589
|
+
this.OR([{ ALT: () => this.CONSUME(Space) }, { ALT: () => this.CONSUME(Newline) }]);
|
|
1590
|
+
});
|
|
1591
|
+
this.OPTION(() => {
|
|
1592
|
+
this.SUBRULE(this.numberPair);
|
|
1593
|
+
this.MANY2(() => {
|
|
1594
|
+
this.MANY3(() => {
|
|
1595
|
+
this.OR2([{ ALT: () => this.CONSUME2(Space) }, { ALT: () => this.CONSUME2(Newline) }]);
|
|
1596
|
+
});
|
|
1597
|
+
this.CONSUME(Comma);
|
|
1598
|
+
this.MANY4(() => {
|
|
1599
|
+
this.OR3([{ ALT: () => this.CONSUME3(Space) }, { ALT: () => this.CONSUME3(Newline) }]);
|
|
1600
|
+
});
|
|
1601
|
+
this.SUBRULE2(this.numberPair);
|
|
1602
|
+
});
|
|
1603
|
+
});
|
|
1604
|
+
this.MANY5(() => {
|
|
1605
|
+
this.OR4([{ ALT: () => this.CONSUME4(Space) }, { ALT: () => this.CONSUME4(Newline) }]);
|
|
1606
|
+
});
|
|
1607
|
+
this.CONSUME(RBrace);
|
|
1608
|
+
});
|
|
1609
|
+
this.numberPair = this.RULE("numberPair", () => {
|
|
1610
|
+
this.CONSUME(LBracket);
|
|
1611
|
+
this.MANY(() => this.CONSUME(Space));
|
|
1612
|
+
this.CONSUME(NumberLiteral, { LABEL: "row" });
|
|
1613
|
+
this.MANY2(() => this.CONSUME2(Space));
|
|
1614
|
+
this.CONSUME(Comma);
|
|
1615
|
+
this.MANY3(() => this.CONSUME3(Space));
|
|
1616
|
+
this.CONSUME2(NumberLiteral, { LABEL: "col" });
|
|
1617
|
+
this.MANY4(() => this.CONSUME4(Space));
|
|
1618
|
+
this.CONSUME(RBracket);
|
|
1619
|
+
});
|
|
1525
1620
|
this.performSelfAnalysis();
|
|
1526
1621
|
}
|
|
1622
|
+
/** Distinguish `{[number, number], ...}` and the valid empty form `{}` from string lists. */
|
|
1623
|
+
isBracedNumberPairListAhead() {
|
|
1624
|
+
if (this.LA(1).tokenType !== LBrace) return false;
|
|
1625
|
+
let offset = 2;
|
|
1626
|
+
while (this.LA(offset).tokenType === Space || this.LA(offset).tokenType === Newline) {
|
|
1627
|
+
offset++;
|
|
1628
|
+
}
|
|
1629
|
+
const firstContentToken = this.LA(offset).tokenType;
|
|
1630
|
+
return firstContentToken === LBracket || firstContentToken === RBrace;
|
|
1631
|
+
}
|
|
1527
1632
|
}
|
|
1528
1633
|
const parser = new MathExerciseParser();
|
|
1529
1634
|
function parseOrThrow(schema, data, label) {
|
|
@@ -2068,6 +2173,135 @@ function createNumberLineBlock(min, max, positions, step, minorTicks, style, lab
|
|
|
2068
2173
|
positions
|
|
2069
2174
|
});
|
|
2070
2175
|
}
|
|
2176
|
+
const GRID_SYMMETRY_AXES = ["vertical", "horizontal"];
|
|
2177
|
+
const GRID_SYMMETRY_INTERACTIVE_AREAS = ["left", "right", "top", "bottom"];
|
|
2178
|
+
const GRID_SYMMETRY_MAX_ROWS = 20;
|
|
2179
|
+
const GRID_SYMMETRY_MAX_COLS = 20;
|
|
2180
|
+
const GRID_SYMMETRY_MIN_CELL_SIZE = 0.25;
|
|
2181
|
+
const GRID_SYMMETRY_MAX_CELL_SIZE = 2;
|
|
2182
|
+
function isCellInInteractiveArea(cell, rows, cols, area) {
|
|
2183
|
+
const [row, col] = cell;
|
|
2184
|
+
switch (area) {
|
|
2185
|
+
case "left":
|
|
2186
|
+
return col <= Math.floor(cols / 2);
|
|
2187
|
+
case "right":
|
|
2188
|
+
return col > Math.ceil(cols / 2);
|
|
2189
|
+
case "top":
|
|
2190
|
+
return row <= Math.floor(rows / 2);
|
|
2191
|
+
case "bottom":
|
|
2192
|
+
return row > Math.ceil(rows / 2);
|
|
2193
|
+
}
|
|
2194
|
+
}
|
|
2195
|
+
function reflectGridSymmetryCell(cell, rows, cols, axis) {
|
|
2196
|
+
const [row, col] = cell;
|
|
2197
|
+
return axis === "vertical" ? [row, cols + 1 - col] : [rows + 1 - row, col];
|
|
2198
|
+
}
|
|
2199
|
+
function computeGridSymmetryExpectedCells(data) {
|
|
2200
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2201
|
+
const expected = [];
|
|
2202
|
+
for (const cell of data.cells) {
|
|
2203
|
+
if (isCellInInteractiveArea(cell, data.rows, data.cols, data.interactiveArea)) continue;
|
|
2204
|
+
const reflected = reflectGridSymmetryCell(cell, data.rows, data.cols, data.axis);
|
|
2205
|
+
if (!isCellInInteractiveArea(reflected, data.rows, data.cols, data.interactiveArea)) continue;
|
|
2206
|
+
const key2 = gridCellKey(reflected);
|
|
2207
|
+
if (!seen.has(key2)) {
|
|
2208
|
+
seen.add(key2);
|
|
2209
|
+
expected.push(reflected);
|
|
2210
|
+
}
|
|
2211
|
+
}
|
|
2212
|
+
return expected.sort(([ar, ac], [br, bc]) => ar - br || ac - bc);
|
|
2213
|
+
}
|
|
2214
|
+
function computeGridSymmetryCellsToFill(data) {
|
|
2215
|
+
const initial = new Set(data.cells.map(gridCellKey));
|
|
2216
|
+
return computeGridSymmetryExpectedCells(data).filter((cell) => !initial.has(gridCellKey(cell)));
|
|
2217
|
+
}
|
|
2218
|
+
const GridSymmetryBlockSchema = zod.z.object({
|
|
2219
|
+
type: zod.z.literal(AST_TYPES.GRID_SYMMETRY_BLOCK),
|
|
2220
|
+
rows: zod.z.number().int("The number of rows must be a whole number.").positive("The number of rows must be greater than 0.").max(GRID_SYMMETRY_MAX_ROWS, {
|
|
2221
|
+
message: `The number of rows cannot exceed ${GRID_SYMMETRY_MAX_ROWS}.`
|
|
2222
|
+
}),
|
|
2223
|
+
cols: zod.z.number().int("The number of columns must be a whole number.").positive("The number of columns must be greater than 0.").max(GRID_SYMMETRY_MAX_COLS, {
|
|
2224
|
+
message: `The number of columns cannot exceed ${GRID_SYMMETRY_MAX_COLS}.`
|
|
2225
|
+
}),
|
|
2226
|
+
axis: zod.z.enum(GRID_SYMMETRY_AXES),
|
|
2227
|
+
interactiveArea: zod.z.enum(GRID_SYMMETRY_INTERACTIVE_AREAS),
|
|
2228
|
+
cellSize: zod.z.number().finite().min(
|
|
2229
|
+
GRID_SYMMETRY_MIN_CELL_SIZE,
|
|
2230
|
+
`Cell size must be between ${GRID_SYMMETRY_MIN_CELL_SIZE} and ${GRID_SYMMETRY_MAX_CELL_SIZE}.`
|
|
2231
|
+
).max(
|
|
2232
|
+
GRID_SYMMETRY_MAX_CELL_SIZE,
|
|
2233
|
+
`Cell size must be between ${GRID_SYMMETRY_MIN_CELL_SIZE} and ${GRID_SYMMETRY_MAX_CELL_SIZE}.`
|
|
2234
|
+
).default(1),
|
|
2235
|
+
cells: zod.z.array(GridCellSchema)
|
|
2236
|
+
}).superRefine((data, ctx) => {
|
|
2237
|
+
const compatible = data.axis === "vertical" ? data.interactiveArea === "left" || data.interactiveArea === "right" : data.interactiveArea === "top" || data.interactiveArea === "bottom";
|
|
2238
|
+
if (!compatible) {
|
|
2239
|
+
ctx.addIssue({
|
|
2240
|
+
code: zod.z.ZodIssueCode.custom,
|
|
2241
|
+
path: ["interactiveArea"],
|
|
2242
|
+
message: data.axis === "vertical" ? 'For a vertical axis, the interactive area must be "left" or "right".' : 'For a horizontal axis, the interactive area must be "top" or "bottom".'
|
|
2243
|
+
});
|
|
2244
|
+
}
|
|
2245
|
+
if (data.axis === "vertical" && data.cols % 2 !== 0) {
|
|
2246
|
+
ctx.addIssue({
|
|
2247
|
+
code: zod.z.ZodIssueCode.custom,
|
|
2248
|
+
path: ["cols"],
|
|
2249
|
+
message: `For a vertical axis, the number of columns must be even. Received: ${data.cols}.`
|
|
2250
|
+
});
|
|
2251
|
+
}
|
|
2252
|
+
if (data.axis === "horizontal" && data.rows % 2 !== 0) {
|
|
2253
|
+
ctx.addIssue({
|
|
2254
|
+
code: zod.z.ZodIssueCode.custom,
|
|
2255
|
+
path: ["rows"],
|
|
2256
|
+
message: `For a horizontal axis, the number of rows must be even. Received: ${data.rows}.`
|
|
2257
|
+
});
|
|
2258
|
+
}
|
|
2259
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2260
|
+
data.cells.forEach((cell, index) => {
|
|
2261
|
+
const [row, col] = cell;
|
|
2262
|
+
if (row > data.rows || col > data.cols) {
|
|
2263
|
+
ctx.addIssue({
|
|
2264
|
+
code: zod.z.ZodIssueCode.custom,
|
|
2265
|
+
path: ["cells", index],
|
|
2266
|
+
message: `Cell [${row},${col}] is outside the ${data.rows} × ${data.cols} grid.`
|
|
2267
|
+
});
|
|
2268
|
+
}
|
|
2269
|
+
const key2 = gridCellKey(cell);
|
|
2270
|
+
if (seen.has(key2)) {
|
|
2271
|
+
ctx.addIssue({
|
|
2272
|
+
code: zod.z.ZodIssueCode.custom,
|
|
2273
|
+
path: ["cells", index],
|
|
2274
|
+
message: `Cell [${row},${col}] is listed more than once.`
|
|
2275
|
+
});
|
|
2276
|
+
}
|
|
2277
|
+
seen.add(key2);
|
|
2278
|
+
});
|
|
2279
|
+
if (!compatible) return;
|
|
2280
|
+
const expected = new Set(computeGridSymmetryExpectedCells(data).map(gridCellKey));
|
|
2281
|
+
data.cells.forEach((cell, index) => {
|
|
2282
|
+
if (isCellInInteractiveArea(cell, data.rows, data.cols, data.interactiveArea) && !expected.has(gridCellKey(cell))) {
|
|
2283
|
+
ctx.addIssue({
|
|
2284
|
+
code: zod.z.ZodIssueCode.custom,
|
|
2285
|
+
path: ["cells", index],
|
|
2286
|
+
message: `Prefilled cell [${cell[0]},${cell[1]}] does not match the expected symmetry.`
|
|
2287
|
+
});
|
|
2288
|
+
}
|
|
2289
|
+
});
|
|
2290
|
+
});
|
|
2291
|
+
function createGridSymmetryBlock(rows, cols, axis, interactiveArea, cells, cellSize = 1) {
|
|
2292
|
+
return GridSymmetryBlockSchema.parse({
|
|
2293
|
+
type: AST_TYPES.GRID_SYMMETRY_BLOCK,
|
|
2294
|
+
rows,
|
|
2295
|
+
cols,
|
|
2296
|
+
axis,
|
|
2297
|
+
interactiveArea,
|
|
2298
|
+
cellSize,
|
|
2299
|
+
cells
|
|
2300
|
+
});
|
|
2301
|
+
}
|
|
2302
|
+
function isGridSymmetryBlock(value) {
|
|
2303
|
+
return GridSymmetryBlockSchema.safeParse(value).success;
|
|
2304
|
+
}
|
|
2071
2305
|
const TableCellSchema = zod.z.object({
|
|
2072
2306
|
type: zod.z.literal(AST_TYPES.TABLE_CELL),
|
|
2073
2307
|
colspan: zod.z.number().int().positive().optional(),
|
|
@@ -2567,7 +2801,7 @@ function isMixedValidation(value) {
|
|
|
2567
2801
|
function isValidationNode(value) {
|
|
2568
2802
|
return ValidationNodeSchema.safeParse(value).success;
|
|
2569
2803
|
}
|
|
2570
|
-
const ContentBlockSchema = zod.z.union([LineBlockSchema, TableBlockSchema, RadioOptionsBlockSchema, NumberLineBlockSchema, MatcherBlockSchema, PairMatcherBlockSchema, VerticalOperationBlockSchema]);
|
|
2804
|
+
const ContentBlockSchema = zod.z.union([LineBlockSchema, TableBlockSchema, RadioOptionsBlockSchema, NumberLineBlockSchema, MatcherBlockSchema, PairMatcherBlockSchema, VerticalOperationBlockSchema, GridSymmetryBlockSchema]);
|
|
2571
2805
|
const ExerciseDocumentSchema = zod.z.object({
|
|
2572
2806
|
type: zod.z.literal(AST_TYPES.EXERCISE_DOCUMENT),
|
|
2573
2807
|
question: TextBlockSchema.optional(),
|
|
@@ -2669,12 +2903,24 @@ class NaturalDSLVisitor extends BaseCstVisitor {
|
|
|
2669
2903
|
if (ctx.table) return this.visit(ctx.table);
|
|
2670
2904
|
if (ctx.radioButtons) return this.visit(ctx.radioButtons);
|
|
2671
2905
|
if (ctx.numberLine) return this.visit(ctx.numberLine);
|
|
2906
|
+
if (ctx.gridSymmetry) return this.visit(ctx.gridSymmetry);
|
|
2672
2907
|
if (ctx.pairMatcher) return this.visit(ctx.pairMatcher);
|
|
2673
2908
|
if (ctx.matcher) return this.visit(ctx.matcher);
|
|
2674
2909
|
if (ctx.verticalOperation) return this.visit(ctx.verticalOperation);
|
|
2675
2910
|
if (ctx.line) return this.visit(ctx.line);
|
|
2676
2911
|
throw new Error("Unknown content block");
|
|
2677
2912
|
}
|
|
2913
|
+
gridSymmetry(ctx) {
|
|
2914
|
+
const params = this.visit(ctx.parameterList);
|
|
2915
|
+
return createGridSymmetryBlock(
|
|
2916
|
+
Number(params.rows),
|
|
2917
|
+
Number(params.cols),
|
|
2918
|
+
params.axis,
|
|
2919
|
+
params.interactiveArea,
|
|
2920
|
+
params.cells,
|
|
2921
|
+
params.cellSize === void 0 ? 1 : Number(params.cellSize)
|
|
2922
|
+
);
|
|
2923
|
+
}
|
|
2678
2924
|
radioButtons(ctx) {
|
|
2679
2925
|
const params = this.visit(ctx.parameterList);
|
|
2680
2926
|
const name = params.name;
|
|
@@ -3263,6 +3509,15 @@ class NaturalDSLVisitor extends BaseCstVisitor {
|
|
|
3263
3509
|
curlyStringList(ctx) {
|
|
3264
3510
|
return this.visit(ctx.stringList);
|
|
3265
3511
|
}
|
|
3512
|
+
bracedNumberPairList(ctx) {
|
|
3513
|
+
return ctx.numberPair ? ctx.numberPair.map((pair) => this.visit(pair)) : [];
|
|
3514
|
+
}
|
|
3515
|
+
numberPair(ctx) {
|
|
3516
|
+
return [
|
|
3517
|
+
parseFloat(ctx.row[0].image),
|
|
3518
|
+
parseFloat(ctx.col[0].image)
|
|
3519
|
+
];
|
|
3520
|
+
}
|
|
3266
3521
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
3267
3522
|
// Helpers
|
|
3268
3523
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -3639,6 +3894,34 @@ const VisualFractionJSONSchema = zod.z.object({
|
|
|
3639
3894
|
*/
|
|
3640
3895
|
size: zod.z.number().positive().optional()
|
|
3641
3896
|
}).superRefine(validateVisualFractionCapacity);
|
|
3897
|
+
const GridSymmetryJSONSchema = zod.z.object({
|
|
3898
|
+
type: zod.z.literal(JSON_TYPES.GRID_SYMMETRY),
|
|
3899
|
+
rows: zod.z.number().int().positive(),
|
|
3900
|
+
cols: zod.z.number().int().positive(),
|
|
3901
|
+
axis: zod.z.enum(["vertical", "horizontal"]),
|
|
3902
|
+
interactiveArea: zod.z.enum(["left", "right", "top", "bottom"]),
|
|
3903
|
+
cellSize: zod.z.number().finite().positive().default(1),
|
|
3904
|
+
cells: zod.z.array(GridCellSchema)
|
|
3905
|
+
}).superRefine((data, ctx) => {
|
|
3906
|
+
const result = GridSymmetryBlockSchema.safeParse({
|
|
3907
|
+
type: AST_TYPES.GRID_SYMMETRY_BLOCK,
|
|
3908
|
+
rows: data.rows,
|
|
3909
|
+
cols: data.cols,
|
|
3910
|
+
axis: data.axis,
|
|
3911
|
+
interactiveArea: data.interactiveArea,
|
|
3912
|
+
cellSize: data.cellSize,
|
|
3913
|
+
cells: data.cells
|
|
3914
|
+
});
|
|
3915
|
+
if (!result.success) {
|
|
3916
|
+
for (const issue of result.error.issues) {
|
|
3917
|
+
ctx.addIssue({
|
|
3918
|
+
code: zod.z.ZodIssueCode.custom,
|
|
3919
|
+
path: issue.path,
|
|
3920
|
+
message: issue.message
|
|
3921
|
+
});
|
|
3922
|
+
}
|
|
3923
|
+
}
|
|
3924
|
+
});
|
|
3642
3925
|
const RulerWidgetSchema = zod.z.object({
|
|
3643
3926
|
type: zod.z.literal(JSON_TYPES.RULER),
|
|
3644
3927
|
size: zod.z.number().optional()
|
|
@@ -3661,6 +3944,7 @@ const WidgetJSONSchema = zod.z.union([
|
|
|
3661
3944
|
MatcherWidgetJSONSchema,
|
|
3662
3945
|
PairMatcherWidgetJSONSchema,
|
|
3663
3946
|
VisualFractionJSONSchema,
|
|
3947
|
+
GridSymmetryJSONSchema,
|
|
3664
3948
|
RulerWidgetSchema,
|
|
3665
3949
|
ProtractorWidgetSchema,
|
|
3666
3950
|
SetSquareWidgetSchema
|
|
@@ -3676,7 +3960,8 @@ const ValidationJSONSchema = zod.z.object({
|
|
|
3676
3960
|
zod.z.string(),
|
|
3677
3961
|
zod.z.number(),
|
|
3678
3962
|
zod.z.array(zod.z.array(zod.z.string().min(1)).min(1)).min(1),
|
|
3679
|
-
zod.z.array(zod.z.number().int().nonnegative())
|
|
3963
|
+
zod.z.array(zod.z.number().int().nonnegative()),
|
|
3964
|
+
zod.z.array(GridCellSchema)
|
|
3680
3965
|
])
|
|
3681
3966
|
).optional(),
|
|
3682
3967
|
expression: zod.z.string().optional(),
|
|
@@ -3774,6 +4059,9 @@ function isNumericKeypadJSON(widget) {
|
|
|
3774
4059
|
function isFractionJSON(widget) {
|
|
3775
4060
|
return widget.type === JSON_TYPES.FRACTION;
|
|
3776
4061
|
}
|
|
4062
|
+
function isGridSymmetryJSON(widget) {
|
|
4063
|
+
return widget.type === JSON_TYPES.GRID_SYMMETRY;
|
|
4064
|
+
}
|
|
3777
4065
|
function isRulerWidget(widget) {
|
|
3778
4066
|
return widget.type === JSON_TYPES.RULER;
|
|
3779
4067
|
}
|
|
@@ -9281,6 +9569,7 @@ function toJSON(ast) {
|
|
|
9281
9569
|
const idGenerator = new LayoutIdGenerator();
|
|
9282
9570
|
const widgets = {};
|
|
9283
9571
|
const answers = {};
|
|
9572
|
+
let gridSymmetryCounter = 0;
|
|
9284
9573
|
const allInlines = collectAllInlines(ast.lines);
|
|
9285
9574
|
const inlineDragIds = /* @__PURE__ */ new Map();
|
|
9286
9575
|
{
|
|
@@ -9335,6 +9624,34 @@ function toJSON(ast) {
|
|
|
9335
9624
|
});
|
|
9336
9625
|
} else if (block.type === AST_TYPES.VERTICAL_OPERATION_BLOCK) {
|
|
9337
9626
|
layout.push(transformVerticalOperation(block, idGenerator));
|
|
9627
|
+
} else if (block.type === AST_TYPES.GRID_SYMMETRY_BLOCK) {
|
|
9628
|
+
const grid = block;
|
|
9629
|
+
const widgetId = `${GRID_SYMMETRY_WIDGET_PREFIX}${gridSymmetryCounter++}`;
|
|
9630
|
+
widgets[widgetId] = {
|
|
9631
|
+
type: JSON_TYPES.GRID_SYMMETRY,
|
|
9632
|
+
rows: grid.rows,
|
|
9633
|
+
cols: grid.cols,
|
|
9634
|
+
axis: grid.axis,
|
|
9635
|
+
interactiveArea: grid.interactiveArea,
|
|
9636
|
+
cellSize: grid.cellSize,
|
|
9637
|
+
cells: grid.cells
|
|
9638
|
+
};
|
|
9639
|
+
const prefilled = grid.cells.filter((cell) => isCellInInteractiveArea(
|
|
9640
|
+
cell,
|
|
9641
|
+
grid.rows,
|
|
9642
|
+
grid.cols,
|
|
9643
|
+
grid.interactiveArea
|
|
9644
|
+
));
|
|
9645
|
+
answers[widgetId] = [...prefilled, ...computeGridSymmetryCellsToFill(grid)].sort(([ar, ac], [br, bc]) => ar - br || ac - bc);
|
|
9646
|
+
layout.push({
|
|
9647
|
+
id: idGenerator.generate(JSON_TYPES.WIDGET_REF),
|
|
9648
|
+
type: JSON_TYPES.WIDGET_REF,
|
|
9649
|
+
widgetId
|
|
9650
|
+
});
|
|
9651
|
+
layout.push({
|
|
9652
|
+
id: idGenerator.generate(JSON_TYPES.LINEBREAK),
|
|
9653
|
+
type: JSON_TYPES.LINEBREAK
|
|
9654
|
+
});
|
|
9338
9655
|
} else if (block.type === AST_TYPES.RADIO_OPTIONS) {
|
|
9339
9656
|
const radioBlock = block;
|
|
9340
9657
|
layout.push({
|
|
@@ -9932,6 +10249,18 @@ function areVisualFractionCountsEqual(expected, result) {
|
|
|
9932
10249
|
if (expected.length !== result.length) return false;
|
|
9933
10250
|
return lodashExports.isEqual(lodashExports.sortBy(expected), lodashExports.sortBy(result));
|
|
9934
10251
|
}
|
|
10252
|
+
function isValidGridSymmetryCell(value, rows, cols) {
|
|
10253
|
+
if (!Array.isArray(value) || value.length !== 2) return false;
|
|
10254
|
+
const [row, col] = value;
|
|
10255
|
+
return Number.isInteger(row) && Number.isInteger(col) && row >= 1 && row <= rows && col >= 1 && col <= cols;
|
|
10256
|
+
}
|
|
10257
|
+
function areGridSymmetryCellsEqual(expected, actual, rows, cols) {
|
|
10258
|
+
if (!Array.isArray(actual) || actual.length !== expected.length) return false;
|
|
10259
|
+
if (!actual.every((cell) => isValidGridSymmetryCell(cell, rows, cols))) return false;
|
|
10260
|
+
const actualKeys = new Set(actual.map(gridCellKey));
|
|
10261
|
+
if (actualKeys.size !== actual.length) return false;
|
|
10262
|
+
return expected.every((cell) => actualKeys.has(gridCellKey(cell)));
|
|
10263
|
+
}
|
|
9935
10264
|
const math$1 = mathjs.create(mathjs.all);
|
|
9936
10265
|
function collectNumberLines(items) {
|
|
9937
10266
|
const result = [];
|
|
@@ -10005,6 +10334,20 @@ function validateSimple(exercise, userInputs) {
|
|
|
10005
10334
|
else allCorrect = false;
|
|
10006
10335
|
continue;
|
|
10007
10336
|
}
|
|
10337
|
+
if (widget?.type === JSON_TYPES.GRID_SYMMETRY) {
|
|
10338
|
+
const userValue2 = userInputs[widgetId];
|
|
10339
|
+
const isCorrect2 = areGridSymmetryCellsEqual(
|
|
10340
|
+
expected,
|
|
10341
|
+
userValue2,
|
|
10342
|
+
widget.rows,
|
|
10343
|
+
widget.cols
|
|
10344
|
+
);
|
|
10345
|
+
details[widgetId] = { widgetId, userValue: userValue2, isCorrect: isCorrect2 };
|
|
10346
|
+
totalCount++;
|
|
10347
|
+
if (isCorrect2) correctCount++;
|
|
10348
|
+
else allCorrect = false;
|
|
10349
|
+
continue;
|
|
10350
|
+
}
|
|
10008
10351
|
totalCount++;
|
|
10009
10352
|
const userValue = userInputs[widgetId];
|
|
10010
10353
|
const isFractionLenient = widget?.type === JSON_TYPES.FRACTION && widget.simplified === false;
|
|
@@ -10435,9 +10778,19 @@ function getMissingInputs(exercise, userInputs) {
|
|
|
10435
10778
|
}
|
|
10436
10779
|
const inputs = preprocessFractionValues(exercise, userInputs);
|
|
10437
10780
|
const isEmpty = (v) => v === void 0 || v === null || v === "" || Array.isArray(v) && v.length === 0;
|
|
10781
|
+
const isMissingAnswer = (widgetId) => {
|
|
10782
|
+
const value = inputs[widgetId];
|
|
10783
|
+
const widget = exercise.widgets[widgetId];
|
|
10784
|
+
if (widget?.type === JSON_TYPES.GRID_SYMMETRY) {
|
|
10785
|
+
const expected = exercise.validation?.answers?.[widgetId];
|
|
10786
|
+
const expectedCount = Array.isArray(expected) ? expected.length : 0;
|
|
10787
|
+
return !Array.isArray(value) || value.length < expectedCount;
|
|
10788
|
+
}
|
|
10789
|
+
return !(widgetId in inputs) || isEmpty(value);
|
|
10790
|
+
};
|
|
10438
10791
|
if (exercise.validation.mode === VALIDATION_MODES.SIMPLE && exercise.validation.answers) {
|
|
10439
10792
|
for (const widgetId of Object.keys(exercise.validation.answers)) {
|
|
10440
|
-
if (
|
|
10793
|
+
if (isMissingAnswer(widgetId)) {
|
|
10441
10794
|
missing.push(widgetId);
|
|
10442
10795
|
}
|
|
10443
10796
|
}
|
|
@@ -10453,7 +10806,7 @@ function getMissingInputs(exercise, userInputs) {
|
|
|
10453
10806
|
...exercise.validation.variables ?? []
|
|
10454
10807
|
]);
|
|
10455
10808
|
for (const k of keys) {
|
|
10456
|
-
if (
|
|
10809
|
+
if (isMissingAnswer(k)) missing.push(k);
|
|
10457
10810
|
}
|
|
10458
10811
|
}
|
|
10459
10812
|
return missing;
|
|
@@ -15876,6 +16229,94 @@ function getSizeStyle(size, _shape) {
|
|
|
15876
16229
|
"--visual-fraction-size": `${size * VISUAL_FRACTION_SVG_SIZE_SCALE}rem`
|
|
15877
16230
|
};
|
|
15878
16231
|
}
|
|
16232
|
+
const GridSymmetry = ({
|
|
16233
|
+
widgetId,
|
|
16234
|
+
widget,
|
|
16235
|
+
value,
|
|
16236
|
+
validationStatus = null,
|
|
16237
|
+
onChange
|
|
16238
|
+
}) => {
|
|
16239
|
+
const initialKeys = React.useMemo(() => new Set(widget.cells.map(gridCellKey)), [widget.cells]);
|
|
16240
|
+
const initialInteractiveCells = React.useMemo(
|
|
16241
|
+
() => widget.cells.filter(
|
|
16242
|
+
(cell) => isCellInInteractiveArea(cell, widget.rows, widget.cols, widget.interactiveArea)
|
|
16243
|
+
),
|
|
16244
|
+
[widget.cells, widget.rows, widget.cols, widget.interactiveArea]
|
|
16245
|
+
);
|
|
16246
|
+
const selectedCells = value ?? initialInteractiveCells;
|
|
16247
|
+
const selectedKeys = React.useMemo(() => new Set(selectedCells.map(gridCellKey)), [selectedCells]);
|
|
16248
|
+
const toggleCell = (cell) => {
|
|
16249
|
+
const key2 = gridCellKey(cell);
|
|
16250
|
+
if (initialKeys.has(key2)) return;
|
|
16251
|
+
const next = selectedKeys.has(key2) ? selectedCells.filter((candidate) => gridCellKey(candidate) !== key2) : [...selectedCells, cell];
|
|
16252
|
+
onChange(widgetId, next);
|
|
16253
|
+
};
|
|
16254
|
+
const statusClass = validationStatus ? ` grid-symmetry--${validationStatus}` : "";
|
|
16255
|
+
const cells = [];
|
|
16256
|
+
for (let row = 1; row <= widget.rows; row++) {
|
|
16257
|
+
for (let col = 1; col <= widget.cols; col++) {
|
|
16258
|
+
const cell = [row, col];
|
|
16259
|
+
const key2 = gridCellKey(cell);
|
|
16260
|
+
const interactive = isCellInInteractiveArea(
|
|
16261
|
+
cell,
|
|
16262
|
+
widget.rows,
|
|
16263
|
+
widget.cols,
|
|
16264
|
+
widget.interactiveArea
|
|
16265
|
+
);
|
|
16266
|
+
const initial = initialKeys.has(key2);
|
|
16267
|
+
const selected = selectedKeys.has(key2);
|
|
16268
|
+
const locked = initial || !interactive;
|
|
16269
|
+
const roleClass = initial ? interactive ? " grid-symmetry__cell--prefilled" : " grid-symmetry__cell--reference" : selected ? " grid-symmetry__cell--answer" : "";
|
|
16270
|
+
const className = `grid-symmetry__cell${interactive ? " grid-symmetry__cell--interactive" : ""}${locked ? "" : " grid-symmetry__cell--editable"}${selected || initial ? " grid-symmetry__cell--selected" : ""}${roleClass}`;
|
|
16271
|
+
const label = `Row ${row}, column ${col}`;
|
|
16272
|
+
cells.push(
|
|
16273
|
+
locked ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
16274
|
+
"span",
|
|
16275
|
+
{
|
|
16276
|
+
className,
|
|
16277
|
+
role: initial ? "img" : void 0,
|
|
16278
|
+
"aria-label": initial ? `${label}, filled` : void 0,
|
|
16279
|
+
"aria-hidden": initial ? void 0 : true
|
|
16280
|
+
},
|
|
16281
|
+
key2
|
|
16282
|
+
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
16283
|
+
"button",
|
|
16284
|
+
{
|
|
16285
|
+
type: "button",
|
|
16286
|
+
className,
|
|
16287
|
+
"aria-label": label,
|
|
16288
|
+
"aria-pressed": selected,
|
|
16289
|
+
onClick: () => toggleCell(cell)
|
|
16290
|
+
},
|
|
16291
|
+
key2
|
|
16292
|
+
)
|
|
16293
|
+
);
|
|
16294
|
+
}
|
|
16295
|
+
}
|
|
16296
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `grid-symmetry${statusClass}`, "data-testid": "grid-symmetry", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
16297
|
+
"div",
|
|
16298
|
+
{
|
|
16299
|
+
className: "grid-symmetry__grid",
|
|
16300
|
+
style: {
|
|
16301
|
+
"--grid-symmetry-cell-size": `calc(48px * ${widget.cellSize})`,
|
|
16302
|
+
gridTemplateColumns: `repeat(${widget.cols}, var(--grid-symmetry-cell-size))`,
|
|
16303
|
+
gridTemplateRows: `repeat(${widget.rows}, var(--grid-symmetry-cell-size))`
|
|
16304
|
+
},
|
|
16305
|
+
role: "group",
|
|
16306
|
+
"aria-label": `Symmetry grid, ${widget.rows} rows by ${widget.cols} columns`,
|
|
16307
|
+
children: [
|
|
16308
|
+
cells,
|
|
16309
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
16310
|
+
"span",
|
|
16311
|
+
{
|
|
16312
|
+
className: `grid-symmetry__axis grid-symmetry__axis--${widget.axis}`,
|
|
16313
|
+
"aria-hidden": "true"
|
|
16314
|
+
}
|
|
16315
|
+
)
|
|
16316
|
+
]
|
|
16317
|
+
}
|
|
16318
|
+
) });
|
|
16319
|
+
};
|
|
15879
16320
|
const RadioOptions = ({
|
|
15880
16321
|
name,
|
|
15881
16322
|
options,
|
|
@@ -16524,6 +16965,9 @@ function isPairMatcherWidget(widget) {
|
|
|
16524
16965
|
function isVisualFractionWidget(widget) {
|
|
16525
16966
|
return widget.type === "visual-fraction";
|
|
16526
16967
|
}
|
|
16968
|
+
function isGridSymmetryWidget(widget) {
|
|
16969
|
+
return widget.type === "grid-symmetry";
|
|
16970
|
+
}
|
|
16527
16971
|
const WidgetRenderer = ({
|
|
16528
16972
|
widget,
|
|
16529
16973
|
widgetId,
|
|
@@ -16668,6 +17112,21 @@ const WidgetRenderer = ({
|
|
|
16668
17112
|
}
|
|
16669
17113
|
);
|
|
16670
17114
|
}
|
|
17115
|
+
if (isGridSymmetryWidget(widget)) {
|
|
17116
|
+
const cells = Array.isArray(value) && value.every(
|
|
17117
|
+
(cell) => Array.isArray(cell) && cell.length === 2 && cell.every(Number.isInteger)
|
|
17118
|
+
) ? value : void 0;
|
|
17119
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
17120
|
+
GridSymmetry,
|
|
17121
|
+
{
|
|
17122
|
+
widgetId,
|
|
17123
|
+
widget,
|
|
17124
|
+
value: cells,
|
|
17125
|
+
validationStatus,
|
|
17126
|
+
onChange: (id, nextValue) => onChange(id, nextValue)
|
|
17127
|
+
}
|
|
17128
|
+
);
|
|
17129
|
+
}
|
|
16671
17130
|
return null;
|
|
16672
17131
|
};
|
|
16673
17132
|
const ExpandDiamondIcon = () => {
|
|
@@ -18732,6 +19191,14 @@ function buildInitialValues(json) {
|
|
|
18732
19191
|
const initial = {};
|
|
18733
19192
|
for (const [id, widget] of Object.entries(json.widgets)) {
|
|
18734
19193
|
if (widget.type === JSON_TYPES.MATCHER || widget.type === JSON_TYPES.PAIR_MATCHER) initial[id] = [];
|
|
19194
|
+
if (widget.type === JSON_TYPES.GRID_SYMMETRY) {
|
|
19195
|
+
initial[id] = widget.cells.filter((cell) => isCellInInteractiveArea(
|
|
19196
|
+
cell,
|
|
19197
|
+
widget.rows,
|
|
19198
|
+
widget.cols,
|
|
19199
|
+
widget.interactiveArea
|
|
19200
|
+
));
|
|
19201
|
+
}
|
|
18735
19202
|
}
|
|
18736
19203
|
return initial;
|
|
18737
19204
|
}
|
|
@@ -19160,6 +19627,15 @@ exports.ExpressionValidationSchema = ExpressionValidationSchema;
|
|
|
19160
19627
|
exports.FractionInlineSchema = FractionInlineSchema;
|
|
19161
19628
|
exports.FractionJSONSchema = FractionJSONSchema;
|
|
19162
19629
|
exports.FreeTextInlineSchema = FreeTextInlineSchema;
|
|
19630
|
+
exports.GRID_SYMMETRY_AXES = GRID_SYMMETRY_AXES;
|
|
19631
|
+
exports.GRID_SYMMETRY_INTERACTIVE_AREAS = GRID_SYMMETRY_INTERACTIVE_AREAS;
|
|
19632
|
+
exports.GRID_SYMMETRY_MAX_CELL_SIZE = GRID_SYMMETRY_MAX_CELL_SIZE;
|
|
19633
|
+
exports.GRID_SYMMETRY_MAX_COLS = GRID_SYMMETRY_MAX_COLS;
|
|
19634
|
+
exports.GRID_SYMMETRY_MAX_ROWS = GRID_SYMMETRY_MAX_ROWS;
|
|
19635
|
+
exports.GRID_SYMMETRY_MIN_CELL_SIZE = GRID_SYMMETRY_MIN_CELL_SIZE;
|
|
19636
|
+
exports.GridCellSchema = GridCellSchema;
|
|
19637
|
+
exports.GridSymmetryBlockSchema = GridSymmetryBlockSchema;
|
|
19638
|
+
exports.GridSymmetryJSONSchema = GridSymmetryJSONSchema;
|
|
19163
19639
|
exports.GroupItemSchema = GroupItemSchema;
|
|
19164
19640
|
exports.HighlightedTextInlineSchema = HighlightedTextInlineSchema;
|
|
19165
19641
|
exports.HighlightedTextSchema = HighlightedTextSchema;
|
|
@@ -19246,6 +19722,8 @@ exports.WidgetRefSchema = WidgetRefSchema;
|
|
|
19246
19722
|
exports.WidgetRenderer = WidgetRenderer;
|
|
19247
19723
|
exports.compile = compile;
|
|
19248
19724
|
exports.compileWithAST = compileWithAST;
|
|
19725
|
+
exports.computeGridSymmetryCellsToFill = computeGridSymmetryCellsToFill;
|
|
19726
|
+
exports.computeGridSymmetryExpectedCells = computeGridSymmetryExpectedCells;
|
|
19249
19727
|
exports.createBoldText = createBoldText;
|
|
19250
19728
|
exports.createColorText = createColorText;
|
|
19251
19729
|
exports.createDisplayClock = createDisplayClock;
|
|
@@ -19254,6 +19732,7 @@ exports.createExerciseDocument = createExerciseDocument;
|
|
|
19254
19732
|
exports.createExpressionValidation = createExpressionValidation;
|
|
19255
19733
|
exports.createFraction = createFraction;
|
|
19256
19734
|
exports.createFreeText = createFreeText;
|
|
19735
|
+
exports.createGridSymmetryBlock = createGridSymmetryBlock;
|
|
19257
19736
|
exports.createHighlightedText = createHighlightedText;
|
|
19258
19737
|
exports.createImage = createImage;
|
|
19259
19738
|
exports.createInlineDragOption = createInlineDragOption;
|
|
@@ -19292,6 +19771,7 @@ exports.getAnswerableInputs = getAnswerableInputs;
|
|
|
19292
19771
|
exports.getFractionKey = getFractionKey;
|
|
19293
19772
|
exports.getMissingInputs = getMissingInputs;
|
|
19294
19773
|
exports.isBoldText = isBoldText;
|
|
19774
|
+
exports.isCellInInteractiveArea = isCellInInteractiveArea;
|
|
19295
19775
|
exports.isClock = isClock;
|
|
19296
19776
|
exports.isClockItem = isClockItem;
|
|
19297
19777
|
exports.isColorText = isColorText;
|
|
@@ -19303,6 +19783,8 @@ exports.isFraction = isFraction;
|
|
|
19303
19783
|
exports.isFractionJSON = isFractionJSON;
|
|
19304
19784
|
exports.isFractionKey = isFractionKey;
|
|
19305
19785
|
exports.isFreeText = isFreeText;
|
|
19786
|
+
exports.isGridSymmetryBlock = isGridSymmetryBlock;
|
|
19787
|
+
exports.isGridSymmetryJSON = isGridSymmetryJSON;
|
|
19306
19788
|
exports.isGroupItem = isGroupItem;
|
|
19307
19789
|
exports.isHighlightedText = isHighlightedText;
|
|
19308
19790
|
exports.isImage = isImage;
|
|
@@ -19355,6 +19837,7 @@ exports.normalizeVopOperator = normalizeVopOperator;
|
|
|
19355
19837
|
exports.parse = parse;
|
|
19356
19838
|
exports.parseFractionKey = parseFractionKey;
|
|
19357
19839
|
exports.parseSafe = parseSafe;
|
|
19840
|
+
exports.reflectGridSymmetryCell = reflectGridSymmetryCell;
|
|
19358
19841
|
exports.renderExercise = renderExercise;
|
|
19359
19842
|
exports.toAST = toAST;
|
|
19360
19843
|
exports.toJSON = toJSON;
|