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