@mathscan/math-exercise-engine 1.1.14 → 1.1.16
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 +424 -54
- package/dist/index.js +325 -63
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +326 -64
- package/dist/index.mjs.map +1 -1
- package/dist/style.css +385 -68
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -231,6 +231,9 @@ const SIDE_PAD = 7;
|
|
|
231
231
|
const CSS_VAR_SIDE_PAD = "--nl-side-pad";
|
|
232
232
|
const NUMBER_LINE_LABELS = ["all", "extremes", "min-only", "max-only", "none"];
|
|
233
233
|
const NUMBER_LINE_HIGHLIGHT_COLORS = ["blue", "red", "green", "orange", "purple", "yellow"];
|
|
234
|
+
const HEX_COLOR_REGEX = /^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$/;
|
|
235
|
+
const isNumberLineHighlightColor = (value) => NUMBER_LINE_HIGHLIGHT_COLORS.includes(value) || HEX_COLOR_REGEX.test(value);
|
|
236
|
+
const highlightColorMessage = (block) => `${block}: color must be one of ${NUMBER_LINE_HIGHLIGHT_COLORS.join(", ")} or a hex value (e.g. "#3B82F6")`;
|
|
234
237
|
const NUMBER_LINE_DRAG_MODES = ["axis", "inputs"];
|
|
235
238
|
function getFractionKey(widgetId, part) {
|
|
236
239
|
return `${widgetId}__${part}`;
|
|
@@ -750,6 +753,11 @@ class MathExerciseParser extends chevrotain.CstParser {
|
|
|
750
753
|
this.OR([{ ALT: () => this.CONSUME(Space) }, { ALT: () => this.CONSUME(Newline) }]);
|
|
751
754
|
});
|
|
752
755
|
this.CONSUME(ROW);
|
|
756
|
+
this.OPTION(() => {
|
|
757
|
+
this.CONSUME(LBracket);
|
|
758
|
+
this.SUBRULE(this.parameterList);
|
|
759
|
+
this.CONSUME(RBracket);
|
|
760
|
+
});
|
|
753
761
|
this.MANY2(() => {
|
|
754
762
|
this.OR2([{ ALT: () => this.CONSUME2(Space) }, { ALT: () => this.CONSUME2(Newline) }]);
|
|
755
763
|
});
|
|
@@ -1596,7 +1604,9 @@ const ImageInlineSchema = zod.z.object({
|
|
|
1596
1604
|
}),
|
|
1597
1605
|
alt: zod.z.string(),
|
|
1598
1606
|
width: zod.z.number().positive().optional(),
|
|
1599
|
-
height: zod.z.number().positive().optional()
|
|
1607
|
+
height: zod.z.number().positive().optional(),
|
|
1608
|
+
/** Opt-in: show an expand control that opens the image fullscreen. */
|
|
1609
|
+
fullscreen: zod.z.boolean().optional()
|
|
1600
1610
|
});
|
|
1601
1611
|
const VisualFractionInlineSchema = zod.z.object({
|
|
1602
1612
|
type: zod.z.literal(AST_TYPES.VISUAL_FRACTION),
|
|
@@ -1901,10 +1911,8 @@ const NumberLineHighlightSchema = zod.z.object({
|
|
|
1901
1911
|
type: zod.z.literal(AST_TYPES.NUMBER_LINE_HIGHLIGHT),
|
|
1902
1912
|
from: zod.z.number().finite({ message: "HIGHLIGHT: from must be a finite number" }),
|
|
1903
1913
|
to: zod.z.number().finite({ message: "HIGHLIGHT: to must be a finite number" }),
|
|
1904
|
-
color: zod.z.
|
|
1905
|
-
|
|
1906
|
-
message: `HIGHLIGHT: color must be one of ${NUMBER_LINE_HIGHLIGHT_COLORS.join(", ")}`
|
|
1907
|
-
})
|
|
1914
|
+
color: zod.z.string().refine(isNumberLineHighlightColor, {
|
|
1915
|
+
message: highlightColorMessage("HIGHLIGHT")
|
|
1908
1916
|
}).optional(),
|
|
1909
1917
|
label: zod.z.string().optional()
|
|
1910
1918
|
}).refine((d) => d.from < d.to, {
|
|
@@ -1923,7 +1931,7 @@ const NumberLineCursorSchema = zod.z.object({
|
|
|
1923
1931
|
type: zod.z.literal(AST_TYPES.NUMBER_LINE_CURSOR),
|
|
1924
1932
|
value: zod.z.number().finite({ message: "CURSOR: value must be a finite number" }).default(0),
|
|
1925
1933
|
answer: zod.z.number({ required_error: "CURSOR: answer is required" }).finite({ message: "CURSOR: answer must be a finite number" }),
|
|
1926
|
-
color: zod.z.string().regex(
|
|
1934
|
+
color: zod.z.string().regex(HEX_COLOR_REGEX, {
|
|
1927
1935
|
message: 'CURSOR: color must be a hex value (e.g. "#3B82F6" or "#F00")'
|
|
1928
1936
|
}).optional(),
|
|
1929
1937
|
fillRange: zod.z.boolean().default(false),
|
|
@@ -1945,8 +1953,8 @@ const NumberLineArrowSchema = zod.z.object({
|
|
|
1945
1953
|
to: zod.z.number().finite({ message: "ARROW: to must be a finite number" }),
|
|
1946
1954
|
color: zod.z.string().optional(),
|
|
1947
1955
|
inlines: zod.z.array(InlineNodeSchema)
|
|
1948
|
-
}).refine((d) => d.from
|
|
1949
|
-
message: "ARROW: from must be
|
|
1956
|
+
}).refine((d) => d.from !== d.to, {
|
|
1957
|
+
message: "ARROW: from and to must be different"
|
|
1950
1958
|
});
|
|
1951
1959
|
function createNumberLineArrow(from, to, inlines, color) {
|
|
1952
1960
|
return NumberLineArrowSchema.parse({
|
|
@@ -2011,7 +2019,9 @@ const NumberLineBlockSchema = zod.z.object({
|
|
|
2011
2019
|
}
|
|
2012
2020
|
}
|
|
2013
2021
|
for (const arrow of data.arrows ?? []) {
|
|
2014
|
-
|
|
2022
|
+
const lo = Math.min(arrow.from, arrow.to);
|
|
2023
|
+
const hi = Math.max(arrow.from, arrow.to);
|
|
2024
|
+
if (lo < data.min || hi > data.max) {
|
|
2015
2025
|
ctx.addIssue({
|
|
2016
2026
|
code: zod.z.ZodIssueCode.custom,
|
|
2017
2027
|
message: `NUMBER_LINE: ARROW range [${arrow.from}, ${arrow.to}] must be within [${data.min}, ${data.max}]`
|
|
@@ -2062,23 +2072,27 @@ const TableCellSchema = zod.z.object({
|
|
|
2062
2072
|
type: zod.z.literal(AST_TYPES.TABLE_CELL),
|
|
2063
2073
|
colspan: zod.z.number().int().positive().optional(),
|
|
2064
2074
|
rowspan: zod.z.number().int().positive().optional(),
|
|
2075
|
+
backgroundColor: zod.z.string().optional(),
|
|
2065
2076
|
inlines: zod.z.array(InlineNodeSchema)
|
|
2066
2077
|
});
|
|
2067
|
-
function createTableCell(inlines, colspan, rowspan) {
|
|
2078
|
+
function createTableCell(inlines, colspan, rowspan, backgroundColor) {
|
|
2068
2079
|
return TableCellSchema.parse({
|
|
2069
2080
|
type: AST_TYPES.TABLE_CELL,
|
|
2070
2081
|
colspan,
|
|
2071
2082
|
rowspan,
|
|
2083
|
+
backgroundColor,
|
|
2072
2084
|
inlines
|
|
2073
2085
|
});
|
|
2074
2086
|
}
|
|
2075
2087
|
const TableRowSchema = zod.z.object({
|
|
2076
2088
|
type: zod.z.literal(AST_TYPES.TABLE_ROW),
|
|
2089
|
+
backgroundColor: zod.z.string().optional(),
|
|
2077
2090
|
cells: zod.z.array(TableCellSchema).min(1)
|
|
2078
2091
|
});
|
|
2079
|
-
function createTableRow(cells) {
|
|
2092
|
+
function createTableRow(cells, backgroundColor) {
|
|
2080
2093
|
return TableRowSchema.parse({
|
|
2081
2094
|
type: AST_TYPES.TABLE_ROW,
|
|
2095
|
+
backgroundColor,
|
|
2082
2096
|
cells
|
|
2083
2097
|
});
|
|
2084
2098
|
}
|
|
@@ -2678,15 +2692,18 @@ class NaturalDSLVisitor extends BaseCstVisitor {
|
|
|
2678
2692
|
return createTableBlock(cols, rows);
|
|
2679
2693
|
}
|
|
2680
2694
|
row(ctx) {
|
|
2695
|
+
const params = ctx.parameterList ? this.visit(ctx.parameterList) : {};
|
|
2696
|
+
const backgroundColor = params.bgcolor;
|
|
2681
2697
|
const cells = ctx.cell.map((c) => this.visit(c));
|
|
2682
|
-
return createTableRow(cells);
|
|
2698
|
+
return createTableRow(cells, backgroundColor);
|
|
2683
2699
|
}
|
|
2684
2700
|
cell(ctx) {
|
|
2685
2701
|
const params = ctx.parameterList ? this.visit(ctx.parameterList) : {};
|
|
2686
2702
|
const colspan = params.colspan;
|
|
2687
2703
|
const rowspan = params.rowspan;
|
|
2704
|
+
const backgroundColor = params.bgcolor;
|
|
2688
2705
|
const inlines = ctx.inlineElement ? ctx.inlineElement.map((elem) => this.visit(elem)) : [];
|
|
2689
|
-
return createTableCell(inlines, colspan, rowspan);
|
|
2706
|
+
return createTableCell(inlines, colspan, rowspan, backgroundColor);
|
|
2690
2707
|
}
|
|
2691
2708
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
2692
2709
|
// Number Line Block
|
|
@@ -3096,7 +3113,8 @@ class NaturalDSLVisitor extends BaseCstVisitor {
|
|
|
3096
3113
|
const alt = params.alt;
|
|
3097
3114
|
const width = params.width;
|
|
3098
3115
|
const height = params.height;
|
|
3099
|
-
|
|
3116
|
+
const fullscreen = params.fullscreen !== void 0 ? params.fullscreen === true || params.fullscreen === "true" : void 0;
|
|
3117
|
+
return createImage(name, src, alt, { width, height, fullscreen });
|
|
3100
3118
|
}
|
|
3101
3119
|
visualFraction(ctx) {
|
|
3102
3120
|
const params = this.visit(ctx.parameterList);
|
|
@@ -3318,7 +3336,9 @@ const ImageItemSchema = zod.z.object({
|
|
|
3318
3336
|
}),
|
|
3319
3337
|
alt: zod.z.string(),
|
|
3320
3338
|
width: zod.z.number().positive().optional(),
|
|
3321
|
-
height: zod.z.number().positive().optional()
|
|
3339
|
+
height: zod.z.number().positive().optional(),
|
|
3340
|
+
/** Opt-in: show an expand control that opens the image fullscreen. */
|
|
3341
|
+
fullscreen: zod.z.boolean().optional()
|
|
3322
3342
|
});
|
|
3323
3343
|
const SpaceItemSchema = zod.z.object({
|
|
3324
3344
|
id: zod.z.string(),
|
|
@@ -3388,11 +3408,13 @@ const TableCellItemSchema = zod.z.object({
|
|
|
3388
3408
|
type: zod.z.literal(JSON_TYPES.TABLE_CELL),
|
|
3389
3409
|
colspan: zod.z.number().int().positive().optional(),
|
|
3390
3410
|
rowspan: zod.z.number().int().positive().optional(),
|
|
3411
|
+
backgroundColor: zod.z.string().optional(),
|
|
3391
3412
|
items: zod.z.array(LayoutItemSchema)
|
|
3392
3413
|
});
|
|
3393
3414
|
const TableRowItemSchema = zod.z.object({
|
|
3394
3415
|
id: zod.z.string(),
|
|
3395
3416
|
type: zod.z.literal(JSON_TYPES.TABLE_ROW),
|
|
3417
|
+
backgroundColor: zod.z.string().optional(),
|
|
3396
3418
|
cells: zod.z.array(TableCellItemSchema)
|
|
3397
3419
|
});
|
|
3398
3420
|
const TableItemSchema = zod.z.object({
|
|
@@ -3421,7 +3443,9 @@ const NumberLineHighlightItemSchema = zod.z.object({
|
|
|
3421
3443
|
type: zod.z.literal(JSON_TYPES.NUMBER_LINE_HIGHLIGHT),
|
|
3422
3444
|
from: zod.z.number().finite(),
|
|
3423
3445
|
to: zod.z.number().finite(),
|
|
3424
|
-
color: zod.z.
|
|
3446
|
+
color: zod.z.string().refine(isNumberLineHighlightColor, {
|
|
3447
|
+
message: highlightColorMessage("HIGHLIGHT")
|
|
3448
|
+
}).optional(),
|
|
3425
3449
|
label: zod.z.string().optional()
|
|
3426
3450
|
});
|
|
3427
3451
|
const NumberLineCursorItemSchema = zod.z.object({
|
|
@@ -3429,7 +3453,7 @@ const NumberLineCursorItemSchema = zod.z.object({
|
|
|
3429
3453
|
type: zod.z.literal(JSON_TYPES.NUMBER_LINE_CURSOR),
|
|
3430
3454
|
value: zod.z.number().finite(),
|
|
3431
3455
|
answer: zod.z.number().finite(),
|
|
3432
|
-
color: zod.z.string().regex(
|
|
3456
|
+
color: zod.z.string().regex(HEX_COLOR_REGEX).optional(),
|
|
3433
3457
|
fillRange: zod.z.boolean(),
|
|
3434
3458
|
label: zod.z.string().optional()
|
|
3435
3459
|
});
|
|
@@ -9677,13 +9701,20 @@ function transformTable(table, idGenerator, inlineDragIds) {
|
|
|
9677
9701
|
if (cell.rowspan !== void 0) {
|
|
9678
9702
|
cellItem.rowspan = cell.rowspan;
|
|
9679
9703
|
}
|
|
9704
|
+
if (cell.backgroundColor !== void 0) {
|
|
9705
|
+
cellItem.backgroundColor = cell.backgroundColor;
|
|
9706
|
+
}
|
|
9680
9707
|
return cellItem;
|
|
9681
9708
|
});
|
|
9682
|
-
|
|
9709
|
+
const rowItem = {
|
|
9683
9710
|
id: idGenerator.generate(JSON_TYPES.TABLE_ROW),
|
|
9684
9711
|
type: JSON_TYPES.TABLE_ROW,
|
|
9685
9712
|
cells
|
|
9686
9713
|
};
|
|
9714
|
+
if (row.backgroundColor !== void 0) {
|
|
9715
|
+
rowItem.backgroundColor = row.backgroundColor;
|
|
9716
|
+
}
|
|
9717
|
+
return rowItem;
|
|
9687
9718
|
});
|
|
9688
9719
|
return {
|
|
9689
9720
|
id: idGenerator.generate(JSON_TYPES.TABLE),
|
|
@@ -9768,7 +9799,8 @@ function transformInline(inline, idGenerator, inlineDragIds) {
|
|
|
9768
9799
|
src: inline.src,
|
|
9769
9800
|
alt: inline.alt,
|
|
9770
9801
|
...inline.width !== void 0 && { width: inline.width },
|
|
9771
|
-
...inline.height !== void 0 && { height: inline.height }
|
|
9802
|
+
...inline.height !== void 0 && { height: inline.height },
|
|
9803
|
+
...inline.fullscreen !== void 0 && { fullscreen: inline.fullscreen }
|
|
9772
9804
|
};
|
|
9773
9805
|
case AST_TYPES.VISUAL_FRACTION:
|
|
9774
9806
|
return {
|
|
@@ -16638,6 +16670,226 @@ const WidgetRenderer = ({
|
|
|
16638
16670
|
}
|
|
16639
16671
|
return null;
|
|
16640
16672
|
};
|
|
16673
|
+
const ExpandDiamondIcon = () => {
|
|
16674
|
+
const gradientId = React.useId();
|
|
16675
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
16676
|
+
"svg",
|
|
16677
|
+
{
|
|
16678
|
+
className: "image-zoom-icon",
|
|
16679
|
+
viewBox: "0 0 60 60",
|
|
16680
|
+
fill: "none",
|
|
16681
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
16682
|
+
"aria-hidden": "true",
|
|
16683
|
+
focusable: "false",
|
|
16684
|
+
children: [
|
|
16685
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
16686
|
+
"rect",
|
|
16687
|
+
{
|
|
16688
|
+
y: "-1.41531",
|
|
16689
|
+
width: "31.6754",
|
|
16690
|
+
height: "31.6754",
|
|
16691
|
+
rx: "7",
|
|
16692
|
+
transform: "matrix(0.706556 0.707657 -0.706556 0.707657 29.4526 10.0862)",
|
|
16693
|
+
fill: "white"
|
|
16694
|
+
}
|
|
16695
|
+
),
|
|
16696
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
16697
|
+
"rect",
|
|
16698
|
+
{
|
|
16699
|
+
y: "-1.41531",
|
|
16700
|
+
width: "31.6754",
|
|
16701
|
+
height: "31.6754",
|
|
16702
|
+
rx: "7",
|
|
16703
|
+
transform: "matrix(0.706556 0.707657 -0.706556 0.707657 29.4526 10.0862)",
|
|
16704
|
+
stroke: "#BDAFFE",
|
|
16705
|
+
strokeWidth: "2"
|
|
16706
|
+
}
|
|
16707
|
+
),
|
|
16708
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
16709
|
+
"rect",
|
|
16710
|
+
{
|
|
16711
|
+
y: "-1.41531",
|
|
16712
|
+
width: "31.6754",
|
|
16713
|
+
height: "31.6754",
|
|
16714
|
+
rx: "7",
|
|
16715
|
+
transform: "matrix(0.706556 0.707657 -0.706556 0.707657 29.4526 10.0862)",
|
|
16716
|
+
stroke: `url(#${gradientId})`,
|
|
16717
|
+
strokeOpacity: "0.3",
|
|
16718
|
+
strokeWidth: "2"
|
|
16719
|
+
}
|
|
16720
|
+
),
|
|
16721
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
16722
|
+
"g",
|
|
16723
|
+
{
|
|
16724
|
+
stroke: "currentColor",
|
|
16725
|
+
strokeWidth: "2.5",
|
|
16726
|
+
strokeLinecap: "round",
|
|
16727
|
+
strokeLinejoin: "round",
|
|
16728
|
+
children: [
|
|
16729
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M22.45 27 L22.45 23.5 L25.95 23.5" }),
|
|
16730
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M34.95 23.5 L38.45 23.5 L38.45 27" }),
|
|
16731
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M38.45 36 L38.45 39.5 L34.95 39.5" }),
|
|
16732
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M25.95 39.5 L22.45 39.5 L22.45 36" })
|
|
16733
|
+
]
|
|
16734
|
+
}
|
|
16735
|
+
),
|
|
16736
|
+
/* @__PURE__ */ jsxRuntime.jsx("defs", { children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
16737
|
+
"linearGradient",
|
|
16738
|
+
{
|
|
16739
|
+
id: gradientId,
|
|
16740
|
+
x1: "3.84406",
|
|
16741
|
+
y1: "3.97407",
|
|
16742
|
+
x2: "24.7079",
|
|
16743
|
+
y2: "24.8379",
|
|
16744
|
+
gradientUnits: "userSpaceOnUse",
|
|
16745
|
+
children: [
|
|
16746
|
+
/* @__PURE__ */ jsxRuntime.jsx("stop", { stopColor: "#FAFAFA" }),
|
|
16747
|
+
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "1", stopColor: "#8A4899" })
|
|
16748
|
+
]
|
|
16749
|
+
}
|
|
16750
|
+
) })
|
|
16751
|
+
]
|
|
16752
|
+
}
|
|
16753
|
+
);
|
|
16754
|
+
};
|
|
16755
|
+
const CloseIcon = () => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
16756
|
+
"svg",
|
|
16757
|
+
{
|
|
16758
|
+
className: "image-lightbox-close-icon",
|
|
16759
|
+
viewBox: "0 0 24 24",
|
|
16760
|
+
fill: "none",
|
|
16761
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
16762
|
+
"aria-hidden": "true",
|
|
16763
|
+
focusable: "false",
|
|
16764
|
+
children: [
|
|
16765
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
16766
|
+
"path",
|
|
16767
|
+
{
|
|
16768
|
+
d: "M18 6L6 18",
|
|
16769
|
+
stroke: "currentColor",
|
|
16770
|
+
strokeWidth: "2",
|
|
16771
|
+
strokeLinecap: "round",
|
|
16772
|
+
strokeLinejoin: "round"
|
|
16773
|
+
}
|
|
16774
|
+
),
|
|
16775
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
16776
|
+
"path",
|
|
16777
|
+
{
|
|
16778
|
+
d: "M6 6L18 18",
|
|
16779
|
+
stroke: "currentColor",
|
|
16780
|
+
strokeWidth: "2",
|
|
16781
|
+
strokeLinecap: "round",
|
|
16782
|
+
strokeLinejoin: "round"
|
|
16783
|
+
}
|
|
16784
|
+
)
|
|
16785
|
+
]
|
|
16786
|
+
}
|
|
16787
|
+
);
|
|
16788
|
+
const ImageItem = ({
|
|
16789
|
+
src,
|
|
16790
|
+
alt,
|
|
16791
|
+
width,
|
|
16792
|
+
height,
|
|
16793
|
+
fullscreen = false
|
|
16794
|
+
}) => {
|
|
16795
|
+
const [isOpen, setIsOpen] = React.useState(false);
|
|
16796
|
+
const triggerRef = React.useRef(null);
|
|
16797
|
+
const closeRef = React.useRef(null);
|
|
16798
|
+
const open = React.useCallback(() => setIsOpen(true), []);
|
|
16799
|
+
const close = React.useCallback(() => setIsOpen(false), []);
|
|
16800
|
+
const isLightboxOpen = isOpen && fullscreen;
|
|
16801
|
+
React.useEffect(() => {
|
|
16802
|
+
if (!isLightboxOpen) return;
|
|
16803
|
+
const onKeyDown = (e) => {
|
|
16804
|
+
if (e.key === "Escape") {
|
|
16805
|
+
e.stopPropagation();
|
|
16806
|
+
close();
|
|
16807
|
+
}
|
|
16808
|
+
};
|
|
16809
|
+
document.addEventListener("keydown", onKeyDown);
|
|
16810
|
+
return () => document.removeEventListener("keydown", onKeyDown);
|
|
16811
|
+
}, [isLightboxOpen, close]);
|
|
16812
|
+
React.useEffect(() => {
|
|
16813
|
+
if (!isLightboxOpen) return;
|
|
16814
|
+
const previous = document.body.style.overflow;
|
|
16815
|
+
document.body.style.overflow = "hidden";
|
|
16816
|
+
return () => {
|
|
16817
|
+
document.body.style.overflow = previous;
|
|
16818
|
+
};
|
|
16819
|
+
}, [isLightboxOpen]);
|
|
16820
|
+
const wasOpen = React.useRef(false);
|
|
16821
|
+
React.useEffect(() => {
|
|
16822
|
+
if (isLightboxOpen) {
|
|
16823
|
+
closeRef.current?.focus();
|
|
16824
|
+
} else if (wasOpen.current) {
|
|
16825
|
+
triggerRef.current?.focus({ preventScroll: true });
|
|
16826
|
+
}
|
|
16827
|
+
wasOpen.current = isLightboxOpen;
|
|
16828
|
+
}, [isLightboxOpen]);
|
|
16829
|
+
const overlay = isLightboxOpen && typeof document !== "undefined" ? reactDom.createPortal(
|
|
16830
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
16831
|
+
"div",
|
|
16832
|
+
{
|
|
16833
|
+
className: "image-lightbox",
|
|
16834
|
+
role: "dialog",
|
|
16835
|
+
"aria-modal": "true",
|
|
16836
|
+
"aria-label": alt || "Image",
|
|
16837
|
+
onClick: close,
|
|
16838
|
+
children: [
|
|
16839
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
16840
|
+
"button",
|
|
16841
|
+
{
|
|
16842
|
+
ref: closeRef,
|
|
16843
|
+
type: "button",
|
|
16844
|
+
className: "image-lightbox-close",
|
|
16845
|
+
"aria-label": "Close fullscreen image",
|
|
16846
|
+
onClick: close,
|
|
16847
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(CloseIcon, {})
|
|
16848
|
+
}
|
|
16849
|
+
),
|
|
16850
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
16851
|
+
"img",
|
|
16852
|
+
{
|
|
16853
|
+
className: "image-lightbox-img",
|
|
16854
|
+
src,
|
|
16855
|
+
alt,
|
|
16856
|
+
onClick: (e) => e.stopPropagation()
|
|
16857
|
+
}
|
|
16858
|
+
)
|
|
16859
|
+
]
|
|
16860
|
+
}
|
|
16861
|
+
),
|
|
16862
|
+
document.body
|
|
16863
|
+
) : null;
|
|
16864
|
+
const img = /* @__PURE__ */ jsxRuntime.jsx(
|
|
16865
|
+
"img",
|
|
16866
|
+
{
|
|
16867
|
+
className: `image-item text-item ${fullscreen ? "image-item-zoomable" : ""}`,
|
|
16868
|
+
src,
|
|
16869
|
+
alt,
|
|
16870
|
+
...width !== void 0 && { width },
|
|
16871
|
+
...height !== void 0 && { height },
|
|
16872
|
+
loading: "lazy",
|
|
16873
|
+
...fullscreen && { onClick: open }
|
|
16874
|
+
}
|
|
16875
|
+
);
|
|
16876
|
+
if (!fullscreen) return img;
|
|
16877
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "image-item-wrapper", children: [
|
|
16878
|
+
img,
|
|
16879
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
16880
|
+
"button",
|
|
16881
|
+
{
|
|
16882
|
+
ref: triggerRef,
|
|
16883
|
+
type: "button",
|
|
16884
|
+
className: "image-zoom-btn",
|
|
16885
|
+
"aria-label": alt ? `View "${alt}" fullscreen` : "View image fullscreen",
|
|
16886
|
+
onClick: open,
|
|
16887
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(ExpandDiamondIcon, {})
|
|
16888
|
+
}
|
|
16889
|
+
),
|
|
16890
|
+
overlay
|
|
16891
|
+
] });
|
|
16892
|
+
};
|
|
16641
16893
|
const EPSILON = 10 ** -INTERNAL_PRECISION;
|
|
16642
16894
|
const cleanFloat = (n) => parseFloat(n.toFixed(INTERNAL_PRECISION));
|
|
16643
16895
|
const roundTick = (n) => parseFloat(n.toFixed(DISPLAY_PRECISION));
|
|
@@ -16691,7 +16943,7 @@ const getPositionWidgetId = (content) => {
|
|
|
16691
16943
|
}
|
|
16692
16944
|
return null;
|
|
16693
16945
|
};
|
|
16694
|
-
const toHighlightColor = (color) => `var(--nl-highlight-${color})`;
|
|
16946
|
+
const toHighlightColor = (color) => color.startsWith("#") ? color : `var(--nl-highlight-${color})`;
|
|
16695
16947
|
const getBandGeometry = (h, min, max) => {
|
|
16696
16948
|
const leftPct = toPercent(Math.min(h.from, h.to), min, max);
|
|
16697
16949
|
const rightPct = toPercent(Math.max(h.from, h.to), min, max);
|
|
@@ -17388,24 +17640,27 @@ function getArrowArcLabelTop(widthPct) {
|
|
|
17388
17640
|
const arcTop = svgTop + ARROW_ARC_BASE_Y - getArrowArcLift(widthPct);
|
|
17389
17641
|
return Math.max(0, arcTop - ARROW_ARC_LABEL_OFFSET);
|
|
17390
17642
|
}
|
|
17391
|
-
function getArrowArcPaths(widthPct, svgWidthPx = ARROW_ARC_VIEWBOX_WIDTH) {
|
|
17392
|
-
const
|
|
17643
|
+
function getArrowArcPaths(widthPct, svgWidthPx = ARROW_ARC_VIEWBOX_WIDTH, reversed = false) {
|
|
17644
|
+
const width = getArrowArcSvgWidth(svgWidthPx);
|
|
17645
|
+
const startX = reversed ? width : 0;
|
|
17646
|
+
const tipX = reversed ? 0 : width;
|
|
17647
|
+
const sign = reversed ? -1 : 1;
|
|
17393
17648
|
const controlInset = Math.min(svgWidthPx * 0.18, 48);
|
|
17394
17649
|
const lift = getArrowArcLift(widthPct);
|
|
17395
17650
|
const controlY = fmt(ARROW_ARC_BASE_Y - lift);
|
|
17396
|
-
const tangentAngle = Math.atan2(ARROW_ARC_BASE_Y - controlY,
|
|
17397
|
-
const tx = Math.cos(tangentAngle);
|
|
17651
|
+
const tangentAngle = Math.atan2(ARROW_ARC_BASE_Y - controlY, controlInset);
|
|
17652
|
+
const tx = Math.cos(tangentAngle) * sign;
|
|
17398
17653
|
const ty = Math.sin(tangentAngle);
|
|
17399
|
-
const shaftEndX = fmt(
|
|
17654
|
+
const shaftEndX = fmt(tipX - tx * ARROW_ARC_HEAD_LENGTH);
|
|
17400
17655
|
const shaftEndY = fmt(ARROW_ARC_BASE_Y - ty * ARROW_ARC_HEAD_LENGTH);
|
|
17401
17656
|
const endControlX = fmt(shaftEndX - tx * controlInset);
|
|
17402
17657
|
const endControlY = fmt(shaftEndY - ty * controlInset);
|
|
17403
|
-
const arcPath = `M
|
|
17658
|
+
const arcPath = `M ${fmt(startX)} ${ARROW_ARC_BASE_Y} C ${fmt(startX + sign * controlInset)} ${controlY} ${endControlX} ${endControlY} ${shaftEndX} ${shaftEndY}`;
|
|
17404
17659
|
const nx = -ty;
|
|
17405
17660
|
const ny = tx;
|
|
17406
|
-
const baseX =
|
|
17661
|
+
const baseX = tipX - tx * ARROW_ARC_HEAD_LENGTH;
|
|
17407
17662
|
const baseY = ARROW_ARC_BASE_Y - ty * ARROW_ARC_HEAD_LENGTH;
|
|
17408
|
-
const headPath = `M ${fmt(
|
|
17663
|
+
const headPath = `M ${fmt(tipX)} ${ARROW_ARC_BASE_Y} L ${fmt(baseX + nx * ARROW_ARC_HEAD_HALF_WIDTH)} ${fmt(baseY + ny * ARROW_ARC_HEAD_HALF_WIDTH)} L ${fmt(baseX - nx * ARROW_ARC_HEAD_HALF_WIDTH)} ${fmt(baseY - ny * ARROW_ARC_HEAD_HALF_WIDTH)} Z`;
|
|
17409
17664
|
return { arcPath, headPath };
|
|
17410
17665
|
}
|
|
17411
17666
|
function getArrowArcLayouts(arrows, min, max) {
|
|
@@ -17416,9 +17671,10 @@ function getArrowArcLayouts(arrows, min, max) {
|
|
|
17416
17671
|
return {
|
|
17417
17672
|
arrow,
|
|
17418
17673
|
index,
|
|
17419
|
-
leftPct: fromPct,
|
|
17420
|
-
rightPct: toPct,
|
|
17421
|
-
widthPct: toPct - fromPct,
|
|
17674
|
+
leftPct: Math.min(fromPct, toPct),
|
|
17675
|
+
rightPct: Math.max(fromPct, toPct),
|
|
17676
|
+
widthPct: Math.abs(toPct - fromPct),
|
|
17677
|
+
reversed: toPct < fromPct,
|
|
17422
17678
|
level: 0
|
|
17423
17679
|
};
|
|
17424
17680
|
}).filter((layout) => layout.widthPct > 0).sort((a, b) => a.leftPct - b.leftPct || a.rightPct - b.rightPct || a.index - b.index);
|
|
@@ -17455,7 +17711,7 @@ function useElementWidth() {
|
|
|
17455
17711
|
return [ref, width];
|
|
17456
17712
|
}
|
|
17457
17713
|
const ArrowArc = ({
|
|
17458
|
-
layout: { arrow, leftPct, widthPct, level },
|
|
17714
|
+
layout: { arrow, leftPct, widthPct, reversed, level },
|
|
17459
17715
|
widgets,
|
|
17460
17716
|
values,
|
|
17461
17717
|
droppedInputs,
|
|
@@ -17467,8 +17723,8 @@ const ArrowArc = ({
|
|
|
17467
17723
|
const hasLabel = arrow.content.length > 0;
|
|
17468
17724
|
const svgWidthPx = getArrowArcSvgWidth(renderedWidthPx);
|
|
17469
17725
|
const { arcPath, headPath } = React.useMemo(
|
|
17470
|
-
() => getArrowArcPaths(widthPct, svgWidthPx),
|
|
17471
|
-
[widthPct, svgWidthPx]
|
|
17726
|
+
() => getArrowArcPaths(widthPct, svgWidthPx, reversed),
|
|
17727
|
+
[widthPct, svgWidthPx, reversed]
|
|
17472
17728
|
);
|
|
17473
17729
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
17474
17730
|
"div",
|
|
@@ -18224,14 +18480,13 @@ const LayoutRenderer = ({
|
|
|
18224
18480
|
}
|
|
18225
18481
|
case JSON_TYPES.IMAGE:
|
|
18226
18482
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
18227
|
-
|
|
18483
|
+
ImageItem,
|
|
18228
18484
|
{
|
|
18229
|
-
className: "image-item text-item",
|
|
18230
18485
|
src: item.src,
|
|
18231
18486
|
alt: item.alt,
|
|
18232
|
-
|
|
18233
|
-
|
|
18234
|
-
|
|
18487
|
+
width: item.width,
|
|
18488
|
+
height: item.height,
|
|
18489
|
+
fullscreen: item.fullscreen
|
|
18235
18490
|
}
|
|
18236
18491
|
);
|
|
18237
18492
|
case JSON_TYPES.WIDGET_REF: {
|
|
@@ -18282,30 +18537,37 @@ const LayoutRenderer = ({
|
|
|
18282
18537
|
},
|
|
18283
18538
|
subItem.id
|
|
18284
18539
|
)) });
|
|
18285
|
-
case JSON_TYPES.TABLE:
|
|
18286
|
-
|
|
18287
|
-
|
|
18288
|
-
|
|
18289
|
-
|
|
18290
|
-
|
|
18291
|
-
|
|
18292
|
-
|
|
18293
|
-
|
|
18294
|
-
|
|
18295
|
-
|
|
18296
|
-
|
|
18297
|
-
|
|
18298
|
-
|
|
18299
|
-
|
|
18300
|
-
|
|
18301
|
-
|
|
18302
|
-
|
|
18303
|
-
|
|
18304
|
-
|
|
18305
|
-
|
|
18306
|
-
|
|
18307
|
-
|
|
18308
|
-
|
|
18540
|
+
case JSON_TYPES.TABLE: {
|
|
18541
|
+
const totalRows = item.rows.length;
|
|
18542
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "exercise-table-container", children: /* @__PURE__ */ jsxRuntime.jsx("table", { className: "exercise-table", children: /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: item.rows.map((row, rowIndex) => /* @__PURE__ */ jsxRuntime.jsx("tr", { className: "exercise-table-row", style: { backgroundColor: row.backgroundColor }, children: row.cells.map((cell) => {
|
|
18543
|
+
const rowspan = cell.rowspan || 1;
|
|
18544
|
+
const isLastRow = rowIndex + rowspan - 1 >= totalRows - 1;
|
|
18545
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
18546
|
+
"td",
|
|
18547
|
+
{
|
|
18548
|
+
className: `exercise-table-cell${isLastRow ? " exercise-table-cell--last-row" : ""}`,
|
|
18549
|
+
colSpan: cell.colspan || 1,
|
|
18550
|
+
rowSpan: rowspan,
|
|
18551
|
+
style: { backgroundColor: cell.backgroundColor },
|
|
18552
|
+
children: cell.items.map((subItem) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
18553
|
+
LayoutRenderer,
|
|
18554
|
+
{
|
|
18555
|
+
item: subItem,
|
|
18556
|
+
widgets,
|
|
18557
|
+
values,
|
|
18558
|
+
droppedInputs,
|
|
18559
|
+
validationDetails,
|
|
18560
|
+
onChange,
|
|
18561
|
+
onInputFocus,
|
|
18562
|
+
resetKey
|
|
18563
|
+
},
|
|
18564
|
+
subItem.id
|
|
18565
|
+
))
|
|
18566
|
+
},
|
|
18567
|
+
cell.id
|
|
18568
|
+
);
|
|
18569
|
+
}) }, row.id)) }) }) });
|
|
18570
|
+
}
|
|
18309
18571
|
case JSON_TYPES.NUMBER_LINE:
|
|
18310
18572
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
18311
18573
|
NumberLineRenderer,
|