@mathscan/math-exercise-engine 1.2.9 → 1.2.11
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/README.md +26 -0
- package/dist/{GeometryWidget-BRV6LLcW.mjs → GeometryWidget-2TCLbTEv.mjs} +2 -2
- package/dist/{GeometryWidget-BRV6LLcW.mjs.map → GeometryWidget-2TCLbTEv.mjs.map} +1 -1
- package/dist/{GeometryWidget-D2QyGzW-.js → GeometryWidget-BTPjXJz-.js} +2 -2
- package/dist/{GeometryWidget-D2QyGzW-.js.map → GeometryWidget-BTPjXJz-.js.map} +1 -1
- package/dist/{index-XAInSHXa.mjs → index-6z_8j1tM.mjs} +1675 -395
- package/dist/index-6z_8j1tM.mjs.map +1 -0
- package/dist/{index-D-QbiBn5.js → index-C3qDr71I.js} +1485 -205
- package/dist/index-C3qDr71I.js.map +1 -0
- package/dist/index.d.ts +14600 -7591
- package/dist/index.js +22 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +212 -191
- package/dist/index.mjs.map +1 -1
- package/dist/style.css +559 -38
- package/package.json +1 -1
- package/dist/index-D-QbiBn5.js.map +0 -1
- package/dist/index-XAInSHXa.mjs.map +0 -1
|
@@ -100,6 +100,12 @@ const AST_TYPES = {
|
|
|
100
100
|
CHART_BAR: "ChartBar",
|
|
101
101
|
/** A single pictograph item inside a CHART[type="pictograph"] block */
|
|
102
102
|
CHART_PICTO: "ChartPicto",
|
|
103
|
+
/** A named series (color + legend label) inside a CHART[type="grouped-bar"] block */
|
|
104
|
+
CHART_SERIES: "ChartSeries",
|
|
105
|
+
/** One category cluster inside a CHART[type="grouped-bar"] block */
|
|
106
|
+
CHART_GROUP: "ChartGroup",
|
|
107
|
+
/** A single bar inside a GROUP, referencing a declared SERIES by id */
|
|
108
|
+
CHART_SERIES_BAR: "ChartSeriesBar",
|
|
103
109
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
104
110
|
// Tools
|
|
105
111
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -203,6 +209,10 @@ const JSON_TYPES = {
|
|
|
203
209
|
CHART_BAR: "chart-bar",
|
|
204
210
|
/** A single pictograph item rendered inside a chart */
|
|
205
211
|
CHART_PICTO: "chart-picto",
|
|
212
|
+
/** A single bar rendered inside a grouped-bar chart's GROUP — distinct from
|
|
213
|
+
* CHART_BAR so the JSON self-identifies which shape it is (has `seriesId`),
|
|
214
|
+
* independent of the widget type used for validation. */
|
|
215
|
+
CHART_SERIES_BAR: "chart-series-bar",
|
|
206
216
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
207
217
|
/** Inline input widget (string or numeric) */
|
|
208
218
|
INLINE_INPUT: "inline-input",
|
|
@@ -230,6 +240,7 @@ const JSON_TYPES = {
|
|
|
230
240
|
GEOMETRY: "geometry"
|
|
231
241
|
};
|
|
232
242
|
const DRAG_OPTIONS_WIDGET_ID = "_drag_options";
|
|
243
|
+
const NUMERIC_KEYPAD_WIDGET_ID = "_numeric_keypad";
|
|
233
244
|
const RULER_WIDGET_ID = "_ruler";
|
|
234
245
|
const PROTRACTOR_WIDGET_ID = "_protractor";
|
|
235
246
|
const SET_SQUARE_WIDGET_ID = "_set_square";
|
|
@@ -262,7 +273,8 @@ const INPUT_TYPES = {
|
|
|
262
273
|
/** Numeric input (digits only) */
|
|
263
274
|
NUMERIC: "numeric"
|
|
264
275
|
};
|
|
265
|
-
const NUMBER_LINE_STYLES = ["arrow-right", "arrow-left", "arrow-both", "line"];
|
|
276
|
+
const NUMBER_LINE_STYLES = ["arrow-right", "arrow-left", "arrow-both", "line", "line-left"];
|
|
277
|
+
const DEFAULT_NUMBER_LINE_STYLE = "line-left";
|
|
266
278
|
const INTERNAL_PRECISION = 9;
|
|
267
279
|
const DISPLAY_PRECISION = 2;
|
|
268
280
|
const SIDE_PAD = 7;
|
|
@@ -436,7 +448,7 @@ const MATCHER = createToken({
|
|
|
436
448
|
});
|
|
437
449
|
const GROUP = createToken({
|
|
438
450
|
name: "GROUP",
|
|
439
|
-
pattern: /GROUP/
|
|
451
|
+
pattern: /GROUP(?=\s*[[{])/
|
|
440
452
|
});
|
|
441
453
|
const ITEM = createToken({
|
|
442
454
|
name: "ITEM",
|
|
@@ -486,6 +498,18 @@ const PICTO = createToken({
|
|
|
486
498
|
name: "PICTO",
|
|
487
499
|
pattern: /PICTO/
|
|
488
500
|
});
|
|
501
|
+
const LABEL = createToken({
|
|
502
|
+
name: "LABEL",
|
|
503
|
+
pattern: /LABEL(?=\s*\{)/
|
|
504
|
+
});
|
|
505
|
+
const SERIES = createToken({
|
|
506
|
+
name: "SERIES",
|
|
507
|
+
pattern: /SERIES(?=\s*\[)/
|
|
508
|
+
});
|
|
509
|
+
const SERIES_BAR = createToken({
|
|
510
|
+
name: "SERIES_BAR",
|
|
511
|
+
pattern: /SERIES_BAR(?=\s*\[)/
|
|
512
|
+
});
|
|
489
513
|
const SEP = createToken({
|
|
490
514
|
name: "SEP",
|
|
491
515
|
pattern: /SEP(?![a-zA-Z0-9_])/
|
|
@@ -684,6 +708,9 @@ const allTokens = [
|
|
|
684
708
|
Y_AXIS,
|
|
685
709
|
BAR,
|
|
686
710
|
PICTO,
|
|
711
|
+
LABEL,
|
|
712
|
+
SERIES_BAR,
|
|
713
|
+
SERIES,
|
|
687
714
|
SEP,
|
|
688
715
|
CARRY,
|
|
689
716
|
INPUT,
|
|
@@ -1770,22 +1797,15 @@ class MathExerciseParser extends CstParser {
|
|
|
1770
1797
|
});
|
|
1771
1798
|
this.MANY6({
|
|
1772
1799
|
GATE: () => {
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
const t = this.LA(k).tokenType;
|
|
1776
|
-
return t === BAR || t === PICTO;
|
|
1800
|
+
const t = this.LA(1).tokenType;
|
|
1801
|
+
return t === BAR || t === PICTO || t === SERIES || t === GROUP;
|
|
1777
1802
|
},
|
|
1778
1803
|
DEF: () => {
|
|
1779
1804
|
this.OR6([
|
|
1780
|
-
{
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
return this.LA(k).tokenType === BAR;
|
|
1785
|
-
},
|
|
1786
|
-
ALT: () => this.SUBRULE(this.chartBarItem)
|
|
1787
|
-
},
|
|
1788
|
-
{ ALT: () => this.SUBRULE(this.chartPictoItem) }
|
|
1805
|
+
{ GATE: () => this.LA(1).tokenType === BAR, ALT: () => this.SUBRULE(this.chartBarItem) },
|
|
1806
|
+
{ GATE: () => this.LA(1).tokenType === PICTO, ALT: () => this.SUBRULE(this.chartPictoItem) },
|
|
1807
|
+
{ GATE: () => this.LA(1).tokenType === SERIES, ALT: () => this.SUBRULE(this.chartSeriesItem) },
|
|
1808
|
+
{ ALT: () => this.SUBRULE(this.chartGroupItem) }
|
|
1789
1809
|
]);
|
|
1790
1810
|
}
|
|
1791
1811
|
});
|
|
@@ -1906,6 +1926,92 @@ class MathExerciseParser extends CstParser {
|
|
|
1906
1926
|
this.OR5([{ ALT: () => this.CONSUME5(Space) }, { ALT: () => this.CONSUME5(Newline) }]);
|
|
1907
1927
|
});
|
|
1908
1928
|
});
|
|
1929
|
+
this.chartSeriesItem = this.RULE("chartSeriesItem", () => {
|
|
1930
|
+
this.MANY(() => {
|
|
1931
|
+
this.OR([{ ALT: () => this.CONSUME(Space) }, { ALT: () => this.CONSUME(Newline) }]);
|
|
1932
|
+
});
|
|
1933
|
+
this.CONSUME(SERIES);
|
|
1934
|
+
this.CONSUME(LBracket);
|
|
1935
|
+
this.SUBRULE(this.parameterList);
|
|
1936
|
+
this.CONSUME(RBracket);
|
|
1937
|
+
this.MANY2(() => {
|
|
1938
|
+
this.OR2([{ ALT: () => this.CONSUME2(Space) }, { ALT: () => this.CONSUME2(Newline) }]);
|
|
1939
|
+
});
|
|
1940
|
+
this.CONSUME(LBrace);
|
|
1941
|
+
this.MANY3(() => {
|
|
1942
|
+
this.OR3([{ ALT: () => this.CONSUME3(Space) }, { ALT: () => this.CONSUME3(Newline) }]);
|
|
1943
|
+
});
|
|
1944
|
+
this.MANY4(() => {
|
|
1945
|
+
this.OR4([
|
|
1946
|
+
{ ALT: () => this.SUBRULE(this.inlineElement) },
|
|
1947
|
+
{ ALT: () => this.CONSUME4(Newline) }
|
|
1948
|
+
]);
|
|
1949
|
+
});
|
|
1950
|
+
this.CONSUME(RBrace);
|
|
1951
|
+
this.MANY5(() => {
|
|
1952
|
+
this.OR5([{ ALT: () => this.CONSUME5(Space) }, { ALT: () => this.CONSUME5(Newline) }]);
|
|
1953
|
+
});
|
|
1954
|
+
});
|
|
1955
|
+
this.chartGroupLabel = this.RULE("chartGroupLabel", () => {
|
|
1956
|
+
this.CONSUME(LABEL);
|
|
1957
|
+
this.MANY(() => {
|
|
1958
|
+
this.OR([{ ALT: () => this.CONSUME(Space) }, { ALT: () => this.CONSUME(Newline) }]);
|
|
1959
|
+
});
|
|
1960
|
+
this.CONSUME(LBrace);
|
|
1961
|
+
this.MANY2(() => {
|
|
1962
|
+
this.OR2([{ ALT: () => this.CONSUME2(Space) }, { ALT: () => this.CONSUME2(Newline) }]);
|
|
1963
|
+
});
|
|
1964
|
+
this.MANY3(() => {
|
|
1965
|
+
this.OR3([
|
|
1966
|
+
{ ALT: () => this.SUBRULE(this.inlineElement) },
|
|
1967
|
+
{ ALT: () => this.CONSUME3(Newline) }
|
|
1968
|
+
]);
|
|
1969
|
+
});
|
|
1970
|
+
this.CONSUME(RBrace);
|
|
1971
|
+
this.MANY4(() => {
|
|
1972
|
+
this.OR4([{ ALT: () => this.CONSUME4(Space) }, { ALT: () => this.CONSUME4(Newline) }]);
|
|
1973
|
+
});
|
|
1974
|
+
});
|
|
1975
|
+
this.chartSeriesBarItem = this.RULE("chartSeriesBarItem", () => {
|
|
1976
|
+
this.MANY(() => {
|
|
1977
|
+
this.OR([{ ALT: () => this.CONSUME(Space) }, { ALT: () => this.CONSUME(Newline) }]);
|
|
1978
|
+
});
|
|
1979
|
+
this.CONSUME(SERIES_BAR);
|
|
1980
|
+
this.CONSUME(LBracket);
|
|
1981
|
+
this.SUBRULE(this.parameterList);
|
|
1982
|
+
this.CONSUME(RBracket);
|
|
1983
|
+
this.MANY2(() => {
|
|
1984
|
+
this.OR2([{ ALT: () => this.CONSUME2(Space) }, { ALT: () => this.CONSUME2(Newline) }]);
|
|
1985
|
+
});
|
|
1986
|
+
});
|
|
1987
|
+
this.chartGroupItem = this.RULE("chartGroupItem", () => {
|
|
1988
|
+
this.MANY(() => {
|
|
1989
|
+
this.OR([{ ALT: () => this.CONSUME(Space) }, { ALT: () => this.CONSUME(Newline) }]);
|
|
1990
|
+
});
|
|
1991
|
+
this.CONSUME(GROUP);
|
|
1992
|
+
this.CONSUME(LBracket);
|
|
1993
|
+
this.SUBRULE(this.parameterList);
|
|
1994
|
+
this.CONSUME(RBracket);
|
|
1995
|
+
this.MANY2(() => {
|
|
1996
|
+
this.OR2([{ ALT: () => this.CONSUME2(Space) }, { ALT: () => this.CONSUME2(Newline) }]);
|
|
1997
|
+
});
|
|
1998
|
+
this.CONSUME(LBrace);
|
|
1999
|
+
this.MANY3(() => {
|
|
2000
|
+
this.OR3([{ ALT: () => this.CONSUME3(Space) }, { ALT: () => this.CONSUME3(Newline) }]);
|
|
2001
|
+
});
|
|
2002
|
+
this.MANY4({
|
|
2003
|
+
GATE: () => this.LA(1).tokenType === LABEL,
|
|
2004
|
+
DEF: () => this.SUBRULE(this.chartGroupLabel)
|
|
2005
|
+
});
|
|
2006
|
+
this.MANY5({
|
|
2007
|
+
GATE: () => this.LA(1).tokenType === SERIES_BAR,
|
|
2008
|
+
DEF: () => this.SUBRULE(this.chartSeriesBarItem)
|
|
2009
|
+
});
|
|
2010
|
+
this.CONSUME(RBrace);
|
|
2011
|
+
this.MANY6(() => {
|
|
2012
|
+
this.OR4([{ ALT: () => this.CONSUME4(Space) }, { ALT: () => this.CONSUME4(Newline) }]);
|
|
2013
|
+
});
|
|
2014
|
+
});
|
|
1909
2015
|
this.performSelfAnalysis();
|
|
1910
2016
|
}
|
|
1911
2017
|
/** Distinguish `{[number, number], ...}` and the valid empty form `{}` from string lists. */
|
|
@@ -1951,6 +2057,8 @@ const VISUAL_FRACTION_SHAPE = {
|
|
|
1951
2057
|
};
|
|
1952
2058
|
const VISUAL_FRACTION_DEFAULT_SIZE = 1.35;
|
|
1953
2059
|
const VISUAL_FRACTION_MAX_DIVISIONS = 100;
|
|
2060
|
+
const INLINE_INPUT_MAX_WIDTH = 20;
|
|
2061
|
+
const InlineInputWidthSchema = z.number().int().positive().max(INLINE_INPUT_MAX_WIDTH, { message: `width cannot exceed ${INLINE_INPUT_MAX_WIDTH}` }).optional();
|
|
1954
2062
|
const FreeTextInlineSchema = z.object({
|
|
1955
2063
|
type: z.literal(AST_TYPES.FREE_TEXT),
|
|
1956
2064
|
value: z.string()
|
|
@@ -1980,6 +2088,7 @@ const InlineInputWidgetSchema = z.discriminatedUnion("inputType", [
|
|
|
1980
2088
|
name: z.string(),
|
|
1981
2089
|
inputType: z.literal(INPUT_TYPES.STRING),
|
|
1982
2090
|
length: z.number().int().positive(),
|
|
2091
|
+
width: InlineInputWidthSchema,
|
|
1983
2092
|
placeholder: z.string().max(32).optional(),
|
|
1984
2093
|
disabled: z.boolean().optional(),
|
|
1985
2094
|
answer: z.union([z.string(), z.number()]).optional()
|
|
@@ -1989,6 +2098,7 @@ const InlineInputWidgetSchema = z.discriminatedUnion("inputType", [
|
|
|
1989
2098
|
name: z.string(),
|
|
1990
2099
|
inputType: z.literal(INPUT_TYPES.NUMERIC),
|
|
1991
2100
|
digits: z.number().int().positive(),
|
|
2101
|
+
width: InlineInputWidthSchema,
|
|
1992
2102
|
placeholder: z.string().max(32).optional(),
|
|
1993
2103
|
disabled: z.boolean().optional(),
|
|
1994
2104
|
answer: z.union([z.string(), z.number()]).optional()
|
|
@@ -2146,7 +2256,7 @@ function createBoldText(value, highlight) {
|
|
|
2146
2256
|
highlight
|
|
2147
2257
|
});
|
|
2148
2258
|
}
|
|
2149
|
-
function createStringInput(name, length, answer, placeholder, disabled) {
|
|
2259
|
+
function createStringInput(name, length, answer, placeholder, disabled, width) {
|
|
2150
2260
|
return parseOrThrow(
|
|
2151
2261
|
InlineInputWidgetSchema,
|
|
2152
2262
|
{
|
|
@@ -2154,6 +2264,7 @@ function createStringInput(name, length, answer, placeholder, disabled) {
|
|
|
2154
2264
|
name,
|
|
2155
2265
|
inputType: INPUT_TYPES.STRING,
|
|
2156
2266
|
length,
|
|
2267
|
+
width,
|
|
2157
2268
|
placeholder,
|
|
2158
2269
|
disabled,
|
|
2159
2270
|
// A disabled input is never graded, so its answer is dropped.
|
|
@@ -2162,7 +2273,7 @@ function createStringInput(name, length, answer, placeholder, disabled) {
|
|
|
2162
2273
|
"STRING_INPUT"
|
|
2163
2274
|
);
|
|
2164
2275
|
}
|
|
2165
|
-
function createNumericInput(name, digits, answer, placeholder, disabled) {
|
|
2276
|
+
function createNumericInput(name, digits, answer, placeholder, disabled, width) {
|
|
2166
2277
|
return parseOrThrow(
|
|
2167
2278
|
InlineInputWidgetSchema,
|
|
2168
2279
|
{
|
|
@@ -2170,6 +2281,7 @@ function createNumericInput(name, digits, answer, placeholder, disabled) {
|
|
|
2170
2281
|
name,
|
|
2171
2282
|
inputType: INPUT_TYPES.NUMERIC,
|
|
2172
2283
|
digits,
|
|
2284
|
+
width,
|
|
2173
2285
|
placeholder,
|
|
2174
2286
|
disabled,
|
|
2175
2287
|
// A disabled input is never graded, so its answer is dropped.
|
|
@@ -2386,7 +2498,7 @@ const NumberLineBlockSchema = z.object({
|
|
|
2386
2498
|
errorMap: () => ({
|
|
2387
2499
|
message: `NUMBER_LINE: style must be one of ${NUMBER_LINE_STYLES.join(", ")}`
|
|
2388
2500
|
})
|
|
2389
|
-
}).
|
|
2501
|
+
}).default(DEFAULT_NUMBER_LINE_STYLE),
|
|
2390
2502
|
labelVisibility: z.enum(NUMBER_LINE_LABELS, {
|
|
2391
2503
|
errorMap: () => ({
|
|
2392
2504
|
message: `NUMBER_LINE: labelVisibility must be one of ${NUMBER_LINE_LABELS.join(", ")}`
|
|
@@ -3065,7 +3177,7 @@ function isSetSquareTool(value) {
|
|
|
3065
3177
|
function isToolNode(value) {
|
|
3066
3178
|
return ToolNodeSchema.safeParse(value).success;
|
|
3067
3179
|
}
|
|
3068
|
-
const CHART_TYPES = ["bar", "pictograph"];
|
|
3180
|
+
const CHART_TYPES = ["bar", "pictograph", "grouped-bar"];
|
|
3069
3181
|
const CHART_AXIS_DEFAULTS = { min: 0, max: 100, step: 10, visible: true };
|
|
3070
3182
|
const CHART_MAX_AXIS_TICKS = 200;
|
|
3071
3183
|
const CHART_MAX_PICTO_SLOTS = 60;
|
|
@@ -3145,6 +3257,63 @@ function createChartBar(id, opts) {
|
|
|
3145
3257
|
function isChartBar(value) {
|
|
3146
3258
|
return ChartBarSchema.safeParse(value).success;
|
|
3147
3259
|
}
|
|
3260
|
+
const ChartSeriesSchema = z.object({
|
|
3261
|
+
type: z.literal(AST_TYPES.CHART_SERIES),
|
|
3262
|
+
id: z.string().min(1),
|
|
3263
|
+
color: z.string().min(1),
|
|
3264
|
+
inlines: z.array(DisplayInlineNodeSchema).default([])
|
|
3265
|
+
});
|
|
3266
|
+
function createChartSeries(id, opts) {
|
|
3267
|
+
return ChartSeriesSchema.parse({
|
|
3268
|
+
type: AST_TYPES.CHART_SERIES,
|
|
3269
|
+
id,
|
|
3270
|
+
color: opts.color,
|
|
3271
|
+
inlines: opts.inlines ?? []
|
|
3272
|
+
});
|
|
3273
|
+
}
|
|
3274
|
+
function isChartSeries(value) {
|
|
3275
|
+
return ChartSeriesSchema.safeParse(value).success;
|
|
3276
|
+
}
|
|
3277
|
+
const ChartSeriesBarSchema = z.object({
|
|
3278
|
+
type: z.literal(AST_TYPES.CHART_SERIES_BAR),
|
|
3279
|
+
id: z.string().min(1),
|
|
3280
|
+
seriesId: z.string().min(1),
|
|
3281
|
+
value: z.number().default(0),
|
|
3282
|
+
answer: z.number().optional()
|
|
3283
|
+
});
|
|
3284
|
+
function createChartSeriesBar(id, opts) {
|
|
3285
|
+
return ChartSeriesBarSchema.parse({
|
|
3286
|
+
type: AST_TYPES.CHART_SERIES_BAR,
|
|
3287
|
+
id,
|
|
3288
|
+
seriesId: opts.seriesId,
|
|
3289
|
+
value: opts.value ?? 0,
|
|
3290
|
+
answer: opts.answer
|
|
3291
|
+
});
|
|
3292
|
+
}
|
|
3293
|
+
function isChartSeriesBar(value) {
|
|
3294
|
+
return ChartSeriesBarSchema.safeParse(value).success;
|
|
3295
|
+
}
|
|
3296
|
+
const ChartGroupSchema = z.object({
|
|
3297
|
+
type: z.literal(AST_TYPES.CHART_GROUP),
|
|
3298
|
+
id: z.string().min(1),
|
|
3299
|
+
labelInlines: z.array(InlineNodeSchema).default([]),
|
|
3300
|
+
bars: z.array(ChartSeriesBarSchema).default([])
|
|
3301
|
+
});
|
|
3302
|
+
function createChartGroup(id, opts = {}) {
|
|
3303
|
+
return ChartGroupSchema.parse({
|
|
3304
|
+
type: AST_TYPES.CHART_GROUP,
|
|
3305
|
+
id,
|
|
3306
|
+
labelInlines: opts.labelInlines ?? [],
|
|
3307
|
+
bars: opts.bars ?? []
|
|
3308
|
+
});
|
|
3309
|
+
}
|
|
3310
|
+
function isChartGroup(value) {
|
|
3311
|
+
return ChartGroupSchema.safeParse(value).success;
|
|
3312
|
+
}
|
|
3313
|
+
function alignGroupBarsToSeries(group, series) {
|
|
3314
|
+
const bySeriesId = new Map(group.bars.map((bar) => [bar.seriesId, bar]));
|
|
3315
|
+
return series.map((s) => bySeriesId.get(s.id));
|
|
3316
|
+
}
|
|
3148
3317
|
const ChartBlockSchema = z.object({
|
|
3149
3318
|
type: z.literal(AST_TYPES.CHART_BLOCK),
|
|
3150
3319
|
name: z.string().optional(),
|
|
@@ -3156,7 +3325,10 @@ const ChartBlockSchema = z.object({
|
|
|
3156
3325
|
xAxis: ChartAxisSchema.optional(),
|
|
3157
3326
|
yAxis: ChartAxisSchema.optional(),
|
|
3158
3327
|
bars: z.array(ChartBarSchema).default([]),
|
|
3159
|
-
pictos: z.array(ChartPictoSchema).default([])
|
|
3328
|
+
pictos: z.array(ChartPictoSchema).default([]),
|
|
3329
|
+
legend: z.boolean().default(false),
|
|
3330
|
+
series: z.array(ChartSeriesSchema).default([]),
|
|
3331
|
+
groups: z.array(ChartGroupSchema).default([])
|
|
3160
3332
|
}).superRefine((block, ctx) => {
|
|
3161
3333
|
validateChartBlock(block, ctx);
|
|
3162
3334
|
});
|
|
@@ -3181,10 +3353,10 @@ function validateChartBlock(block, ctx) {
|
|
|
3181
3353
|
const { valueAxis } = resolveChartAxes(block);
|
|
3182
3354
|
validateValueAxisRange(valueAxis, ctx);
|
|
3183
3355
|
if (block.chartType === "bar") {
|
|
3184
|
-
if (block.pictos.length > 0) {
|
|
3356
|
+
if (block.pictos.length > 0 || block.series.length > 0 || block.groups.length > 0) {
|
|
3185
3357
|
ctx.addIssue({
|
|
3186
3358
|
code: z.ZodIssueCode.custom,
|
|
3187
|
-
message: 'CHART[type="bar"]: body must contain BAR items only, not PICTO',
|
|
3359
|
+
message: 'CHART[type="bar"]: body must contain BAR items only, not PICTO, SERIES, or GROUP',
|
|
3188
3360
|
path: ["pictos"]
|
|
3189
3361
|
});
|
|
3190
3362
|
return;
|
|
@@ -3197,16 +3369,16 @@ function validateChartBlock(block, ctx) {
|
|
|
3197
3369
|
});
|
|
3198
3370
|
return;
|
|
3199
3371
|
}
|
|
3200
|
-
validateUniqueIds(block.bars, ctx);
|
|
3372
|
+
validateUniqueIds(block.bars, ctx, (i) => ["bars", i]);
|
|
3201
3373
|
validateChartSnap(block.snap, valueAxis.step, ctx);
|
|
3202
|
-
validateBarAnswers(block.bars, valueAxis, block.snap, ctx);
|
|
3374
|
+
validateBarAnswers(block.bars, valueAxis, block.snap, ctx, (i) => ["bars", i]);
|
|
3203
3375
|
return;
|
|
3204
3376
|
}
|
|
3205
3377
|
if (block.chartType === "pictograph") {
|
|
3206
|
-
if (block.bars.length > 0) {
|
|
3378
|
+
if (block.bars.length > 0 || block.series.length > 0 || block.groups.length > 0) {
|
|
3207
3379
|
ctx.addIssue({
|
|
3208
3380
|
code: z.ZodIssueCode.custom,
|
|
3209
|
-
message: 'CHART[type="pictograph"]: body must contain PICTO items only, not BAR',
|
|
3381
|
+
message: 'CHART[type="pictograph"]: body must contain PICTO items only, not BAR, SERIES, or GROUP',
|
|
3210
3382
|
path: ["bars"]
|
|
3211
3383
|
});
|
|
3212
3384
|
return;
|
|
@@ -3227,7 +3399,7 @@ function validateChartBlock(block, ctx) {
|
|
|
3227
3399
|
});
|
|
3228
3400
|
return;
|
|
3229
3401
|
}
|
|
3230
|
-
validateUniqueIds(block.pictos, ctx);
|
|
3402
|
+
validateUniqueIds(block.pictos, ctx, (i) => ["pictos", i]);
|
|
3231
3403
|
if (!axisHasNumericParams(valueAxis.axis)) {
|
|
3232
3404
|
ctx.addIssue({
|
|
3233
3405
|
code: z.ZodIssueCode.custom,
|
|
@@ -3248,6 +3420,63 @@ function validateChartBlock(block, ctx) {
|
|
|
3248
3420
|
validatePictoAnswers(block.pictos, valueAxis, ctx);
|
|
3249
3421
|
return;
|
|
3250
3422
|
}
|
|
3423
|
+
if (block.chartType === "grouped-bar") {
|
|
3424
|
+
if (block.bars.length > 0 || block.pictos.length > 0) {
|
|
3425
|
+
ctx.addIssue({
|
|
3426
|
+
code: z.ZodIssueCode.custom,
|
|
3427
|
+
message: 'CHART[type="grouped-bar"]: body must contain SERIES and GROUP items only, not BAR or PICTO',
|
|
3428
|
+
path: ["bars"]
|
|
3429
|
+
});
|
|
3430
|
+
return;
|
|
3431
|
+
}
|
|
3432
|
+
if (block.series.length === 0) {
|
|
3433
|
+
ctx.addIssue({
|
|
3434
|
+
code: z.ZodIssueCode.custom,
|
|
3435
|
+
message: 'CHART[type="grouped-bar"]: at least one SERIES is required',
|
|
3436
|
+
path: ["series"]
|
|
3437
|
+
});
|
|
3438
|
+
return;
|
|
3439
|
+
}
|
|
3440
|
+
if (block.groups.length === 0) {
|
|
3441
|
+
ctx.addIssue({
|
|
3442
|
+
code: z.ZodIssueCode.custom,
|
|
3443
|
+
message: 'CHART[type="grouped-bar"]: at least one GROUP is required',
|
|
3444
|
+
path: ["groups"]
|
|
3445
|
+
});
|
|
3446
|
+
return;
|
|
3447
|
+
}
|
|
3448
|
+
validateUniqueIds(block.series, ctx, (i) => ["series", i]);
|
|
3449
|
+
validateUniqueIds(block.groups, ctx, (i) => ["groups", i]);
|
|
3450
|
+
const allBars = block.groups.flatMap((g, gi) => g.bars.map((bar, bi) => ({ ...bar, groupIndex: gi, barIndex: bi })));
|
|
3451
|
+
const pathForFlatBar = (i) => ["groups", allBars[i].groupIndex, "bars", allBars[i].barIndex];
|
|
3452
|
+
validateUniqueIds(allBars, ctx, pathForFlatBar);
|
|
3453
|
+
const seriesIds = new Set(block.series.map((s) => s.id));
|
|
3454
|
+
block.groups.forEach((group, gi) => {
|
|
3455
|
+
const seenInGroup = /* @__PURE__ */ new Set();
|
|
3456
|
+
group.bars.forEach((bar, bi) => {
|
|
3457
|
+
if (!seriesIds.has(bar.seriesId)) {
|
|
3458
|
+
ctx.addIssue({
|
|
3459
|
+
code: z.ZodIssueCode.custom,
|
|
3460
|
+
message: `CHART: GROUP "${group.id}" SERIES_BAR "${bar.id}" references unknown series "${bar.seriesId}" — no SERIES with that id is declared`,
|
|
3461
|
+
path: ["groups", gi, "bars", bi, "seriesId"]
|
|
3462
|
+
});
|
|
3463
|
+
return;
|
|
3464
|
+
}
|
|
3465
|
+
if (seenInGroup.has(bar.seriesId)) {
|
|
3466
|
+
ctx.addIssue({
|
|
3467
|
+
code: z.ZodIssueCode.custom,
|
|
3468
|
+
message: `CHART: GROUP "${group.id}" references series "${bar.seriesId}" twice — each SERIES can appear at most once per GROUP`,
|
|
3469
|
+
path: ["groups", gi, "bars", bi, "seriesId"]
|
|
3470
|
+
});
|
|
3471
|
+
return;
|
|
3472
|
+
}
|
|
3473
|
+
seenInGroup.add(bar.seriesId);
|
|
3474
|
+
});
|
|
3475
|
+
});
|
|
3476
|
+
validateChartSnap(block.snap, valueAxis.step, ctx);
|
|
3477
|
+
validateBarAnswers(allBars, valueAxis, block.snap, ctx, pathForFlatBar);
|
|
3478
|
+
return;
|
|
3479
|
+
}
|
|
3251
3480
|
ctx.addIssue({
|
|
3252
3481
|
code: z.ZodIssueCode.custom,
|
|
3253
3482
|
message: `CHART: unsupported chartType "${block.chartType}" — add a validation branch to validateChartBlock`,
|
|
@@ -3268,14 +3497,14 @@ function resolveNumericAxis(axis) {
|
|
|
3268
3497
|
function hasAmbiguousValueAxes(block) {
|
|
3269
3498
|
return axisHasNumericParams(block.xAxis) && axisHasNumericParams(block.yAxis);
|
|
3270
3499
|
}
|
|
3271
|
-
function validateUniqueIds(items, ctx) {
|
|
3500
|
+
function validateUniqueIds(items, ctx, pathFor) {
|
|
3272
3501
|
const seen = /* @__PURE__ */ new Set();
|
|
3273
3502
|
items.forEach((item, index) => {
|
|
3274
3503
|
if (seen.has(item.id)) {
|
|
3275
3504
|
ctx.addIssue({
|
|
3276
3505
|
code: z.ZodIssueCode.custom,
|
|
3277
3506
|
message: `CHART: duplicate id "${item.id}" — ids must be unique within the chart`,
|
|
3278
|
-
path: [index, "id"]
|
|
3507
|
+
path: [...pathFor(index), "id"]
|
|
3279
3508
|
});
|
|
3280
3509
|
return;
|
|
3281
3510
|
}
|
|
@@ -3317,14 +3546,14 @@ function validateChartSnap(snap, step, ctx) {
|
|
|
3317
3546
|
});
|
|
3318
3547
|
}
|
|
3319
3548
|
}
|
|
3320
|
-
function validateBarAnswers(bars, valueAxis, snap, ctx) {
|
|
3549
|
+
function validateBarAnswers(bars, valueAxis, snap, ctx, pathFor) {
|
|
3321
3550
|
const gridStep = snap ?? valueAxis.step;
|
|
3322
3551
|
bars.forEach((bar, index) => {
|
|
3323
3552
|
if (bar.value < valueAxis.min || bar.value > valueAxis.max) {
|
|
3324
3553
|
ctx.addIssue({
|
|
3325
3554
|
code: z.ZodIssueCode.custom,
|
|
3326
3555
|
message: `CHART: BAR "${bar.id}" value (${bar.value}) is outside axis range [${valueAxis.min}, ${valueAxis.max}] — the bar will not render correctly`,
|
|
3327
|
-
path: [
|
|
3556
|
+
path: [...pathFor(index), "value"]
|
|
3328
3557
|
});
|
|
3329
3558
|
}
|
|
3330
3559
|
if (bar.answer === void 0) return;
|
|
@@ -3332,7 +3561,7 @@ function validateBarAnswers(bars, valueAxis, snap, ctx) {
|
|
|
3332
3561
|
ctx.addIssue({
|
|
3333
3562
|
code: z.ZodIssueCode.custom,
|
|
3334
3563
|
message: `CHART: BAR "${bar.id}" answer (${bar.answer}) is outside axis range [${valueAxis.min}, ${valueAxis.max}] — the student can never reach this value`,
|
|
3335
|
-
path: [
|
|
3564
|
+
path: [...pathFor(index), "answer"]
|
|
3336
3565
|
});
|
|
3337
3566
|
return;
|
|
3338
3567
|
}
|
|
@@ -3341,7 +3570,7 @@ function validateBarAnswers(bars, valueAxis, snap, ctx) {
|
|
|
3341
3570
|
ctx.addIssue({
|
|
3342
3571
|
code: z.ZodIssueCode.custom,
|
|
3343
3572
|
message: `CHART: BAR "${bar.id}" answer (${bar.answer}) is not on the drag grid (min ${valueAxis.min} + multiples of ${gridStep}) — the student can never land on it. Align the answer to the step/snap grid.`,
|
|
3344
|
-
path: [
|
|
3573
|
+
path: [...pathFor(index), "answer"]
|
|
3345
3574
|
});
|
|
3346
3575
|
}
|
|
3347
3576
|
});
|
|
@@ -3403,7 +3632,10 @@ function createChartBlock(chartType, opts = {}) {
|
|
|
3403
3632
|
xAxis: opts.xAxis,
|
|
3404
3633
|
yAxis: opts.yAxis,
|
|
3405
3634
|
bars: opts.bars ?? [],
|
|
3406
|
-
pictos: opts.pictos ?? []
|
|
3635
|
+
pictos: opts.pictos ?? [],
|
|
3636
|
+
series: opts.series ?? [],
|
|
3637
|
+
groups: opts.groups ?? [],
|
|
3638
|
+
legend: opts.legend ?? false
|
|
3407
3639
|
});
|
|
3408
3640
|
}
|
|
3409
3641
|
function isChartBlock(value) {
|
|
@@ -3490,7 +3722,9 @@ const ExerciseDocumentSchema = z.object({
|
|
|
3490
3722
|
};
|
|
3491
3723
|
const visitInlines = (inlines, basePath) => {
|
|
3492
3724
|
inlines.forEach((inline, i) => {
|
|
3493
|
-
if (inline.type === AST_TYPES.INLINE_INPUT || inline.type === AST_TYPES.FRACTION || inline.type === AST_TYPES.IMAGE) {
|
|
3725
|
+
if (inline.type === AST_TYPES.INLINE_INPUT || inline.type === AST_TYPES.FRACTION || inline.type === AST_TYPES.IMAGE || inline.type === AST_TYPES.VISUAL_FRACTION) {
|
|
3726
|
+
record(inline.name, [...basePath, i, "name"]);
|
|
3727
|
+
} else if (inline.type === AST_TYPES.CLOCK && inline.mode === "interactive") {
|
|
3494
3728
|
record(inline.name, [...basePath, i, "name"]);
|
|
3495
3729
|
}
|
|
3496
3730
|
});
|
|
@@ -3522,6 +3756,28 @@ const ExerciseDocumentSchema = z.object({
|
|
|
3522
3756
|
pm.groups.flat().forEach((item, ii) => {
|
|
3523
3757
|
visitInlines(item.inlines, ["lines", bi, "items", ii, "inlines"]);
|
|
3524
3758
|
});
|
|
3759
|
+
} else if (block.type === AST_TYPES.CHART_BLOCK) {
|
|
3760
|
+
const chart = block;
|
|
3761
|
+
const base = ["lines", bi];
|
|
3762
|
+
chart.bars.forEach((bar, i) => {
|
|
3763
|
+
if (bar.answer !== void 0) record(bar.id, [...base, "bars", i, "id"]);
|
|
3764
|
+
visitInlines(bar.inlines, [...base, "bars", i, "inlines"]);
|
|
3765
|
+
});
|
|
3766
|
+
chart.pictos.forEach((picto, i) => {
|
|
3767
|
+
if (picto.answer !== void 0) record(picto.id, [...base, "pictos", i, "id"]);
|
|
3768
|
+
visitInlines(picto.inlines, [...base, "pictos", i, "inlines"]);
|
|
3769
|
+
});
|
|
3770
|
+
chart.groups.forEach((group, gi) => {
|
|
3771
|
+
group.bars.forEach((bar, i) => {
|
|
3772
|
+
if (bar.answer !== void 0) record(bar.id, [...base, "groups", gi, "bars", i, "id"]);
|
|
3773
|
+
});
|
|
3774
|
+
visitInlines(group.labelInlines, [...base, "groups", gi, "labelInlines"]);
|
|
3775
|
+
});
|
|
3776
|
+
chart.series.forEach((series, i) => {
|
|
3777
|
+
visitInlines(series.inlines, [...base, "series", i, "inlines"]);
|
|
3778
|
+
});
|
|
3779
|
+
if (chart.xAxis) visitInlines(chart.xAxis.inlines, [...base, "xAxis", "inlines"]);
|
|
3780
|
+
if (chart.yAxis) visitInlines(chart.yAxis.inlines, [...base, "yAxis", "inlines"]);
|
|
3525
3781
|
} else if (block.type === AST_TYPES.RADIO_OPTIONS) {
|
|
3526
3782
|
record(block.name, ["lines", bi, "name"]);
|
|
3527
3783
|
} else if (block.type === AST_TYPES.GEOMETRY_BLOCK && block.name !== void 0) {
|
|
@@ -4075,12 +4331,19 @@ class NaturalDSLVisitor extends BaseCstVisitor {
|
|
|
4075
4331
|
`${ctx.stringInput ? "STRING_INPUT" : "NUMERIC_INPUT"} "${name}": disabled must be true or false, got ${JSON.stringify(params.disabled)}`
|
|
4076
4332
|
);
|
|
4077
4333
|
}
|
|
4334
|
+
const width = params.width;
|
|
4335
|
+
const maxChars = ctx.stringInput ? params.length : params.digits;
|
|
4336
|
+
if (width !== void 0 && width < maxChars) {
|
|
4337
|
+
throw new Error(
|
|
4338
|
+
`${ctx.stringInput ? "STRING_INPUT" : "NUMERIC_INPUT"} "${name}": width (${width}) cannot be smaller than ${ctx.stringInput ? "length" : "digits"} (${maxChars})`
|
|
4339
|
+
);
|
|
4340
|
+
}
|
|
4078
4341
|
if (ctx.stringInput) {
|
|
4079
4342
|
const length = params.length;
|
|
4080
|
-
return createStringInput(name, length, answer, placeholder, disabled);
|
|
4343
|
+
return createStringInput(name, length, answer, placeholder, disabled, width);
|
|
4081
4344
|
}
|
|
4082
4345
|
const digits = params.digits;
|
|
4083
|
-
return createNumericInput(name, digits, answer, placeholder, disabled);
|
|
4346
|
+
return createNumericInput(name, digits, answer, placeholder, disabled, width);
|
|
4084
4347
|
}
|
|
4085
4348
|
inlineDragOption(ctx) {
|
|
4086
4349
|
const valueToken = ctx.StringLiteral[0];
|
|
@@ -4093,7 +4356,7 @@ class NaturalDSLVisitor extends BaseCstVisitor {
|
|
|
4093
4356
|
chartBlock(ctx) {
|
|
4094
4357
|
const params = this.visit(ctx.parameterList);
|
|
4095
4358
|
const chartType = params.type;
|
|
4096
|
-
if (!chartType) throw new Error(
|
|
4359
|
+
if (!chartType) throw new Error(`CHART: type is required (${CHART_TYPES.map((t) => `"${t}"`).join(" | ")})`);
|
|
4097
4360
|
if (!CHART_TYPES.includes(chartType)) {
|
|
4098
4361
|
throw new Error(`CHART: type must be one of ${CHART_TYPES.join(", ")} (got "${chartType}")`);
|
|
4099
4362
|
}
|
|
@@ -4112,7 +4375,26 @@ class NaturalDSLVisitor extends BaseCstVisitor {
|
|
|
4112
4375
|
const yAxis = ctx.chartYAxis ? this.visit(ctx.chartYAxis) : void 0;
|
|
4113
4376
|
const bars = ctx.chartBarItem ? ctx.chartBarItem.map((b) => this.visit(b)) : [];
|
|
4114
4377
|
const pictos = ctx.chartPictoItem ? ctx.chartPictoItem.map((p) => this.visit(p)) : [];
|
|
4115
|
-
|
|
4378
|
+
const series = ctx.chartSeriesItem ? ctx.chartSeriesItem.map((s) => this.visit(s)) : [];
|
|
4379
|
+
const groups = ctx.chartGroupItem ? ctx.chartGroupItem.map((g) => this.visit(g)) : [];
|
|
4380
|
+
if (params.legend !== void 0 && chartType !== "grouped-bar") {
|
|
4381
|
+
throw new Error(`CHART: "legend" is only supported for type="grouped-bar" (got "${chartType}")`);
|
|
4382
|
+
}
|
|
4383
|
+
const legend = params.legend === true || params.legend === "true";
|
|
4384
|
+
return createChartBlock(chartType, {
|
|
4385
|
+
bars,
|
|
4386
|
+
pictos,
|
|
4387
|
+
series,
|
|
4388
|
+
groups,
|
|
4389
|
+
legend,
|
|
4390
|
+
name,
|
|
4391
|
+
title,
|
|
4392
|
+
grid,
|
|
4393
|
+
scale,
|
|
4394
|
+
snap,
|
|
4395
|
+
xAxis,
|
|
4396
|
+
yAxis
|
|
4397
|
+
});
|
|
4116
4398
|
}
|
|
4117
4399
|
/** X_AXIS and Y_AXIS carry identical params — only the produced AST type differs.
|
|
4118
4400
|
* Chevrotain needs two separate grammar rules, but one visitor body serves both. */
|
|
@@ -4151,6 +4433,52 @@ class NaturalDSLVisitor extends BaseCstVisitor {
|
|
|
4151
4433
|
inlines
|
|
4152
4434
|
});
|
|
4153
4435
|
}
|
|
4436
|
+
chartSeriesItem(ctx) {
|
|
4437
|
+
const params = this.visit(ctx.parameterList);
|
|
4438
|
+
const inlines = ctx.inlineElement ? ctx.inlineElement.map((e) => this.visit(e)) : [];
|
|
4439
|
+
if (inlines.some((n) => !DisplayInlineNodeSchema.safeParse(n).success)) {
|
|
4440
|
+
throw new Error(
|
|
4441
|
+
`CHART: SERIES "${params.id}" label must be display-only — inputs, drag options, clocks and interactive fractions are not allowed in a legend label`
|
|
4442
|
+
);
|
|
4443
|
+
}
|
|
4444
|
+
if (!params.color) {
|
|
4445
|
+
throw new Error(`CHART: SERIES "${params.id}" requires "color"`);
|
|
4446
|
+
}
|
|
4447
|
+
return createChartSeries(params.id, {
|
|
4448
|
+
color: params.color,
|
|
4449
|
+
inlines
|
|
4450
|
+
});
|
|
4451
|
+
}
|
|
4452
|
+
chartGroupLabel(ctx) {
|
|
4453
|
+
return ctx.inlineElement ? ctx.inlineElement.map((e) => this.visit(e)) : [];
|
|
4454
|
+
}
|
|
4455
|
+
chartGroupItem(ctx) {
|
|
4456
|
+
const params = this.visit(ctx.parameterList);
|
|
4457
|
+
if (ctx.chartGroupLabel && ctx.chartGroupLabel.length > 1) {
|
|
4458
|
+
throw new Error(`CHART: GROUP "${params.id}" has more than one LABEL — only one is allowed`);
|
|
4459
|
+
}
|
|
4460
|
+
const labelInlines = ctx.chartGroupLabel ? this.visit(ctx.chartGroupLabel) : [];
|
|
4461
|
+
const bars = ctx.chartSeriesBarItem ? ctx.chartSeriesBarItem.map((b) => this.visit(b)) : [];
|
|
4462
|
+
return createChartGroup(params.id, { labelInlines, bars });
|
|
4463
|
+
}
|
|
4464
|
+
// `params.series` is the raw DSL attribute name (SERIES_BAR[series="..."])
|
|
4465
|
+
// — unrelated to the AST field's own `seriesId` name.
|
|
4466
|
+
chartSeriesBarItem(ctx) {
|
|
4467
|
+
const params = this.visit(ctx.parameterList);
|
|
4468
|
+
if (!params.series) {
|
|
4469
|
+
throw new Error(`CHART: SERIES_BAR "${params.id}" requires "series"`);
|
|
4470
|
+
}
|
|
4471
|
+
if (params.color !== void 0) {
|
|
4472
|
+
throw new Error(
|
|
4473
|
+
`CHART: SERIES_BAR "${params.id}" cannot set "color" — it comes from its SERIES`
|
|
4474
|
+
);
|
|
4475
|
+
}
|
|
4476
|
+
return createChartSeriesBar(params.id, {
|
|
4477
|
+
seriesId: params.series,
|
|
4478
|
+
value: params.value !== void 0 ? Number(params.value) : void 0,
|
|
4479
|
+
answer: params.answer !== void 0 ? Number(params.answer) : void 0
|
|
4480
|
+
});
|
|
4481
|
+
}
|
|
4154
4482
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
4155
4483
|
// Tools
|
|
4156
4484
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -4605,23 +4933,23 @@ const ChartAxisJSONSchema = z.object({
|
|
|
4605
4933
|
});
|
|
4606
4934
|
}
|
|
4607
4935
|
});
|
|
4608
|
-
const
|
|
4609
|
-
z.
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
id: z.string(),
|
|
4619
|
-
type: z.literal(JSON_TYPES.CHART_BAR),
|
|
4936
|
+
const ChartBarJSONBaseSchema = z.object({
|
|
4937
|
+
id: z.string(),
|
|
4938
|
+
type: z.literal(JSON_TYPES.CHART_BAR),
|
|
4939
|
+
value: z.number(),
|
|
4940
|
+
answer: z.number().optional(),
|
|
4941
|
+
color: z.string().optional(),
|
|
4942
|
+
label: z.array(LayoutItemSchema).optional()
|
|
4943
|
+
});
|
|
4944
|
+
const ChartBarJSONSchema = z.union([
|
|
4945
|
+
ChartBarJSONBaseSchema.extend({
|
|
4620
4946
|
interactive: z.literal(true),
|
|
4621
|
-
|
|
4622
|
-
widgetId: z.string()
|
|
4623
|
-
|
|
4624
|
-
|
|
4947
|
+
/** The widgets/answers key, equal to `id` when compiled. */
|
|
4948
|
+
widgetId: z.string()
|
|
4949
|
+
}),
|
|
4950
|
+
ChartBarJSONBaseSchema.extend({
|
|
4951
|
+
interactive: z.literal(false).optional(),
|
|
4952
|
+
widgetId: z.undefined().optional()
|
|
4625
4953
|
})
|
|
4626
4954
|
]);
|
|
4627
4955
|
const ChartPictoJSONSchema = z.object({
|
|
@@ -4635,21 +4963,55 @@ const ChartPictoJSONSchema = z.object({
|
|
|
4635
4963
|
),
|
|
4636
4964
|
label: z.array(LayoutItemSchema).optional()
|
|
4637
4965
|
});
|
|
4638
|
-
const
|
|
4966
|
+
const ChartSeriesJSONSchema = z.object({
|
|
4967
|
+
id: z.string(),
|
|
4968
|
+
color: z.string(),
|
|
4969
|
+
label: z.array(LayoutItemSchema).optional()
|
|
4970
|
+
});
|
|
4971
|
+
const ChartSeriesBarJSONSchema = z.object({
|
|
4972
|
+
id: z.string(),
|
|
4973
|
+
type: z.literal(JSON_TYPES.CHART_SERIES_BAR),
|
|
4974
|
+
value: z.number(),
|
|
4975
|
+
answer: z.number().optional(),
|
|
4976
|
+
color: z.string(),
|
|
4977
|
+
seriesId: z.string()
|
|
4978
|
+
});
|
|
4979
|
+
const ChartGroupJSONSchema = z.object({
|
|
4980
|
+
id: z.string(),
|
|
4981
|
+
label: z.array(LayoutItemSchema).optional(),
|
|
4982
|
+
bars: z.array(ChartSeriesBarJSONSchema)
|
|
4983
|
+
});
|
|
4984
|
+
const ChartItemBaseSchema = z.object({
|
|
4639
4985
|
id: z.string(),
|
|
4640
4986
|
type: z.literal(JSON_TYPES.CHART),
|
|
4641
4987
|
name: z.string().optional(),
|
|
4642
4988
|
title: z.string().optional(),
|
|
4643
|
-
chartType: z.enum(CHART_TYPES),
|
|
4644
4989
|
orientation: z.enum(["vertical", "horizontal"]).default("vertical"),
|
|
4645
4990
|
grid: z.boolean().default(false),
|
|
4646
4991
|
scale: z.number().positive().default(1),
|
|
4647
4992
|
snap: z.number().positive().optional(),
|
|
4648
4993
|
xAxis: ChartAxisJSONSchema.optional(),
|
|
4649
|
-
yAxis: ChartAxisJSONSchema.optional()
|
|
4650
|
-
|
|
4651
|
-
|
|
4652
|
-
|
|
4994
|
+
yAxis: ChartAxisJSONSchema.optional()
|
|
4995
|
+
});
|
|
4996
|
+
const ChartItemBarSchema = ChartItemBaseSchema.extend({
|
|
4997
|
+
chartType: z.literal("bar"),
|
|
4998
|
+
bars: z.array(ChartBarJSONSchema)
|
|
4999
|
+
});
|
|
5000
|
+
const ChartItemPictographSchema = ChartItemBaseSchema.extend({
|
|
5001
|
+
chartType: z.literal("pictograph"),
|
|
5002
|
+
pictos: z.array(ChartPictoJSONSchema)
|
|
5003
|
+
});
|
|
5004
|
+
const ChartItemGroupedBarSchema = ChartItemBaseSchema.extend({
|
|
5005
|
+
chartType: z.literal("grouped-bar"),
|
|
5006
|
+
series: z.array(ChartSeriesJSONSchema),
|
|
5007
|
+
groups: z.array(ChartGroupJSONSchema),
|
|
5008
|
+
legend: z.boolean().default(false)
|
|
5009
|
+
});
|
|
5010
|
+
const ChartItemSchema = z.discriminatedUnion("chartType", [
|
|
5011
|
+
ChartItemBarSchema,
|
|
5012
|
+
ChartItemPictographSchema,
|
|
5013
|
+
ChartItemGroupedBarSchema
|
|
5014
|
+
]).superRefine((item, ctx) => {
|
|
4653
5015
|
if (item.chartType === "bar" && item.bars.length === 0) {
|
|
4654
5016
|
ctx.addIssue({
|
|
4655
5017
|
code: z.ZodIssueCode.custom,
|
|
@@ -4657,18 +5019,63 @@ const ChartItemSchema = z.object({
|
|
|
4657
5019
|
path: ["bars"]
|
|
4658
5020
|
});
|
|
4659
5021
|
}
|
|
4660
|
-
if (item.chartType === "pictograph"
|
|
4661
|
-
|
|
4662
|
-
|
|
4663
|
-
|
|
4664
|
-
|
|
4665
|
-
|
|
5022
|
+
if (item.chartType === "pictograph") {
|
|
5023
|
+
if (item.pictos.length === 0) {
|
|
5024
|
+
ctx.addIssue({
|
|
5025
|
+
code: z.ZodIssueCode.custom,
|
|
5026
|
+
message: 'CHART[type="pictograph"]: at least one PICTO is required',
|
|
5027
|
+
path: ["pictos"]
|
|
5028
|
+
});
|
|
5029
|
+
}
|
|
5030
|
+
if (item.snap !== void 0) {
|
|
5031
|
+
ctx.addIssue({
|
|
5032
|
+
code: z.ZodIssueCode.custom,
|
|
5033
|
+
message: 'CHART[type="pictograph"]: snap is not supported for pictograph charts',
|
|
5034
|
+
path: ["snap"]
|
|
5035
|
+
});
|
|
5036
|
+
}
|
|
4666
5037
|
}
|
|
4667
|
-
if (item.chartType === "
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
5038
|
+
if (item.chartType === "grouped-bar") {
|
|
5039
|
+
if (item.series.length === 0) {
|
|
5040
|
+
ctx.addIssue({
|
|
5041
|
+
code: z.ZodIssueCode.custom,
|
|
5042
|
+
message: 'CHART[type="grouped-bar"]: at least one SERIES is required',
|
|
5043
|
+
path: ["series"]
|
|
5044
|
+
});
|
|
5045
|
+
}
|
|
5046
|
+
if (item.groups.length === 0) {
|
|
5047
|
+
ctx.addIssue({
|
|
5048
|
+
code: z.ZodIssueCode.custom,
|
|
5049
|
+
message: 'CHART[type="grouped-bar"]: at least one GROUP is required',
|
|
5050
|
+
path: ["groups"]
|
|
5051
|
+
});
|
|
5052
|
+
}
|
|
5053
|
+
validateUniqueIds(item.series, ctx, (i) => ["series", i]);
|
|
5054
|
+
validateUniqueIds(item.groups, ctx, (i) => ["groups", i]);
|
|
5055
|
+
const allBars = item.groups.flatMap((g, gi) => g.bars.map((bar, bi) => ({ id: bar.id, gi, bi })));
|
|
5056
|
+
validateUniqueIds(allBars, ctx, (i) => ["groups", allBars[i].gi, "bars", allBars[i].bi]);
|
|
5057
|
+
const seriesIds = new Set(item.series.map((s) => s.id));
|
|
5058
|
+
item.groups.forEach((group, gi) => {
|
|
5059
|
+
const seenInGroup = /* @__PURE__ */ new Set();
|
|
5060
|
+
group.bars.forEach((bar, bi) => {
|
|
5061
|
+
if (!seriesIds.has(bar.seriesId)) {
|
|
5062
|
+
ctx.addIssue({
|
|
5063
|
+
code: z.ZodIssueCode.custom,
|
|
5064
|
+
message: `CHART[type="grouped-bar"]: GROUP "${group.id}" bar "${bar.id}" references unknown seriesId "${bar.seriesId}" — no SERIES with that id is declared`,
|
|
5065
|
+
path: ["groups", gi, "bars", bi, "seriesId"]
|
|
5066
|
+
});
|
|
5067
|
+
return;
|
|
5068
|
+
}
|
|
5069
|
+
if (seenInGroup.has(bar.seriesId)) {
|
|
5070
|
+
ctx.addIssue({
|
|
5071
|
+
code: z.ZodIssueCode.custom,
|
|
5072
|
+
message: `CHART[type="grouped-bar"]: GROUP "${group.id}" has two bars for seriesId "${bar.seriesId}" — each series can appear at most once per group`,
|
|
5073
|
+
path: ["groups", gi, "bars", bi, "seriesId"]
|
|
5074
|
+
});
|
|
5075
|
+
return;
|
|
5076
|
+
}
|
|
5077
|
+
seenInGroup.add(bar.seriesId);
|
|
5078
|
+
});
|
|
4672
5079
|
});
|
|
4673
5080
|
}
|
|
4674
5081
|
});
|
|
@@ -4676,6 +5083,8 @@ const StringInputJSONSchema = z.object({
|
|
|
4676
5083
|
type: z.literal(JSON_TYPES.INLINE_INPUT),
|
|
4677
5084
|
inputType: z.literal(INPUT_TYPES.STRING),
|
|
4678
5085
|
length: z.number().int().positive(),
|
|
5086
|
+
/** Box width in characters; defaults to `length`. */
|
|
5087
|
+
width: InlineInputWidthSchema,
|
|
4679
5088
|
placeholder: z.string().max(32).optional(),
|
|
4680
5089
|
/** Gray, non-interactive, never graded (any answer for it is ignored). */
|
|
4681
5090
|
disabled: z.boolean().optional()
|
|
@@ -4684,6 +5093,8 @@ const NumericInputJSONSchema = z.object({
|
|
|
4684
5093
|
type: z.literal(JSON_TYPES.INLINE_INPUT),
|
|
4685
5094
|
inputType: z.literal(INPUT_TYPES.NUMERIC),
|
|
4686
5095
|
digits: z.number().int().positive(),
|
|
5096
|
+
/** Box width in characters; defaults to `digits`. */
|
|
5097
|
+
width: InlineInputWidthSchema,
|
|
4687
5098
|
placeholder: z.string().max(32).optional(),
|
|
4688
5099
|
/** Gray, non-interactive, never graded (any answer for it is ignored). */
|
|
4689
5100
|
disabled: z.boolean().optional()
|
|
@@ -5674,7 +6085,7 @@ lodash.exports;
|
|
|
5674
6085
|
var reIsNative = RegExp2(
|
|
5675
6086
|
"^" + funcToString.call(hasOwnProperty).replace(reRegExpChar, "\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, "$1.*?") + "$"
|
|
5676
6087
|
);
|
|
5677
|
-
var Buffer = moduleExports ? context.Buffer : undefined$1,
|
|
6088
|
+
var Buffer = moduleExports ? context.Buffer : undefined$1, Symbol2 = context.Symbol, Uint8Array = context.Uint8Array, allocUnsafe = Buffer ? Buffer.allocUnsafe : undefined$1, getPrototype = overArg(Object2.getPrototypeOf, Object2), objectCreate = Object2.create, propertyIsEnumerable = objectProto.propertyIsEnumerable, splice = arrayProto.splice, spreadableSymbol = Symbol2 ? Symbol2.isConcatSpreadable : undefined$1, symIterator = Symbol2 ? Symbol2.iterator : undefined$1, symToStringTag = Symbol2 ? Symbol2.toStringTag : undefined$1;
|
|
5678
6089
|
var defineProperty = function() {
|
|
5679
6090
|
try {
|
|
5680
6091
|
var func = getNative(Object2, "defineProperty");
|
|
@@ -5689,7 +6100,7 @@ lodash.exports;
|
|
|
5689
6100
|
var metaMap = WeakMap && new WeakMap();
|
|
5690
6101
|
var realNames = {};
|
|
5691
6102
|
var dataViewCtorString = toSource(DataView), mapCtorString = toSource(Map2), promiseCtorString = toSource(Promise2), setCtorString = toSource(Set2), weakMapCtorString = toSource(WeakMap);
|
|
5692
|
-
var symbolProto =
|
|
6103
|
+
var symbolProto = Symbol2 ? Symbol2.prototype : undefined$1, symbolValueOf = symbolProto ? symbolProto.valueOf : undefined$1, symbolToString = symbolProto ? symbolProto.toString : undefined$1;
|
|
5693
6104
|
function lodash2(value) {
|
|
5694
6105
|
if (isObjectLike(value) && !isArray(value) && !(value instanceof LazyWrapper)) {
|
|
5695
6106
|
if (value instanceof LodashWrapper) {
|
|
@@ -10588,6 +10999,7 @@ function toJSON(ast) {
|
|
|
10588
10999
|
type: JSON_TYPES.INLINE_INPUT,
|
|
10589
11000
|
inputType: INPUT_TYPES.STRING,
|
|
10590
11001
|
length: inline.length,
|
|
11002
|
+
...inline.width !== void 0 && { width: inline.width },
|
|
10591
11003
|
placeholder: inline.placeholder,
|
|
10592
11004
|
...inline.disabled && { disabled: true }
|
|
10593
11005
|
};
|
|
@@ -10596,6 +11008,7 @@ function toJSON(ast) {
|
|
|
10596
11008
|
type: JSON_TYPES.INLINE_INPUT,
|
|
10597
11009
|
inputType: INPUT_TYPES.NUMERIC,
|
|
10598
11010
|
digits: inline.digits,
|
|
11011
|
+
...inline.width !== void 0 && { width: inline.width },
|
|
10599
11012
|
placeholder: inline.placeholder,
|
|
10600
11013
|
...inline.disabled && { disabled: true }
|
|
10601
11014
|
};
|
|
@@ -10691,7 +11104,7 @@ function toJSON(ast) {
|
|
|
10691
11104
|
options: lodashExports.shuffle(tool.options)
|
|
10692
11105
|
};
|
|
10693
11106
|
} else if (tool.type === AST_TYPES.NUMERIC_KEYPAD) {
|
|
10694
|
-
widgets[
|
|
11107
|
+
widgets[NUMERIC_KEYPAD_WIDGET_ID] = {
|
|
10695
11108
|
type: JSON_TYPES.NUMERIC_KEYPAD,
|
|
10696
11109
|
includeOperators: tool.includeOperators,
|
|
10697
11110
|
includeDecimal: tool.includeDecimal,
|
|
@@ -10767,6 +11180,8 @@ function collectAllInlines(blocks) {
|
|
|
10767
11180
|
for (const picto of chart.pictos) result.push(...picto.inlines);
|
|
10768
11181
|
if (chart.xAxis) result.push(...chart.xAxis.inlines);
|
|
10769
11182
|
if (chart.yAxis) result.push(...chart.yAxis.inlines);
|
|
11183
|
+
for (const series of chart.series) result.push(...series.inlines);
|
|
11184
|
+
for (const group of chart.groups) result.push(...group.labelInlines);
|
|
10770
11185
|
}
|
|
10771
11186
|
}
|
|
10772
11187
|
return result;
|
|
@@ -10801,27 +11216,19 @@ function transformChart(block, idGenerator, inlineDragIds) {
|
|
|
10801
11216
|
const chartAnswers = {};
|
|
10802
11217
|
const bars = block.bars.map((bar) => {
|
|
10803
11218
|
const label = toLabel(bar.inlines);
|
|
10804
|
-
|
|
10805
|
-
chartWidgets[bar.id] = { type: JSON_TYPES.CHART_BAR, value: bar.value };
|
|
10806
|
-
chartAnswers[bar.id] = bar.answer;
|
|
10807
|
-
return {
|
|
10808
|
-
id: bar.id,
|
|
10809
|
-
type: JSON_TYPES.CHART_BAR,
|
|
10810
|
-
interactive: true,
|
|
10811
|
-
value: bar.value,
|
|
10812
|
-
widgetId: bar.id,
|
|
10813
|
-
...bar.color !== void 0 && { color: bar.color },
|
|
10814
|
-
...label !== void 0 && { label }
|
|
10815
|
-
};
|
|
10816
|
-
}
|
|
10817
|
-
return {
|
|
11219
|
+
const common = {
|
|
10818
11220
|
id: bar.id,
|
|
10819
11221
|
type: JSON_TYPES.CHART_BAR,
|
|
10820
|
-
interactive: false,
|
|
10821
11222
|
value: bar.value,
|
|
10822
11223
|
...bar.color !== void 0 && { color: bar.color },
|
|
10823
11224
|
...label !== void 0 && { label }
|
|
10824
11225
|
};
|
|
11226
|
+
if (bar.answer !== void 0) {
|
|
11227
|
+
chartWidgets[bar.id] = { type: JSON_TYPES.CHART_BAR, value: bar.value };
|
|
11228
|
+
chartAnswers[bar.id] = bar.answer;
|
|
11229
|
+
return { ...common, interactive: true, widgetId: bar.id, answer: bar.answer };
|
|
11230
|
+
}
|
|
11231
|
+
return { ...common, interactive: false };
|
|
10825
11232
|
});
|
|
10826
11233
|
const pictos = block.pictos.map((picto) => {
|
|
10827
11234
|
const label = toLabel(picto.inlines);
|
|
@@ -10838,21 +11245,62 @@ function transformChart(block, idGenerator, inlineDragIds) {
|
|
|
10838
11245
|
...label !== void 0 && { label }
|
|
10839
11246
|
};
|
|
10840
11247
|
});
|
|
10841
|
-
const
|
|
11248
|
+
const series = block.series.map((s) => {
|
|
11249
|
+
const label = toLabel(s.inlines);
|
|
11250
|
+
return {
|
|
11251
|
+
id: s.id,
|
|
11252
|
+
color: s.color,
|
|
11253
|
+
...label !== void 0 && { label }
|
|
11254
|
+
};
|
|
11255
|
+
});
|
|
11256
|
+
const groups = block.groups.map((group) => {
|
|
11257
|
+
const slots = alignGroupBarsToSeries(group, block.series);
|
|
11258
|
+
const groupBars = slots.map((bar, i) => {
|
|
11259
|
+
if (bar === void 0) return void 0;
|
|
11260
|
+
const series2 = block.series[i];
|
|
11261
|
+
if (bar.answer !== void 0) {
|
|
11262
|
+
chartWidgets[bar.id] = { type: JSON_TYPES.CHART_BAR, value: bar.value };
|
|
11263
|
+
chartAnswers[bar.id] = bar.answer;
|
|
11264
|
+
}
|
|
11265
|
+
return {
|
|
11266
|
+
id: bar.id,
|
|
11267
|
+
type: JSON_TYPES.CHART_SERIES_BAR,
|
|
11268
|
+
value: bar.value,
|
|
11269
|
+
...bar.answer !== void 0 && { answer: bar.answer },
|
|
11270
|
+
color: series2.color,
|
|
11271
|
+
seriesId: series2.id
|
|
11272
|
+
};
|
|
11273
|
+
}).filter((bar) => bar !== void 0);
|
|
11274
|
+
const label = toLabel(group.labelInlines);
|
|
11275
|
+
return {
|
|
11276
|
+
id: group.id,
|
|
11277
|
+
...label !== void 0 && { label },
|
|
11278
|
+
bars: groupBars
|
|
11279
|
+
};
|
|
11280
|
+
});
|
|
11281
|
+
const base = {
|
|
10842
11282
|
id: idGenerator.generate(JSON_TYPES.CHART),
|
|
10843
11283
|
type: JSON_TYPES.CHART,
|
|
10844
11284
|
...block.name !== void 0 && { name: block.name },
|
|
10845
11285
|
...block.title !== void 0 && { title: block.title },
|
|
10846
|
-
chartType: block.chartType,
|
|
10847
11286
|
orientation,
|
|
10848
11287
|
grid: block.grid,
|
|
10849
11288
|
scale: block.scale,
|
|
10850
11289
|
...block.snap !== void 0 && { snap: block.snap },
|
|
10851
11290
|
...xAxis !== void 0 && { xAxis },
|
|
10852
|
-
...yAxis !== void 0 && { yAxis }
|
|
10853
|
-
bars,
|
|
10854
|
-
pictos
|
|
11291
|
+
...yAxis !== void 0 && { yAxis }
|
|
10855
11292
|
};
|
|
11293
|
+
let item;
|
|
11294
|
+
if (block.chartType === "bar") {
|
|
11295
|
+
item = { ...base, chartType: "bar", bars };
|
|
11296
|
+
} else if (block.chartType === "pictograph") {
|
|
11297
|
+
item = { ...base, chartType: "pictograph", pictos };
|
|
11298
|
+
} else if (block.chartType === "grouped-bar") {
|
|
11299
|
+
item = { ...base, chartType: "grouped-bar", series, groups, legend: block.legend };
|
|
11300
|
+
} else {
|
|
11301
|
+
const unhandled = block.chartType;
|
|
11302
|
+
throw new Error(`CHART: unsupported chart type "${String(unhandled)}"`);
|
|
11303
|
+
}
|
|
10856
11304
|
return { item, chartWidgets, chartAnswers };
|
|
10857
11305
|
}
|
|
10858
11306
|
function transformNumberLine(numberLine, idGenerator, inlineDragIds) {
|
|
@@ -10919,7 +11367,7 @@ function transformNumberLine(numberLine, idGenerator, inlineDragIds) {
|
|
|
10919
11367
|
max: numberLine.max,
|
|
10920
11368
|
step: numberLine.step,
|
|
10921
11369
|
...numberLine.minorTicks !== void 0 && { minorTicks: numberLine.minorTicks },
|
|
10922
|
-
|
|
11370
|
+
style: numberLine.style,
|
|
10923
11371
|
...numberLine.labelVisibility !== void 0 && { labelVisibility: numberLine.labelVisibility },
|
|
10924
11372
|
...counterItem !== void 0 && { counter: counterItem },
|
|
10925
11373
|
...highlights !== void 0 && { highlights },
|
|
@@ -15576,22 +16024,22 @@ function createDefaultDropAnimation(options) {
|
|
|
15576
16024
|
easing,
|
|
15577
16025
|
fill: "forwards"
|
|
15578
16026
|
});
|
|
15579
|
-
return new Promise((
|
|
16027
|
+
return new Promise((resolve2) => {
|
|
15580
16028
|
animation.onfinish = () => {
|
|
15581
16029
|
cleanup == null ? void 0 : cleanup();
|
|
15582
|
-
|
|
16030
|
+
resolve2();
|
|
15583
16031
|
};
|
|
15584
16032
|
});
|
|
15585
16033
|
};
|
|
15586
16034
|
}
|
|
15587
|
-
let key = 0;
|
|
16035
|
+
let key$1 = 0;
|
|
15588
16036
|
function useKey(id) {
|
|
15589
16037
|
return useMemo(() => {
|
|
15590
16038
|
if (id == null) {
|
|
15591
16039
|
return;
|
|
15592
16040
|
}
|
|
15593
|
-
key++;
|
|
15594
|
-
return key;
|
|
16041
|
+
key$1++;
|
|
16042
|
+
return key$1;
|
|
15595
16043
|
}, [id]);
|
|
15596
16044
|
}
|
|
15597
16045
|
const DragOverlay = /* @__PURE__ */ React.memo((_ref) => {
|
|
@@ -15673,6 +16121,7 @@ const DEFAULT_SOUNDS = {
|
|
|
15673
16121
|
"drag-start": dragStart,
|
|
15674
16122
|
"drag-drop": dragDrop,
|
|
15675
16123
|
"drag-cancel": dragDrop,
|
|
16124
|
+
"drag-hint": buttonClick,
|
|
15676
16125
|
"radio-select": buttonClick,
|
|
15677
16126
|
"pair-select": buttonClick,
|
|
15678
16127
|
"pair-link": buttonClick,
|
|
@@ -15776,14 +16225,24 @@ function useExerciseDnd() {
|
|
|
15776
16225
|
}
|
|
15777
16226
|
return ctx;
|
|
15778
16227
|
}
|
|
16228
|
+
const NO_EXERCISE = Symbol("no-exercise");
|
|
15779
16229
|
const ExerciseDndProvider = ({
|
|
15780
16230
|
children,
|
|
15781
16231
|
droppedSources,
|
|
15782
|
-
onDragEnd
|
|
16232
|
+
onDragEnd,
|
|
16233
|
+
exerciseKey
|
|
15783
16234
|
}) => {
|
|
15784
16235
|
const [activeItem, setActiveItem] = useState(null);
|
|
16236
|
+
const [draggedExerciseKey, setDraggedExerciseKey] = useState(NO_EXERCISE);
|
|
16237
|
+
const hasDraggedOption = draggedExerciseKey === exerciseKey;
|
|
15785
16238
|
const [dropHandlers] = useState(() => /* @__PURE__ */ new Map());
|
|
15786
16239
|
const playSound = useSound();
|
|
16240
|
+
const hintSoundExerciseRef = useRef(NO_EXERCISE);
|
|
16241
|
+
const playHintSound = useCallback(() => {
|
|
16242
|
+
if (hintSoundExerciseRef.current === exerciseKey) return;
|
|
16243
|
+
hintSoundExerciseRef.current = exerciseKey;
|
|
16244
|
+
playSound("drag-hint");
|
|
16245
|
+
}, [playSound, exerciseKey]);
|
|
15787
16246
|
const usedOptionIds = useMemo(() => {
|
|
15788
16247
|
return new Set(droppedSources.values());
|
|
15789
16248
|
}, [droppedSources]);
|
|
@@ -15807,6 +16266,7 @@ const ExerciseDndProvider = ({
|
|
|
15807
16266
|
const raw = active.data.current;
|
|
15808
16267
|
const item = raw?.type === "placed-chip" ? { type: "placed-chip", widgetId: raw.widgetId, label: raw.label, sourceOptionId: raw.sourceOptionId } : raw;
|
|
15809
16268
|
setActiveItem(item);
|
|
16269
|
+
if (item.type === "drag-option") setDraggedExerciseKey(exerciseKey);
|
|
15810
16270
|
playSound("drag-start");
|
|
15811
16271
|
};
|
|
15812
16272
|
const handleDragEnd = (event) => {
|
|
@@ -15832,6 +16292,9 @@ const ExerciseDndProvider = ({
|
|
|
15832
16292
|
value: {
|
|
15833
16293
|
activeItem,
|
|
15834
16294
|
isDragging: activeItem !== null,
|
|
16295
|
+
exerciseKey,
|
|
16296
|
+
hasDraggedOption,
|
|
16297
|
+
playHintSound,
|
|
15835
16298
|
usedOptionIds,
|
|
15836
16299
|
droppedSources,
|
|
15837
16300
|
registerDropHandler,
|
|
@@ -16612,9 +17075,40 @@ function isUndoDrop(event, dragId) {
|
|
|
16612
17075
|
const LockContext = createContext(false);
|
|
16613
17076
|
const ExerciseLockProvider = ({ locked, children }) => /* @__PURE__ */ jsx(LockContext.Provider, { value: locked, children });
|
|
16614
17077
|
const useExerciseLocked = () => useContext(LockContext);
|
|
16615
|
-
const
|
|
16616
|
-
|
|
16617
|
-
|
|
17078
|
+
const OVERLAY_STYLE = {
|
|
17079
|
+
position: "absolute",
|
|
17080
|
+
top: 0,
|
|
17081
|
+
left: 0,
|
|
17082
|
+
width: "100%",
|
|
17083
|
+
height: "100%",
|
|
17084
|
+
minWidth: 0,
|
|
17085
|
+
maxWidth: "none",
|
|
17086
|
+
margin: 0,
|
|
17087
|
+
boxSizing: "border-box"
|
|
17088
|
+
};
|
|
17089
|
+
const SlotSizer = ({
|
|
17090
|
+
style,
|
|
17091
|
+
...props
|
|
17092
|
+
}) => /* @__PURE__ */ jsx(
|
|
17093
|
+
"input",
|
|
17094
|
+
{
|
|
17095
|
+
...props,
|
|
17096
|
+
type: "text",
|
|
17097
|
+
value: "",
|
|
17098
|
+
readOnly: true,
|
|
17099
|
+
tabIndex: -1,
|
|
17100
|
+
"aria-hidden": "true",
|
|
17101
|
+
style: { ...style, visibility: "hidden" }
|
|
17102
|
+
}
|
|
17103
|
+
);
|
|
17104
|
+
const PlacedInputChip = ({
|
|
17105
|
+
widgetId,
|
|
17106
|
+
value,
|
|
17107
|
+
className = "",
|
|
17108
|
+
onRemove
|
|
17109
|
+
}) => {
|
|
17110
|
+
const { droppedSources } = useExerciseDnd();
|
|
17111
|
+
const locked = useExerciseLocked();
|
|
16618
17112
|
const sourceOptionId = droppedSources.get(widgetId);
|
|
16619
17113
|
const dragId = `${widgetId}__chip`;
|
|
16620
17114
|
const { attributes, listeners, setNodeRef, isDragging } = useDraggable({
|
|
@@ -16636,13 +17130,41 @@ const PlacedInputChip = ({ widgetId, value, className = "", onRemove }) => {
|
|
|
16636
17130
|
if (!locked && e.detail !== 0) onRemove();
|
|
16637
17131
|
},
|
|
16638
17132
|
title: locked ? void 0 : "Drag to move, or click to remove",
|
|
16639
|
-
style: {
|
|
17133
|
+
style: {
|
|
17134
|
+
...OVERLAY_STYLE,
|
|
17135
|
+
touchAction: "none",
|
|
17136
|
+
cursor: locked ? "default" : "grab"
|
|
17137
|
+
},
|
|
16640
17138
|
...listeners,
|
|
16641
17139
|
...attributes,
|
|
16642
17140
|
children: value
|
|
16643
17141
|
}
|
|
16644
17142
|
);
|
|
16645
17143
|
};
|
|
17144
|
+
function getMaxInputLength(exercise, inputKey) {
|
|
17145
|
+
const widget = exercise.widgets[inputKey];
|
|
17146
|
+
if (widget?.type !== JSON_TYPES.INLINE_INPUT) return void 0;
|
|
17147
|
+
return widget.inputType === INPUT_TYPES.NUMERIC ? widget.digits : widget.length;
|
|
17148
|
+
}
|
|
17149
|
+
function getInputElementId(key2) {
|
|
17150
|
+
return `${key2}-input`;
|
|
17151
|
+
}
|
|
17152
|
+
function getTextInputs(container) {
|
|
17153
|
+
return Array.from(
|
|
17154
|
+
container.querySelectorAll(
|
|
17155
|
+
'input[type="text"], input[type="number"], textarea, select'
|
|
17156
|
+
)
|
|
17157
|
+
);
|
|
17158
|
+
}
|
|
17159
|
+
function canTakeInput(el) {
|
|
17160
|
+
return !el.readOnly && !el.disabled;
|
|
17161
|
+
}
|
|
17162
|
+
function findNextInput(container, currentKey) {
|
|
17163
|
+
const inputs = getTextInputs(container);
|
|
17164
|
+
const currentIndex = inputs.findIndex((el) => el.id === getInputElementId(currentKey));
|
|
17165
|
+
if (currentIndex === -1) return null;
|
|
17166
|
+
return inputs.slice(currentIndex + 1).find(canTakeInput) ?? null;
|
|
17167
|
+
}
|
|
16646
17168
|
const InlineInput = ({
|
|
16647
17169
|
widgetId,
|
|
16648
17170
|
value,
|
|
@@ -16651,6 +17173,7 @@ const InlineInput = ({
|
|
|
16651
17173
|
inputType,
|
|
16652
17174
|
length,
|
|
16653
17175
|
digits,
|
|
17176
|
+
width,
|
|
16654
17177
|
placeholder,
|
|
16655
17178
|
disabled = false,
|
|
16656
17179
|
onChange,
|
|
@@ -16664,8 +17187,8 @@ const InlineInput = ({
|
|
|
16664
17187
|
const handleDrop = useCallback((item) => {
|
|
16665
17188
|
return applyDropToInput(widgetId, item, onChange);
|
|
16666
17189
|
}, [onChange, widgetId]);
|
|
16667
|
-
const expectedLength = inputType ===
|
|
16668
|
-
const dynamicWidth = `${(expectedLength
|
|
17190
|
+
const expectedLength = inputType === INPUT_TYPES.NUMERIC ? digits : length;
|
|
17191
|
+
const dynamicWidth = `${(width ?? expectedLength ?? 5) * 0.8 + 1}em`;
|
|
16669
17192
|
const statusClass = validationStatus ? ` widget--${validationStatus}` : "";
|
|
16670
17193
|
const disabledClass = disabled ? " inline-input--disabled" : "";
|
|
16671
17194
|
return /* @__PURE__ */ jsx(
|
|
@@ -16678,21 +17201,31 @@ const InlineInput = ({
|
|
|
16678
17201
|
className: "inline-widget inline-input-droppable",
|
|
16679
17202
|
children: isDropped && value !== "" ? (
|
|
16680
17203
|
// Show as chip only when value came from drag-drop
|
|
16681
|
-
/* @__PURE__ */
|
|
16682
|
-
|
|
16683
|
-
|
|
16684
|
-
|
|
16685
|
-
|
|
16686
|
-
|
|
16687
|
-
|
|
16688
|
-
|
|
16689
|
-
|
|
17204
|
+
/* @__PURE__ */ jsxs(Fragment, { children: [
|
|
17205
|
+
/* @__PURE__ */ jsx(
|
|
17206
|
+
SlotSizer,
|
|
17207
|
+
{
|
|
17208
|
+
className: `inline-input${disabledClass}`,
|
|
17209
|
+
style: { width: dynamicWidth },
|
|
17210
|
+
placeholder
|
|
17211
|
+
}
|
|
17212
|
+
),
|
|
17213
|
+
/* @__PURE__ */ jsx(
|
|
17214
|
+
PlacedInputChip,
|
|
17215
|
+
{
|
|
17216
|
+
widgetId,
|
|
17217
|
+
value,
|
|
17218
|
+
className: statusClass.trim(),
|
|
17219
|
+
onRemove: () => handleChange("", false)
|
|
17220
|
+
}
|
|
17221
|
+
)
|
|
17222
|
+
] })
|
|
16690
17223
|
) : (
|
|
16691
17224
|
// Regular input for typing
|
|
16692
17225
|
/* @__PURE__ */ jsx(
|
|
16693
17226
|
"input",
|
|
16694
17227
|
{
|
|
16695
|
-
id: widgetId
|
|
17228
|
+
id: getInputElementId(widgetId),
|
|
16696
17229
|
type: "text",
|
|
16697
17230
|
className: `inline-input${statusClass}${disabledClass}`,
|
|
16698
17231
|
value,
|
|
@@ -16712,15 +17245,20 @@ const InlineInput = ({
|
|
|
16712
17245
|
}
|
|
16713
17246
|
);
|
|
16714
17247
|
};
|
|
16715
|
-
const SingleDragOption = ({
|
|
17248
|
+
const SingleDragOption = (props) => {
|
|
17249
|
+
const { exerciseKey } = useExerciseDnd();
|
|
17250
|
+
return /* @__PURE__ */ jsx(DragOptionChip, { ...props }, exerciseKey ?? "");
|
|
17251
|
+
};
|
|
17252
|
+
const DragOptionChip = ({
|
|
16716
17253
|
optionId,
|
|
16717
17254
|
value,
|
|
16718
17255
|
onOptionClick,
|
|
16719
17256
|
className = ""
|
|
16720
17257
|
}) => {
|
|
16721
|
-
const { usedOptionIds } = useExerciseDnd();
|
|
17258
|
+
const { usedOptionIds, hasDraggedOption, playHintSound } = useExerciseDnd();
|
|
16722
17259
|
const locked = useExerciseLocked();
|
|
16723
17260
|
const isUsed = usedOptionIds.has(optionId);
|
|
17261
|
+
const { jiggling: hinting, onAnimationEnd: onHintEnd } = useJiggle(hasDraggedOption);
|
|
16724
17262
|
return /* @__PURE__ */ jsx("div", { className: `drag-option-slot ${className}`.trim(), children: isUsed ? /* @__PURE__ */ jsx("div", { className: "drag-option-empty", "aria-hidden": "true" }) : /* @__PURE__ */ jsx(
|
|
16725
17263
|
Draggable,
|
|
16726
17264
|
{
|
|
@@ -16732,7 +17270,11 @@ const SingleDragOption = ({
|
|
|
16732
17270
|
children: /* @__PURE__ */ jsx(
|
|
16733
17271
|
"button",
|
|
16734
17272
|
{
|
|
16735
|
-
className: "drag-option",
|
|
17273
|
+
className: hinting && !locked ? "drag-option drag-option--hint" : "drag-option",
|
|
17274
|
+
onAnimationStart: () => {
|
|
17275
|
+
if (hinting) playHintSound();
|
|
17276
|
+
},
|
|
17277
|
+
onAnimationEnd: onHintEnd,
|
|
16736
17278
|
onClick: () => {
|
|
16737
17279
|
if (!locked) onOptionClick?.(value);
|
|
16738
17280
|
},
|
|
@@ -16861,23 +17403,23 @@ const NumericKeypad = ({
|
|
|
16861
17403
|
playSound("keypad-press");
|
|
16862
17404
|
onChange(value.slice(0, -1));
|
|
16863
17405
|
};
|
|
16864
|
-
return /* @__PURE__ */ jsxs("div", { className: `numeric-keypad ${includeOperators ? "numeric-keypad-with-operators" : ""}`, children: [
|
|
16865
|
-
/* @__PURE__ */ jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-digit", onClick: () => handleDigit("1"), children: "1" }),
|
|
16866
|
-
/* @__PURE__ */ jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-digit", onClick: () => handleDigit("2"), children: "2" }),
|
|
16867
|
-
/* @__PURE__ */ jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-digit", onClick: () => handleDigit("3"), children: "3" }),
|
|
16868
|
-
includeOperators && /* @__PURE__ */ jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-operator", onClick: () => handleDigit("+"), children: "+" }),
|
|
16869
|
-
/* @__PURE__ */ jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-digit", onClick: () => handleDigit("4"), children: "4" }),
|
|
16870
|
-
/* @__PURE__ */ jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-digit", onClick: () => handleDigit("5"), children: "5" }),
|
|
16871
|
-
/* @__PURE__ */ jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-digit", onClick: () => handleDigit("6"), children: "6" }),
|
|
16872
|
-
includeOperators && /* @__PURE__ */ jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-operator", onClick: () => handleDigit("-"), children: "-" }),
|
|
16873
|
-
/* @__PURE__ */ jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-digit", onClick: () => handleDigit("7"), children: "7" }),
|
|
16874
|
-
/* @__PURE__ */ jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-digit", onClick: () => handleDigit("8"), children: "8" }),
|
|
16875
|
-
/* @__PURE__ */ jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-digit", onClick: () => handleDigit("9"), children: "9" }),
|
|
16876
|
-
includeOperators && /* @__PURE__ */ jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-operator", onClick: () => handleDigit("×"), children: "×" }),
|
|
16877
|
-
includeDecimal ? /* @__PURE__ */ jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-decimal", onClick: () => handleDigit(","), children: "," }) : /* @__PURE__ */ jsx("div", {}),
|
|
16878
|
-
/* @__PURE__ */ jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-digit", onClick: () => handleDigit("0"), children: "0" }),
|
|
16879
|
-
/* @__PURE__ */ jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-backspace", onClick: handleBackspace, children: "⌫" }),
|
|
16880
|
-
includeOperators && /* @__PURE__ */ jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-operator", onClick: () => handleDigit("÷"), children: "÷" })
|
|
17406
|
+
return /* @__PURE__ */ jsxs("div", { className: `numeric-keypad ${includeOperators ? "numeric-keypad-with-operators" : ""}`, "data-tutorial": "keypad", children: [
|
|
17407
|
+
/* @__PURE__ */ jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-digit", "data-key": "1", onClick: () => handleDigit("1"), children: "1" }),
|
|
17408
|
+
/* @__PURE__ */ jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-digit", "data-key": "2", onClick: () => handleDigit("2"), children: "2" }),
|
|
17409
|
+
/* @__PURE__ */ jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-digit", "data-key": "3", onClick: () => handleDigit("3"), children: "3" }),
|
|
17410
|
+
includeOperators && /* @__PURE__ */ jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-operator", "data-key": "+", onClick: () => handleDigit("+"), children: "+" }),
|
|
17411
|
+
/* @__PURE__ */ jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-digit", "data-key": "4", onClick: () => handleDigit("4"), children: "4" }),
|
|
17412
|
+
/* @__PURE__ */ jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-digit", "data-key": "5", onClick: () => handleDigit("5"), children: "5" }),
|
|
17413
|
+
/* @__PURE__ */ jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-digit", "data-key": "6", onClick: () => handleDigit("6"), children: "6" }),
|
|
17414
|
+
includeOperators && /* @__PURE__ */ jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-operator", "data-key": "-", onClick: () => handleDigit("-"), children: "-" }),
|
|
17415
|
+
/* @__PURE__ */ jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-digit", "data-key": "7", onClick: () => handleDigit("7"), children: "7" }),
|
|
17416
|
+
/* @__PURE__ */ jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-digit", "data-key": "8", onClick: () => handleDigit("8"), children: "8" }),
|
|
17417
|
+
/* @__PURE__ */ jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-digit", "data-key": "9", onClick: () => handleDigit("9"), children: "9" }),
|
|
17418
|
+
includeOperators && /* @__PURE__ */ jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-operator", "data-key": "×", onClick: () => handleDigit("×"), children: "×" }),
|
|
17419
|
+
includeDecimal ? /* @__PURE__ */ jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-decimal", "data-key": ",", onClick: () => handleDigit(","), children: "," }) : /* @__PURE__ */ jsx("div", {}),
|
|
17420
|
+
/* @__PURE__ */ jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-digit", "data-key": "0", onClick: () => handleDigit("0"), children: "0" }),
|
|
17421
|
+
/* @__PURE__ */ jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-backspace", "data-key": "backspace", onClick: handleBackspace, children: "⌫" }),
|
|
17422
|
+
includeOperators && /* @__PURE__ */ jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-operator", "data-key": "÷", onClick: () => handleDigit("÷"), children: "÷" })
|
|
16881
17423
|
] });
|
|
16882
17424
|
};
|
|
16883
17425
|
const FractionInput = ({
|
|
@@ -16917,18 +17459,28 @@ const FractionInput = ({
|
|
|
16917
17459
|
onDrop: handleDrop(subKey),
|
|
16918
17460
|
disabled: locked,
|
|
16919
17461
|
className: "fraction-slot-droppable",
|
|
16920
|
-
children: isDroppedHere && currentValue !== "" ? /* @__PURE__ */
|
|
16921
|
-
|
|
16922
|
-
|
|
16923
|
-
|
|
16924
|
-
|
|
16925
|
-
|
|
16926
|
-
|
|
16927
|
-
|
|
16928
|
-
|
|
17462
|
+
children: isDroppedHere && currentValue !== "" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
17463
|
+
/* @__PURE__ */ jsx(
|
|
17464
|
+
SlotSizer,
|
|
17465
|
+
{
|
|
17466
|
+
className: "inline-input fraction-input",
|
|
17467
|
+
size: placeholder ? placeholder.length : void 0,
|
|
17468
|
+
placeholder
|
|
17469
|
+
}
|
|
17470
|
+
),
|
|
17471
|
+
/* @__PURE__ */ jsx(
|
|
17472
|
+
PlacedInputChip,
|
|
17473
|
+
{
|
|
17474
|
+
widgetId: subKey,
|
|
17475
|
+
value: currentValue,
|
|
17476
|
+
className: `fraction-chip${slotClass}`,
|
|
17477
|
+
onRemove: () => onChange(subKey, "", false)
|
|
17478
|
+
}
|
|
17479
|
+
)
|
|
17480
|
+
] }) : /* @__PURE__ */ jsx(
|
|
16929
17481
|
"input",
|
|
16930
17482
|
{
|
|
16931
|
-
id:
|
|
17483
|
+
id: getInputElementId(subKey),
|
|
16932
17484
|
type: "text",
|
|
16933
17485
|
className: `inline-input fraction-input${slotClass}`,
|
|
16934
17486
|
value: currentValue,
|
|
@@ -17517,12 +18069,17 @@ const GridSymmetry = ({
|
|
|
17517
18069
|
}
|
|
17518
18070
|
) });
|
|
17519
18071
|
};
|
|
18072
|
+
function interactiveBarKey(bar) {
|
|
18073
|
+
if (bar.answer === void 0 && bar.interactive !== true) return void 0;
|
|
18074
|
+
return bar.widgetId ?? bar.id;
|
|
18075
|
+
}
|
|
17520
18076
|
function getChartClassName(item) {
|
|
17521
18077
|
const isHorizontal = item.orientation === "horizontal";
|
|
17522
18078
|
return [
|
|
17523
18079
|
"chart",
|
|
17524
18080
|
isHorizontal ? "chart--horizontal" : "",
|
|
17525
18081
|
item.chartType === "pictograph" ? "chart--pictograph" : "",
|
|
18082
|
+
item.chartType === "grouped-bar" ? "chart--grouped-bar" : "",
|
|
17526
18083
|
item.xAxis?.visible === false ? "chart--no-xaxis" : "",
|
|
17527
18084
|
item.yAxis?.visible === false ? "chart--no-yaxis" : ""
|
|
17528
18085
|
].filter(Boolean).join(" ");
|
|
@@ -17533,7 +18090,8 @@ function resolveItemValue(id, defaultValue2, values) {
|
|
|
17533
18090
|
return isNaN(parsed) ? defaultValue2 : parsed;
|
|
17534
18091
|
}
|
|
17535
18092
|
function resolveValue(bar, values) {
|
|
17536
|
-
|
|
18093
|
+
const widgetKey = interactiveBarKey(bar);
|
|
18094
|
+
return widgetKey !== void 0 ? resolveItemValue(widgetKey, bar.value, values) : bar.value;
|
|
17537
18095
|
}
|
|
17538
18096
|
function clamp(v, min, max) {
|
|
17539
18097
|
return Math.min(max, Math.max(min, v));
|
|
@@ -17592,6 +18150,8 @@ const CHART_METRICS = {
|
|
|
17592
18150
|
gapWidth: 1,
|
|
17593
18151
|
axisWidth: 2.35,
|
|
17594
18152
|
barWidth: 3.35,
|
|
18153
|
+
// Fixed narrow width so several series fit in one cluster; not derived from count.
|
|
18154
|
+
groupedBarWidth: 1.8,
|
|
17595
18155
|
fontSize: 0.9,
|
|
17596
18156
|
labelFontSize: 0.98,
|
|
17597
18157
|
handleSize: 1.15,
|
|
@@ -17644,6 +18204,41 @@ function computePictographChartStyle(scale, columnCount, totalSlots, orientation
|
|
|
17644
18204
|
"--chart-height": slotSpan
|
|
17645
18205
|
};
|
|
17646
18206
|
}
|
|
18207
|
+
function computeGroupedChartStyle(scale, groupCount, seriesCount, orientation = "vertical", isInteractive = false) {
|
|
18208
|
+
const visualScale = clamp(scale, CHART_METRICS.scaleMin, CHART_METRICS.scaleMax);
|
|
18209
|
+
const barWidth = (isInteractive ? CHART_METRICS.barWidth : CHART_METRICS.groupedBarWidth) * visualScale;
|
|
18210
|
+
const barGap = CHART_METRICS.gapWidth * visualScale * 0.5;
|
|
18211
|
+
const groupGap = CHART_METRICS.gapWidth * visualScale * 2;
|
|
18212
|
+
const seriesPerGroup = Math.max(1, seriesCount);
|
|
18213
|
+
const groupWidth = seriesPerGroup * barWidth + Math.max(0, seriesPerGroup - 1) * barGap;
|
|
18214
|
+
const trackLength = groupCount * groupWidth + Math.max(0, groupCount - 1) * groupGap + groupGap * 2;
|
|
18215
|
+
const cssVars = {
|
|
18216
|
+
"--chart-axis-width": `${CHART_METRICS.axisWidth * visualScale}rem`,
|
|
18217
|
+
"--chart-bar-width": `${barWidth}rem`,
|
|
18218
|
+
// A bar slot inside a group is just the bar; reuse the plain bar-chart var.
|
|
18219
|
+
"--chart-bar-slot-width": `${barWidth}rem`,
|
|
18220
|
+
"--chart-bar-gap": `${barGap}rem`,
|
|
18221
|
+
"--chart-group-gap": `${groupGap}rem`,
|
|
18222
|
+
"--chart-group-width": `${groupWidth}rem`,
|
|
18223
|
+
"--chart-font-size": `${CHART_METRICS.fontSize * visualScale}rem`,
|
|
18224
|
+
"--chart-label-font-size": `${CHART_METRICS.labelFontSize * visualScale}rem`,
|
|
18225
|
+
"--chart-handle-size": `${CHART_METRICS.handleSize * visualScale}rem`
|
|
18226
|
+
};
|
|
18227
|
+
if (orientation === "horizontal") {
|
|
18228
|
+
const valueWidth = CHART_METRICS.chartHeight * visualScale;
|
|
18229
|
+
return {
|
|
18230
|
+
...cssVars,
|
|
18231
|
+
"--chart-height": `${trackLength}rem`,
|
|
18232
|
+
"--chart-h-value-width": `${valueWidth}rem`,
|
|
18233
|
+
"--chart-content-width": `${valueWidth}rem`
|
|
18234
|
+
};
|
|
18235
|
+
}
|
|
18236
|
+
return {
|
|
18237
|
+
...cssVars,
|
|
18238
|
+
"--chart-height": `${CHART_METRICS.chartHeight * visualScale}rem`,
|
|
18239
|
+
"--chart-content-width": `${trackLength}rem`
|
|
18240
|
+
};
|
|
18241
|
+
}
|
|
17647
18242
|
function computeHorizontalChartStyle(scale, barCount) {
|
|
17648
18243
|
const { trackLength, visualScale, cssVars } = computeBaseChartVars(scale, barCount);
|
|
17649
18244
|
const valueWidth = CHART_METRICS.chartHeight * visualScale;
|
|
@@ -17695,6 +18290,7 @@ const DraggableBar = ({
|
|
|
17695
18290
|
const axisMax = axis.max ?? CHART_AXIS_DEFAULTS.max;
|
|
17696
18291
|
const axisStep = axis.step ?? CHART_AXIS_DEFAULTS.step;
|
|
17697
18292
|
const snapStep = snap ?? axisStep;
|
|
18293
|
+
const widgetKey = bar.widgetId ?? bar.id;
|
|
17698
18294
|
const fillFromPointer = useCallback(
|
|
17699
18295
|
(clientY, clientX) => {
|
|
17700
18296
|
const track = trackRef.current;
|
|
@@ -17712,10 +18308,10 @@ const DraggableBar = ({
|
|
|
17712
18308
|
if (snapped !== lastSnap.current) {
|
|
17713
18309
|
lastSnap.current = snapped;
|
|
17714
18310
|
playSound("bar-step");
|
|
17715
|
-
onChange(
|
|
18311
|
+
onChange(widgetKey, String(snapped));
|
|
17716
18312
|
}
|
|
17717
18313
|
},
|
|
17718
|
-
[axisMin, axisMax, snapStep,
|
|
18314
|
+
[axisMin, axisMax, snapStep, widgetKey, onChange, orientation, playSound]
|
|
17719
18315
|
);
|
|
17720
18316
|
const onPointerDown = useCallback(
|
|
17721
18317
|
(e) => {
|
|
@@ -17748,8 +18344,8 @@ const DraggableBar = ({
|
|
|
17748
18344
|
if (!dragging.current) return;
|
|
17749
18345
|
dragging.current = false;
|
|
17750
18346
|
setIsDragging(false);
|
|
17751
|
-
onChange(
|
|
17752
|
-
}, [
|
|
18347
|
+
onChange(widgetKey, String(preDragValue.current));
|
|
18348
|
+
}, [widgetKey, onChange]);
|
|
17753
18349
|
const onKeyDown = useCallback(
|
|
17754
18350
|
(e) => {
|
|
17755
18351
|
if (exerciseLocked) return;
|
|
@@ -17758,22 +18354,22 @@ const DraggableBar = ({
|
|
|
17758
18354
|
if (isInc) {
|
|
17759
18355
|
e.preventDefault();
|
|
17760
18356
|
playSound("bar-step");
|
|
17761
|
-
onChange(
|
|
18357
|
+
onChange(widgetKey, String(nextStepValue(currentValue, axis, 1, snap)));
|
|
17762
18358
|
} else if (isDec) {
|
|
17763
18359
|
e.preventDefault();
|
|
17764
18360
|
playSound("bar-step");
|
|
17765
|
-
onChange(
|
|
18361
|
+
onChange(widgetKey, String(nextStepValue(currentValue, axis, -1, snap)));
|
|
17766
18362
|
} else if (e.key === "Home") {
|
|
17767
18363
|
e.preventDefault();
|
|
17768
18364
|
playSound("bar-step");
|
|
17769
|
-
onChange(
|
|
18365
|
+
onChange(widgetKey, String(axisMin));
|
|
17770
18366
|
} else if (e.key === "End") {
|
|
17771
18367
|
e.preventDefault();
|
|
17772
18368
|
playSound("bar-step");
|
|
17773
|
-
onChange(
|
|
18369
|
+
onChange(widgetKey, String(axisMax));
|
|
17774
18370
|
}
|
|
17775
18371
|
},
|
|
17776
|
-
[axisMin, axisMax,
|
|
18372
|
+
[axisMin, axisMax, widgetKey, currentValue, axis, snap, onChange, playSound, exerciseLocked]
|
|
17777
18373
|
);
|
|
17778
18374
|
const fill = toFill(currentValue, axis);
|
|
17779
18375
|
const isAtMin = currentValue <= axisMin;
|
|
@@ -17849,7 +18445,7 @@ const StaticBar = ({
|
|
|
17849
18445
|
}
|
|
17850
18446
|
) });
|
|
17851
18447
|
};
|
|
17852
|
-
const EMPTY_SET$
|
|
18448
|
+
const EMPTY_SET$4 = /* @__PURE__ */ new Set();
|
|
17853
18449
|
const XAxisLabel = ({
|
|
17854
18450
|
bar,
|
|
17855
18451
|
widgets,
|
|
@@ -17864,14 +18460,14 @@ const XAxisLabel = ({
|
|
|
17864
18460
|
item: subItem,
|
|
17865
18461
|
widgets,
|
|
17866
18462
|
values,
|
|
17867
|
-
droppedInputs: EMPTY_SET$
|
|
18463
|
+
droppedInputs: EMPTY_SET$4,
|
|
17868
18464
|
validationDetails,
|
|
17869
18465
|
onChange
|
|
17870
18466
|
},
|
|
17871
18467
|
subItem.id
|
|
17872
18468
|
)) }) });
|
|
17873
18469
|
};
|
|
17874
|
-
const EMPTY_SET$
|
|
18470
|
+
const EMPTY_SET$3 = /* @__PURE__ */ new Set();
|
|
17875
18471
|
const BarChart = ({
|
|
17876
18472
|
item,
|
|
17877
18473
|
widgets,
|
|
@@ -17879,6 +18475,7 @@ const BarChart = ({
|
|
|
17879
18475
|
validationDetails,
|
|
17880
18476
|
onChange
|
|
17881
18477
|
}) => {
|
|
18478
|
+
if (item.chartType !== "bar") return null;
|
|
17882
18479
|
const { bars, grid } = item;
|
|
17883
18480
|
const isHorizontal = item.orientation === "horizontal";
|
|
17884
18481
|
const valueAxis = isHorizontal ? item.xAxis : item.yAxis;
|
|
@@ -17892,7 +18489,7 @@ const BarChart = ({
|
|
|
17892
18489
|
item: subItem,
|
|
17893
18490
|
widgets,
|
|
17894
18491
|
values,
|
|
17895
|
-
droppedInputs: EMPTY_SET$
|
|
18492
|
+
droppedInputs: EMPTY_SET$3,
|
|
17896
18493
|
validationDetails,
|
|
17897
18494
|
onChange
|
|
17898
18495
|
},
|
|
@@ -17902,9 +18499,10 @@ const BarChart = ({
|
|
|
17902
18499
|
const fillAxis = valueAxis ?? CHART_AXIS_DEFAULTS;
|
|
17903
18500
|
const renderBar = (bar, orient = "vertical") => {
|
|
17904
18501
|
const currentValue = resolveValue(bar, values);
|
|
17905
|
-
const
|
|
18502
|
+
const widgetKey = interactiveBarKey(bar);
|
|
18503
|
+
const vStatus = widgetKey !== void 0 ? validationDetails?.[widgetKey] : void 0;
|
|
17906
18504
|
const validationStatus = vStatus?.isCorrect === true ? "correct" : vStatus?.isCorrect === false ? "incorrect" : null;
|
|
17907
|
-
return /* @__PURE__ */ jsx("div", { className: "chart-bar-wrapper", children:
|
|
18505
|
+
return /* @__PURE__ */ jsx("div", { className: "chart-bar-wrapper", children: widgetKey !== void 0 ? /* @__PURE__ */ jsx(
|
|
17908
18506
|
DraggableBar,
|
|
17909
18507
|
{
|
|
17910
18508
|
bar,
|
|
@@ -17937,7 +18535,7 @@ const BarChart = ({
|
|
|
17937
18535
|
item: subItem,
|
|
17938
18536
|
widgets,
|
|
17939
18537
|
values,
|
|
17940
|
-
droppedInputs: EMPTY_SET$
|
|
18538
|
+
droppedInputs: EMPTY_SET$3,
|
|
17941
18539
|
validationDetails,
|
|
17942
18540
|
onChange
|
|
17943
18541
|
},
|
|
@@ -18142,7 +18740,7 @@ const PictographGrid = ({
|
|
|
18142
18740
|
))
|
|
18143
18741
|
] });
|
|
18144
18742
|
};
|
|
18145
|
-
const EMPTY_SET = /* @__PURE__ */ new Set();
|
|
18743
|
+
const EMPTY_SET$2 = /* @__PURE__ */ new Set();
|
|
18146
18744
|
const PictographChart = ({
|
|
18147
18745
|
item,
|
|
18148
18746
|
widgets,
|
|
@@ -18150,7 +18748,8 @@ const PictographChart = ({
|
|
|
18150
18748
|
validationDetails,
|
|
18151
18749
|
onChange
|
|
18152
18750
|
}) => {
|
|
18153
|
-
|
|
18751
|
+
if (item.chartType !== "pictograph") return null;
|
|
18752
|
+
const { pictos, grid } = item;
|
|
18154
18753
|
const isHorizontal = item.orientation === "horizontal";
|
|
18155
18754
|
const valueAxis = isHorizontal ? item.xAxis : item.yAxis;
|
|
18156
18755
|
const categoryAxis = isHorizontal ? item.yAxis : item.xAxis;
|
|
@@ -18163,7 +18762,7 @@ const PictographChart = ({
|
|
|
18163
18762
|
item: subItem,
|
|
18164
18763
|
widgets,
|
|
18165
18764
|
values,
|
|
18166
|
-
droppedInputs: EMPTY_SET,
|
|
18765
|
+
droppedInputs: EMPTY_SET$2,
|
|
18167
18766
|
validationDetails,
|
|
18168
18767
|
onChange
|
|
18169
18768
|
},
|
|
@@ -18201,7 +18800,7 @@ const PictographChart = ({
|
|
|
18201
18800
|
item: subItem,
|
|
18202
18801
|
widgets,
|
|
18203
18802
|
values,
|
|
18204
|
-
droppedInputs: EMPTY_SET,
|
|
18803
|
+
droppedInputs: EMPTY_SET$2,
|
|
18205
18804
|
validationDetails,
|
|
18206
18805
|
onChange
|
|
18207
18806
|
},
|
|
@@ -18222,7 +18821,7 @@ const PictographChart = ({
|
|
|
18222
18821
|
item: subItem,
|
|
18223
18822
|
widgets,
|
|
18224
18823
|
values,
|
|
18225
|
-
droppedInputs: EMPTY_SET,
|
|
18824
|
+
droppedInputs: EMPTY_SET$2,
|
|
18226
18825
|
validationDetails,
|
|
18227
18826
|
onChange
|
|
18228
18827
|
},
|
|
@@ -18241,9 +18840,183 @@ const PictographChart = ({
|
|
|
18241
18840
|
xAxisTitle
|
|
18242
18841
|
] }) }) });
|
|
18243
18842
|
};
|
|
18843
|
+
const GroupSlot = ({
|
|
18844
|
+
group,
|
|
18845
|
+
series,
|
|
18846
|
+
axis,
|
|
18847
|
+
snap,
|
|
18848
|
+
orientation = "vertical",
|
|
18849
|
+
values,
|
|
18850
|
+
validationDetails,
|
|
18851
|
+
onChange
|
|
18852
|
+
}) => {
|
|
18853
|
+
return /* @__PURE__ */ jsx("div", { className: "chart-group", children: series.map((s) => {
|
|
18854
|
+
const bar = group.bars.find((b) => b.seriesId === s.id);
|
|
18855
|
+
if (!bar) {
|
|
18856
|
+
return /* @__PURE__ */ jsx("div", { className: "chart-group-bar-gap", "aria-hidden": "true" }, s.id);
|
|
18857
|
+
}
|
|
18858
|
+
const currentValue = resolveValue(bar, values);
|
|
18859
|
+
const vStatus = bar.answer !== void 0 ? validationDetails?.[bar.id] : void 0;
|
|
18860
|
+
const validationStatus = vStatus?.isCorrect === true ? "correct" : vStatus?.isCorrect === false ? "incorrect" : null;
|
|
18861
|
+
return /* @__PURE__ */ jsx("div", { className: "chart-bar-wrapper", children: bar.answer !== void 0 ? /* @__PURE__ */ jsx(
|
|
18862
|
+
DraggableBar,
|
|
18863
|
+
{
|
|
18864
|
+
bar: { ...bar, answer: bar.answer },
|
|
18865
|
+
currentValue,
|
|
18866
|
+
axis,
|
|
18867
|
+
snap,
|
|
18868
|
+
validationStatus,
|
|
18869
|
+
orientation,
|
|
18870
|
+
onChange
|
|
18871
|
+
}
|
|
18872
|
+
) : /* @__PURE__ */ jsx(
|
|
18873
|
+
StaticBar,
|
|
18874
|
+
{
|
|
18875
|
+
bar,
|
|
18876
|
+
currentValue,
|
|
18877
|
+
axis,
|
|
18878
|
+
validationStatus,
|
|
18879
|
+
orientation
|
|
18880
|
+
}
|
|
18881
|
+
) }, bar.id);
|
|
18882
|
+
}) });
|
|
18883
|
+
};
|
|
18884
|
+
const EMPTY_SET$1 = /* @__PURE__ */ new Set();
|
|
18885
|
+
const ChartLegend = ({
|
|
18886
|
+
series,
|
|
18887
|
+
widgets,
|
|
18888
|
+
values,
|
|
18889
|
+
validationDetails,
|
|
18890
|
+
onChange
|
|
18891
|
+
}) => {
|
|
18892
|
+
return /* @__PURE__ */ jsx("div", { className: "chart-legend", children: series.map((s) => /* @__PURE__ */ jsxs("div", { className: "chart-legend-item", children: [
|
|
18893
|
+
/* @__PURE__ */ jsx("span", { className: "chart-legend-swatch", "aria-hidden": "true", style: { backgroundColor: s.color } }),
|
|
18894
|
+
/* @__PURE__ */ jsx("span", { className: "chart-legend-label", children: s.label?.map((subItem) => /* @__PURE__ */ jsx(
|
|
18895
|
+
LayoutRenderer,
|
|
18896
|
+
{
|
|
18897
|
+
item: subItem,
|
|
18898
|
+
widgets,
|
|
18899
|
+
values,
|
|
18900
|
+
droppedInputs: EMPTY_SET$1,
|
|
18901
|
+
validationDetails,
|
|
18902
|
+
onChange
|
|
18903
|
+
},
|
|
18904
|
+
subItem.id
|
|
18905
|
+
)) })
|
|
18906
|
+
] }, s.id)) });
|
|
18907
|
+
};
|
|
18908
|
+
const EMPTY_SET = /* @__PURE__ */ new Set();
|
|
18909
|
+
const GroupedBarChart = ({
|
|
18910
|
+
item,
|
|
18911
|
+
widgets,
|
|
18912
|
+
values,
|
|
18913
|
+
validationDetails,
|
|
18914
|
+
onChange
|
|
18915
|
+
}) => {
|
|
18916
|
+
if (item.chartType !== "grouped-bar") return null;
|
|
18917
|
+
const { groups, series, grid } = item;
|
|
18918
|
+
const isHorizontal = item.orientation === "horizontal";
|
|
18919
|
+
const isInteractive = groups.some((g) => g.bars.some((bar) => bar.answer !== void 0));
|
|
18920
|
+
const valueAxis = isHorizontal ? item.xAxis : item.yAxis;
|
|
18921
|
+
const categoryAxis = isHorizontal ? item.yAxis : item.xAxis;
|
|
18922
|
+
const chartClass = getChartClassName(item);
|
|
18923
|
+
const fillAxis = valueAxis ?? CHART_AXIS_DEFAULTS;
|
|
18924
|
+
const renderAxisTitle = (title) => {
|
|
18925
|
+
if (!title?.length) return null;
|
|
18926
|
+
return /* @__PURE__ */ jsx(Fragment, { children: title.map((subItem) => /* @__PURE__ */ jsx(
|
|
18927
|
+
LayoutRenderer,
|
|
18928
|
+
{
|
|
18929
|
+
item: subItem,
|
|
18930
|
+
widgets,
|
|
18931
|
+
values,
|
|
18932
|
+
droppedInputs: EMPTY_SET,
|
|
18933
|
+
validationDetails,
|
|
18934
|
+
onChange
|
|
18935
|
+
},
|
|
18936
|
+
subItem.id
|
|
18937
|
+
)) });
|
|
18938
|
+
};
|
|
18939
|
+
const renderGroupSlots = (orient) => /* @__PURE__ */ jsx("div", { className: "chart-groups", children: groups.map((group) => /* @__PURE__ */ jsx(
|
|
18940
|
+
GroupSlot,
|
|
18941
|
+
{
|
|
18942
|
+
group,
|
|
18943
|
+
series,
|
|
18944
|
+
axis: fillAxis,
|
|
18945
|
+
snap: item.snap,
|
|
18946
|
+
orientation: orient,
|
|
18947
|
+
values,
|
|
18948
|
+
validationDetails,
|
|
18949
|
+
onChange
|
|
18950
|
+
},
|
|
18951
|
+
group.id
|
|
18952
|
+
)) });
|
|
18953
|
+
const legend = item.legend ? /* @__PURE__ */ jsx(
|
|
18954
|
+
ChartLegend,
|
|
18955
|
+
{
|
|
18956
|
+
series,
|
|
18957
|
+
widgets,
|
|
18958
|
+
values,
|
|
18959
|
+
validationDetails,
|
|
18960
|
+
onChange
|
|
18961
|
+
}
|
|
18962
|
+
) : null;
|
|
18963
|
+
if (isHorizontal) {
|
|
18964
|
+
const chartStyle2 = computeGroupedChartStyle(item.scale, groups.length, series.length, "horizontal", isInteractive);
|
|
18965
|
+
return /* @__PURE__ */ jsx("div", { className: "chart-container", children: /* @__PURE__ */ jsx("div", { className: "chart-scroll", children: /* @__PURE__ */ jsxs("div", { className: chartClass, dir: "ltr", style: chartStyle2, children: [
|
|
18966
|
+
item.title && /* @__PURE__ */ jsx("div", { className: "chart-title", children: item.title }),
|
|
18967
|
+
categoryAxis?.visible && categoryAxis.title?.length ? /* @__PURE__ */ jsx("div", { className: "chart-axis-title chart-axis-title--y-h", children: renderAxisTitle(categoryAxis.title) }) : null,
|
|
18968
|
+
/* @__PURE__ */ jsx("div", { className: "chart-axis-y--categories", children: groups.map((group) => /* @__PURE__ */ jsx("div", { className: "chart-bar-y-label", children: group.label?.map((subItem) => /* @__PURE__ */ jsx(
|
|
18969
|
+
LayoutRenderer,
|
|
18970
|
+
{
|
|
18971
|
+
item: subItem,
|
|
18972
|
+
widgets,
|
|
18973
|
+
values,
|
|
18974
|
+
droppedInputs: EMPTY_SET,
|
|
18975
|
+
validationDetails,
|
|
18976
|
+
onChange
|
|
18977
|
+
},
|
|
18978
|
+
subItem.id
|
|
18979
|
+
)) }, group.id)) }),
|
|
18980
|
+
/* @__PURE__ */ jsxs("div", { className: "chart-plot", children: [
|
|
18981
|
+
grid && /* @__PURE__ */ jsx(Grid, { axis: fillAxis, orientation: "horizontal" }),
|
|
18982
|
+
renderGroupSlots("horizontal")
|
|
18983
|
+
] }),
|
|
18984
|
+
valueAxis && valueAxis.visible && /* @__PURE__ */ jsx(XAxis, { axis: valueAxis, title: renderAxisTitle(valueAxis.title) }),
|
|
18985
|
+
legend
|
|
18986
|
+
] }) }) });
|
|
18987
|
+
}
|
|
18988
|
+
const chartStyle = computeGroupedChartStyle(item.scale, groups.length, series.length, "vertical", isInteractive);
|
|
18989
|
+
const hasAnyGroupLabel = groups.some((g) => g.label && g.label.length > 0);
|
|
18990
|
+
const groupLabelRow = hasAnyGroupLabel ? /* @__PURE__ */ jsx("div", { className: "chart-axis-x", children: groups.map((group) => /* @__PURE__ */ jsx("div", { className: "chart-axis-tick-x", children: /* @__PURE__ */ jsx("div", { className: "chart-axis-tick-x-inner", children: group.label?.map((subItem) => /* @__PURE__ */ jsx(
|
|
18991
|
+
LayoutRenderer,
|
|
18992
|
+
{
|
|
18993
|
+
item: subItem,
|
|
18994
|
+
widgets,
|
|
18995
|
+
values,
|
|
18996
|
+
droppedInputs: EMPTY_SET,
|
|
18997
|
+
validationDetails,
|
|
18998
|
+
onChange
|
|
18999
|
+
},
|
|
19000
|
+
subItem.id
|
|
19001
|
+
)) }) }, group.id)) }) : null;
|
|
19002
|
+
const xAxisTitle = categoryAxis?.visible && categoryAxis.title?.length ? /* @__PURE__ */ jsx("div", { className: "chart-axis-title chart-axis-title--x-category", children: renderAxisTitle(categoryAxis.title) }) : null;
|
|
19003
|
+
return /* @__PURE__ */ jsx("div", { className: "chart-container", children: /* @__PURE__ */ jsx("div", { className: "chart-scroll", children: /* @__PURE__ */ jsxs("div", { className: chartClass, dir: "ltr", style: chartStyle, children: [
|
|
19004
|
+
item.title && /* @__PURE__ */ jsx("div", { className: "chart-title", children: item.title }),
|
|
19005
|
+
valueAxis?.visible && valueAxis.title?.length ? /* @__PURE__ */ jsx("div", { className: "chart-axis-title chart-axis-title--y", children: renderAxisTitle(valueAxis.title) }) : null,
|
|
19006
|
+
valueAxis && valueAxis.visible && /* @__PURE__ */ jsx(YAxis, { axis: valueAxis }),
|
|
19007
|
+
/* @__PURE__ */ jsxs("div", { className: "chart-plot", children: [
|
|
19008
|
+
grid && /* @__PURE__ */ jsx(Grid, { axis: fillAxis }),
|
|
19009
|
+
renderGroupSlots("vertical")
|
|
19010
|
+
] }),
|
|
19011
|
+
groupLabelRow,
|
|
19012
|
+
xAxisTitle,
|
|
19013
|
+
legend
|
|
19014
|
+
] }) }) });
|
|
19015
|
+
};
|
|
18244
19016
|
const CHART_TYPE_COMPONENTS = {
|
|
18245
19017
|
bar: BarChart,
|
|
18246
|
-
pictograph: PictographChart
|
|
19018
|
+
pictograph: PictographChart,
|
|
19019
|
+
"grouped-bar": GroupedBarChart
|
|
18247
19020
|
// line: LineChart,
|
|
18248
19021
|
// pie: PieChart,
|
|
18249
19022
|
};
|
|
@@ -18285,11 +19058,12 @@ const RadioOptions = ({
|
|
|
18285
19058
|
};
|
|
18286
19059
|
const Btn = ({
|
|
18287
19060
|
label,
|
|
19061
|
+
dataKey = label,
|
|
18288
19062
|
extraClass = "keypad-btn-digit",
|
|
18289
19063
|
onClick
|
|
18290
19064
|
}) => {
|
|
18291
19065
|
const locked = useExerciseLocked();
|
|
18292
|
-
return /* @__PURE__ */ jsx("button", { className: `keypad-btn ${extraClass}`, disabled: locked, onClick, children: /* @__PURE__ */ jsx("span", { children: label }) });
|
|
19066
|
+
return /* @__PURE__ */ jsx("button", { className: `keypad-btn ${extraClass}`, "data-key": dataKey, disabled: locked, onClick, children: /* @__PURE__ */ jsx("span", { children: label }) });
|
|
18293
19067
|
};
|
|
18294
19068
|
const DiamondKeypad = ({ value, onChange }) => {
|
|
18295
19069
|
const playSound = useSound();
|
|
@@ -18301,7 +19075,7 @@ const DiamondKeypad = ({ value, onChange }) => {
|
|
|
18301
19075
|
playSound("keypad-press");
|
|
18302
19076
|
onChange(value.slice(0, -1));
|
|
18303
19077
|
};
|
|
18304
|
-
return /* @__PURE__ */ jsxs("div", { className: "diamond-keypad", children: [
|
|
19078
|
+
return /* @__PURE__ */ jsxs("div", { className: "diamond-keypad", "data-tutorial": "keypad", children: [
|
|
18305
19079
|
/* @__PURE__ */ jsxs("div", { className: "diamond-keypad-row", children: [
|
|
18306
19080
|
/* @__PURE__ */ jsx(Btn, { label: "1", onClick: () => append("1") }),
|
|
18307
19081
|
/* @__PURE__ */ jsx(Btn, { label: "2", onClick: () => append("2") }),
|
|
@@ -18320,7 +19094,7 @@ const DiamondKeypad = ({ value, onChange }) => {
|
|
|
18320
19094
|
] }),
|
|
18321
19095
|
/* @__PURE__ */ jsxs("div", { className: "diamond-keypad-row", children: [
|
|
18322
19096
|
/* @__PURE__ */ jsx(Btn, { label: ".", extraClass: "keypad-btn-decimal", onClick: () => append(".") }),
|
|
18323
|
-
/* @__PURE__ */ jsx(Btn, { label: "⌫", extraClass: "keypad-btn-backspace", onClick: backspace })
|
|
19097
|
+
/* @__PURE__ */ jsx(Btn, { label: "⌫", dataKey: "backspace", extraClass: "keypad-btn-backspace", onClick: backspace })
|
|
18324
19098
|
] })
|
|
18325
19099
|
] });
|
|
18326
19100
|
};
|
|
@@ -18926,7 +19700,7 @@ function isGeometryWidget(widget) {
|
|
|
18926
19700
|
return widget.type === JSON_TYPES.GEOMETRY;
|
|
18927
19701
|
}
|
|
18928
19702
|
const GeometryWidget = React.lazy(async () => ({
|
|
18929
|
-
default: (await import("./GeometryWidget-
|
|
19703
|
+
default: (await import("./GeometryWidget-2TCLbTEv.mjs")).GeometryWidget
|
|
18930
19704
|
}));
|
|
18931
19705
|
const WidgetRenderer = ({
|
|
18932
19706
|
widget,
|
|
@@ -18954,8 +19728,9 @@ const WidgetRenderer = ({
|
|
|
18954
19728
|
isDropped,
|
|
18955
19729
|
validationStatus,
|
|
18956
19730
|
inputType: widget.inputType,
|
|
18957
|
-
length: widget.inputType ===
|
|
18958
|
-
digits: widget.inputType ===
|
|
19731
|
+
length: widget.inputType === INPUT_TYPES.STRING ? widget.length : void 0,
|
|
19732
|
+
digits: widget.inputType === INPUT_TYPES.NUMERIC ? widget.digits : void 0,
|
|
19733
|
+
width: widget.width,
|
|
18959
19734
|
placeholder: widget.placeholder,
|
|
18960
19735
|
disabled: widget.disabled,
|
|
18961
19736
|
onChange,
|
|
@@ -19326,6 +20101,13 @@ const EPSILON = 10 ** -INTERNAL_PRECISION;
|
|
|
19326
20101
|
const cleanFloat = (n) => parseFloat(n.toFixed(INTERNAL_PRECISION));
|
|
19327
20102
|
const roundTick = (n) => parseFloat(n.toFixed(DISPLAY_PRECISION));
|
|
19328
20103
|
const almostEqual = (a, b) => Math.abs(a - b) <= EPSILON;
|
|
20104
|
+
const NUMBER_LINE_AXIS_ENDS = {
|
|
20105
|
+
"arrow-right": { left: "tick", right: "arrow" },
|
|
20106
|
+
"arrow-left": { left: "arrow", right: "tick" },
|
|
20107
|
+
"arrow-both": { left: "arrow", right: "arrow" },
|
|
20108
|
+
"line": { left: "line", right: "line" },
|
|
20109
|
+
"line-left": { left: "line", right: "arrow" }
|
|
20110
|
+
};
|
|
19329
20111
|
const toPercent = (value, min, max) => {
|
|
19330
20112
|
const raw = SIDE_PAD + (value - min) / (max - min) * (100 - 2 * SIDE_PAD);
|
|
19331
20113
|
return Math.max(SIDE_PAD, Math.min(100 - SIDE_PAD, raw));
|
|
@@ -20373,7 +21155,7 @@ const NumberLineRenderer = ({
|
|
|
20373
21155
|
max,
|
|
20374
21156
|
step,
|
|
20375
21157
|
minorTicks = 0,
|
|
20376
|
-
style: lineStyle =
|
|
21158
|
+
style: lineStyle = DEFAULT_NUMBER_LINE_STYLE,
|
|
20377
21159
|
labelVisibility,
|
|
20378
21160
|
highlights,
|
|
20379
21161
|
arrows,
|
|
@@ -20437,8 +21219,9 @@ const NumberLineRenderer = ({
|
|
|
20437
21219
|
() => getMinorTickValues(majorTicks, minorTicks),
|
|
20438
21220
|
[majorTicks, minorTicks]
|
|
20439
21221
|
);
|
|
20440
|
-
const
|
|
20441
|
-
const showArrowLeft =
|
|
21222
|
+
const axisEnds = NUMBER_LINE_AXIS_ENDS[lineStyle] ?? NUMBER_LINE_AXIS_ENDS[DEFAULT_NUMBER_LINE_STYLE];
|
|
21223
|
+
const showArrowLeft = axisEnds.left === "arrow";
|
|
21224
|
+
const showArrowRight = axisEnds.right === "arrow";
|
|
20442
21225
|
const hasDragOptions = Boolean(widgets[DRAG_OPTIONS_WIDGET_ID]);
|
|
20443
21226
|
const isAxisDragMode = hasDragOptions && item.dragMode === "axis";
|
|
20444
21227
|
const inputTickSet = useMemo(() => {
|
|
@@ -20486,7 +21269,7 @@ const NumberLineRenderer = ({
|
|
|
20486
21269
|
return /* @__PURE__ */ jsxs(
|
|
20487
21270
|
"div",
|
|
20488
21271
|
{
|
|
20489
|
-
className: `number-line${isAxisDragMode ? " number-line--drag-mode" : ""}${
|
|
21272
|
+
className: `number-line${isAxisDragMode ? " number-line--drag-mode" : ""}${axisEnds.left === "line" ? " number-line--extend-left" : ""}${axisEnds.right === "line" ? " number-line--extend-right" : ""}${nlValidationStatus ? ` number-line--${nlValidationStatus}` : ""}`,
|
|
20490
21273
|
style: {
|
|
20491
21274
|
[CSS_VAR_SIDE_PAD]: `${SIDE_PAD}%`,
|
|
20492
21275
|
...positionContentHeight && { "--nl-position-content-height": positionContentHeight }
|
|
@@ -20859,6 +21642,35 @@ function groupLineItems(items) {
|
|
|
20859
21642
|
}
|
|
20860
21643
|
return groups;
|
|
20861
21644
|
}
|
|
21645
|
+
function placeTableCells(rows, cols) {
|
|
21646
|
+
const placements = /* @__PURE__ */ new Map();
|
|
21647
|
+
const occupied = rows.map(() => []);
|
|
21648
|
+
const totalRows = rows.length;
|
|
21649
|
+
rows.forEach((row, rowIndex) => {
|
|
21650
|
+
const taken = occupied[rowIndex] ?? [];
|
|
21651
|
+
let col = 0;
|
|
21652
|
+
for (const cell of row.cells) {
|
|
21653
|
+
while (taken[col]) col++;
|
|
21654
|
+
const colspan = cell.colspan || 1;
|
|
21655
|
+
const rowspan = Math.min(cell.rowspan || 1, totalRows - rowIndex);
|
|
21656
|
+
for (let r = rowIndex; r < rowIndex + rowspan; r++) {
|
|
21657
|
+
const line = occupied[r];
|
|
21658
|
+
if (!line) continue;
|
|
21659
|
+
for (let c = col; c < col + colspan; c++) line[c] = true;
|
|
21660
|
+
}
|
|
21661
|
+
placements.set(cell.id, {
|
|
21662
|
+
column: col + 1,
|
|
21663
|
+
row: rowIndex + 1,
|
|
21664
|
+
colspan,
|
|
21665
|
+
rowspan,
|
|
21666
|
+
isLastColumn: col + colspan >= cols,
|
|
21667
|
+
isLastRow: rowIndex + rowspan >= totalRows
|
|
21668
|
+
});
|
|
21669
|
+
col += colspan;
|
|
21670
|
+
}
|
|
21671
|
+
});
|
|
21672
|
+
return placements;
|
|
21673
|
+
}
|
|
20862
21674
|
const toValidationStatus = (v) => v === true ? "correct" : v === false ? "incorrect" : null;
|
|
20863
21675
|
const isNumericText = (value) => /^[\d.,\s]+$/.test(value);
|
|
20864
21676
|
const numberClass = (value) => isNumericText(value) ? "text-item-number" : "";
|
|
@@ -20982,21 +21794,28 @@ const LayoutRenderer = ({
|
|
|
20982
21794
|
subItem.id
|
|
20983
21795
|
)) });
|
|
20984
21796
|
case JSON_TYPES.TABLE: {
|
|
20985
|
-
const
|
|
21797
|
+
const placements = placeTableCells(item.rows, item.cols);
|
|
20986
21798
|
const tableStyle = {
|
|
20987
|
-
"--exercise-table-border-color": item.borderColor ?? DEFAULT_TABLE_BORDER_COLOR
|
|
21799
|
+
"--exercise-table-border-color": item.borderColor ?? DEFAULT_TABLE_BORDER_COLOR,
|
|
21800
|
+
"--exercise-table-cols": item.cols
|
|
20988
21801
|
};
|
|
20989
|
-
return /* @__PURE__ */ jsx("div", { className: "exercise-table-container", style: tableStyle, children: /* @__PURE__ */ jsx("
|
|
20990
|
-
const
|
|
20991
|
-
const
|
|
21802
|
+
return /* @__PURE__ */ jsx("div", { className: "exercise-table-container", style: tableStyle, children: /* @__PURE__ */ jsx("div", { className: "exercise-table", role: "table", children: item.rows.map((row) => /* @__PURE__ */ jsx("div", { className: "exercise-table-row", role: "row", children: row.cells.map((cell) => {
|
|
21803
|
+
const placement = placements.get(cell.id);
|
|
21804
|
+
const modifiers = [
|
|
21805
|
+
placement?.isLastRow ? " exercise-table-cell--last-row" : "",
|
|
21806
|
+
placement?.isLastColumn ? " exercise-table-cell--last-col" : ""
|
|
21807
|
+
].join("");
|
|
20992
21808
|
return /* @__PURE__ */ jsx(
|
|
20993
|
-
"
|
|
21809
|
+
"div",
|
|
20994
21810
|
{
|
|
20995
|
-
className: `exercise-table-cell${
|
|
20996
|
-
|
|
20997
|
-
|
|
20998
|
-
|
|
20999
|
-
|
|
21811
|
+
className: `exercise-table-cell${modifiers}`,
|
|
21812
|
+
role: "cell",
|
|
21813
|
+
style: {
|
|
21814
|
+
backgroundColor: cell.backgroundColor ?? row.backgroundColor,
|
|
21815
|
+
gridColumn: placement && `${placement.column} / span ${placement.colspan}`,
|
|
21816
|
+
gridRow: placement && `${placement.row} / span ${placement.rowspan}`
|
|
21817
|
+
},
|
|
21818
|
+
children: /* @__PURE__ */ jsx("div", { className: "exercise-table-cell-content", children: cell.items.map((subItem) => /* @__PURE__ */ jsx(
|
|
21000
21819
|
LayoutRenderer,
|
|
21001
21820
|
{
|
|
21002
21821
|
item: subItem,
|
|
@@ -21009,11 +21828,11 @@ const LayoutRenderer = ({
|
|
|
21009
21828
|
resetKey
|
|
21010
21829
|
},
|
|
21011
21830
|
subItem.id
|
|
21012
|
-
))
|
|
21831
|
+
)) })
|
|
21013
21832
|
},
|
|
21014
21833
|
cell.id
|
|
21015
21834
|
);
|
|
21016
|
-
}) }, row.id)) }) })
|
|
21835
|
+
}) }, row.id)) }) });
|
|
21017
21836
|
}
|
|
21018
21837
|
case JSON_TYPES.NUMBER_LINE:
|
|
21019
21838
|
return /* @__PURE__ */ jsx(
|
|
@@ -21183,6 +22002,394 @@ const LayoutRenderer = ({
|
|
|
21183
22002
|
return null;
|
|
21184
22003
|
}
|
|
21185
22004
|
};
|
|
22005
|
+
function LionHand({ className }) {
|
|
22006
|
+
const id = useId();
|
|
22007
|
+
const shadowId = `${id}-shadow`;
|
|
22008
|
+
const maskId = `${id}-mask`;
|
|
22009
|
+
return /* @__PURE__ */ jsxs(
|
|
22010
|
+
"svg",
|
|
22011
|
+
{
|
|
22012
|
+
"aria-hidden": "true",
|
|
22013
|
+
focusable: "false",
|
|
22014
|
+
width: "75",
|
|
22015
|
+
height: "78",
|
|
22016
|
+
viewBox: "0 0 75 78",
|
|
22017
|
+
fill: "none",
|
|
22018
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
22019
|
+
className,
|
|
22020
|
+
children: [
|
|
22021
|
+
/* @__PURE__ */ jsxs("g", { filter: `url(#${shadowId})`, children: [
|
|
22022
|
+
/* @__PURE__ */ jsx(
|
|
22023
|
+
"mask",
|
|
22024
|
+
{
|
|
22025
|
+
id: maskId,
|
|
22026
|
+
style: { maskType: "alpha" },
|
|
22027
|
+
maskUnits: "userSpaceOnUse",
|
|
22028
|
+
x: "9",
|
|
22029
|
+
y: "19",
|
|
22030
|
+
width: "49",
|
|
22031
|
+
height: "42",
|
|
22032
|
+
children: /* @__PURE__ */ jsx("path", { d: "M49.7484 33.9668C58.158 45.6281 37.9572 61.4399 28.1745 60.1051C17.1397 58.5995 5.03802 42.2474 10.4349 38.3224C15.8318 34.3974 18.9715 32.4031 23.1181 30.1278C24.1486 29.5624 25.1742 29.0263 26.1947 28.5358C33.7314 24.764 52.8138 16.0308 57.0969 21.97C59.9201 25.8848 54.8651 28.9522 48.1097 31.8806C48.658 32.518 49.2042 33.2123 49.7484 33.9668Z", fill: "#FCD592" })
|
|
22033
|
+
}
|
|
22034
|
+
),
|
|
22035
|
+
/* @__PURE__ */ jsxs("g", { mask: `url(#${maskId})`, children: [
|
|
22036
|
+
/* @__PURE__ */ jsx("path", { d: "M49.7484 33.9672C58.158 45.6285 38.3118 60.3151 32.1037 64.7332C22.6672 71.4486 2.68204 43.9969 12.2424 37.1926C15.1884 35.0959 18.9716 32.4035 23.1182 30.1282C24.7308 29.2433 26.3314 28.4303 27.9195 27.7514C36.611 23.6398 54.4269 16.1501 58.4471 21.7248C61.34 25.7362 55.5686 28.9294 48.2024 31.9893C48.7196 32.5968 49.2349 33.2552 49.7484 33.9672Z", fill: "#FCD592" }),
|
|
22037
|
+
/* @__PURE__ */ jsx("path", { d: "M12.2426 37.1924C2.68223 43.9967 22.6674 71.4485 32.1039 64.733C31.3434 63.6785 26.4317 61.1668 25.3134 59.6161C24.1951 58.0654 22.794 50.5211 22.123 49.5907C21.4521 48.6603 16.3219 45.7409 15.5637 44.4032C14.8055 43.0655 13.8979 39.4877 14.7258 35.4253L12.2426 37.1924Z", fill: "#6B291E" }),
|
|
22038
|
+
/* @__PURE__ */ jsx("path", { d: "M50.4102 36.1737C50.6823 37.524 52.0035 38.4016 53.3614 38.1339C54.7192 37.8662 55.5994 36.5546 55.3274 35.2043L52.8688 35.689L50.4102 36.1737ZM38.8014 26.9523C37.4241 27.0934 36.425 28.3178 36.5697 29.6872C36.7144 31.0565 37.9482 32.0522 39.3255 31.9111L39.0634 29.4317L38.8014 26.9523ZM52.8688 35.689C55.3274 35.2043 55.327 35.2023 55.3266 35.2004C55.3265 35.1997 55.326 35.1977 55.3258 35.1963C55.3252 35.1935 55.3246 35.1906 55.324 35.1875C55.3227 35.1815 55.3213 35.1749 55.3198 35.1679C55.3168 35.1539 55.3133 35.1379 55.3092 35.1201C55.3011 35.0845 55.2907 35.0415 55.2779 34.9916C55.2522 34.8918 55.2163 34.764 55.1676 34.6127C55.0703 34.3106 54.9205 33.911 54.6961 33.4507C54.247 32.5296 53.4925 31.3535 52.2552 30.241C49.7078 27.9508 45.5021 26.2658 38.8014 26.9523L39.0634 29.4317L39.3255 31.9111C44.9159 31.3384 47.6128 32.782 48.8954 33.9352C49.5731 34.5445 49.9653 35.1687 50.1842 35.6177C50.2938 35.8424 50.3584 36.0201 50.3919 36.1242C50.4086 36.1759 50.4172 36.2084 50.4197 36.2181C50.4209 36.223 50.4206 36.222 50.419 36.2148C50.4181 36.2111 50.417 36.2059 50.4155 36.1991C50.4148 36.1957 50.414 36.1919 50.4131 36.1876C50.4127 36.1855 50.4122 36.1833 50.4117 36.181C50.4115 36.1798 50.4111 36.178 50.411 36.1774C50.4106 36.1755 50.4102 36.1737 52.8688 35.689Z", fill: "#A2634A" }),
|
|
22039
|
+
/* @__PURE__ */ jsx("path", { d: "M54.7526 47.3947C55.5602 48.5146 57.1269 48.7734 58.2518 47.9729C59.3767 47.1723 59.634 45.6155 58.8264 44.4956L56.7895 45.9452L54.7526 47.3947ZM38.1539 36.5617C36.7805 36.7357 35.8113 37.9837 35.9891 39.3493C36.1669 40.7148 37.4244 41.6808 38.7978 41.5068L38.4758 39.0342L38.1539 36.5617ZM56.7895 45.9452L58.8264 44.4956C57.4277 42.5562 55.3182 40.1317 51.9378 38.4204C48.5385 36.6995 44.0772 35.8112 38.1539 36.5617L38.4758 39.0342L38.7978 41.5068C43.8796 40.863 47.3003 41.6624 49.6691 42.8616C52.0569 44.0704 53.6015 45.7985 54.7526 47.3947L56.7895 45.9452Z", fill: "#A2634A" })
|
|
22040
|
+
] })
|
|
22041
|
+
] }),
|
|
22042
|
+
/* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsxs("filter", { id: shadowId, x: "-1.99995", y: "0", width: "78.3658", height: "81.8518", filterUnits: "userSpaceOnUse", colorInterpolationFilters: "sRGB", children: [
|
|
22043
|
+
/* @__PURE__ */ jsx("feFlood", { floodOpacity: "0", result: "BackgroundImageFix" }),
|
|
22044
|
+
/* @__PURE__ */ jsx("feColorMatrix", { in: "SourceAlpha", type: "matrix", values: "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0", result: "hardAlpha" }),
|
|
22045
|
+
/* @__PURE__ */ jsx("feOffset", { dy: "2" }),
|
|
22046
|
+
/* @__PURE__ */ jsx("feGaussianBlur", { stdDeviation: "1" }),
|
|
22047
|
+
/* @__PURE__ */ jsx("feComposite", { in2: "hardAlpha", operator: "out" }),
|
|
22048
|
+
/* @__PURE__ */ jsx("feColorMatrix", { type: "matrix", values: "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0" }),
|
|
22049
|
+
/* @__PURE__ */ jsx("feBlend", { mode: "normal", in2: "BackgroundImageFix", result: "effect1_dropShadow" }),
|
|
22050
|
+
/* @__PURE__ */ jsx("feBlend", { mode: "normal", in: "SourceGraphic", in2: "effect1_dropShadow", result: "shape" })
|
|
22051
|
+
] }) })
|
|
22052
|
+
]
|
|
22053
|
+
}
|
|
22054
|
+
);
|
|
22055
|
+
}
|
|
22056
|
+
const TUTORIAL_HAND_TIP = { x: 56 / 75, y: 22 / 78 };
|
|
22057
|
+
const CYCLE_MS = 1400;
|
|
22058
|
+
const PRESS_AT_MS = 650;
|
|
22059
|
+
const PRESS_HOLD_MS = 220;
|
|
22060
|
+
const HOLE_PAD = 10;
|
|
22061
|
+
const HOLE_RADIUS = 18;
|
|
22062
|
+
const HAND_W = 72;
|
|
22063
|
+
const HAND_H = 75;
|
|
22064
|
+
const BUBBLE_W = 240;
|
|
22065
|
+
const BUBBLE_GAP = 14;
|
|
22066
|
+
const EDGE = 8;
|
|
22067
|
+
const DEFAULT_NEXT_LABEL = "فهمت";
|
|
22068
|
+
function resolve(t) {
|
|
22069
|
+
return (typeof t === "function" ? t() : t) ?? null;
|
|
22070
|
+
}
|
|
22071
|
+
function toRect(t) {
|
|
22072
|
+
if (!t) return null;
|
|
22073
|
+
const r = t instanceof Element ? t.getBoundingClientRect() : t;
|
|
22074
|
+
if (r.width <= 0 || r.height <= 0) return null;
|
|
22075
|
+
return { x: Math.round(r.x), y: Math.round(r.y), width: Math.round(r.width), height: Math.round(r.height) };
|
|
22076
|
+
}
|
|
22077
|
+
const centerOf = (r) => ({ x: r.x + r.width / 2, y: r.y + r.height / 2 });
|
|
22078
|
+
const TutorialOverlay = ({ tutorial, root, json, options, onClose }) => {
|
|
22079
|
+
const { steps } = tutorial;
|
|
22080
|
+
const [stepIndex, setStepIndex] = useState(
|
|
22081
|
+
() => Math.min(Math.max(0, Math.floor(options.startStep ?? 0)), steps.length - 1)
|
|
22082
|
+
);
|
|
22083
|
+
const step = steps[stepIndex];
|
|
22084
|
+
const optionsRef = useRef(options);
|
|
22085
|
+
optionsRef.current = options;
|
|
22086
|
+
const ctx = { root, json, submitTarget: resolve(options.submitTarget) };
|
|
22087
|
+
const ctxRef = useRef(ctx);
|
|
22088
|
+
ctxRef.current = ctx;
|
|
22089
|
+
const [layout, setLayout] = useState(null);
|
|
22090
|
+
useEffect(() => {
|
|
22091
|
+
let raf = 0;
|
|
22092
|
+
let last = "";
|
|
22093
|
+
const tick = () => {
|
|
22094
|
+
const c = ctxRef.current;
|
|
22095
|
+
const next2 = {
|
|
22096
|
+
spotlight: step.spotlight(c).map(toRect),
|
|
22097
|
+
taps: step.taps(c).map(toRect),
|
|
22098
|
+
anchor: toRect(resolve(optionsRef.current.bubbleAnchor)),
|
|
22099
|
+
root: toRect(c.root),
|
|
22100
|
+
vw: window.innerWidth,
|
|
22101
|
+
vh: window.innerHeight
|
|
22102
|
+
};
|
|
22103
|
+
const key2 = JSON.stringify(next2);
|
|
22104
|
+
if (key2 !== last) {
|
|
22105
|
+
last = key2;
|
|
22106
|
+
setLayout(next2);
|
|
22107
|
+
}
|
|
22108
|
+
raf = requestAnimationFrame(tick);
|
|
22109
|
+
};
|
|
22110
|
+
tick();
|
|
22111
|
+
return () => cancelAnimationFrame(raf);
|
|
22112
|
+
}, [step]);
|
|
22113
|
+
const [tapIndex, setTapIndex] = useState(0);
|
|
22114
|
+
const [tapCount, setTapCount] = useState(0);
|
|
22115
|
+
const [shownStep, setShownStep] = useState(stepIndex);
|
|
22116
|
+
if (shownStep !== stepIndex) {
|
|
22117
|
+
setShownStep(stepIndex);
|
|
22118
|
+
setTapIndex(0);
|
|
22119
|
+
setTapCount(0);
|
|
22120
|
+
}
|
|
22121
|
+
useEffect(() => {
|
|
22122
|
+
let release;
|
|
22123
|
+
const press = setTimeout(() => {
|
|
22124
|
+
setTapCount((n) => n + 1);
|
|
22125
|
+
const targets = step.taps(ctxRef.current);
|
|
22126
|
+
const el = targets[tapIndex % Math.max(1, targets.length)];
|
|
22127
|
+
if (el instanceof Element) {
|
|
22128
|
+
el.classList.add("met-tutorial-pressed");
|
|
22129
|
+
release = setTimeout(() => el.classList.remove("met-tutorial-pressed"), PRESS_HOLD_MS);
|
|
22130
|
+
}
|
|
22131
|
+
}, PRESS_AT_MS);
|
|
22132
|
+
const advance = setTimeout(() => setTapIndex((i) => i + 1), CYCLE_MS);
|
|
22133
|
+
return () => {
|
|
22134
|
+
clearTimeout(press);
|
|
22135
|
+
clearTimeout(advance);
|
|
22136
|
+
if (release) clearTimeout(release);
|
|
22137
|
+
root.querySelectorAll(".met-tutorial-pressed").forEach((el) => el.classList.remove("met-tutorial-pressed"));
|
|
22138
|
+
};
|
|
22139
|
+
}, [step, tapIndex, root]);
|
|
22140
|
+
const text = options.texts?.[step.id] ?? step.text;
|
|
22141
|
+
const textRef = useRef(text);
|
|
22142
|
+
textRef.current = text;
|
|
22143
|
+
useEffect(() => {
|
|
22144
|
+
optionsRef.current.onStep?.(stepIndex, step.id);
|
|
22145
|
+
optionsRef.current.onSpeak?.(step.id, textRef.current);
|
|
22146
|
+
}, [stepIndex, step]);
|
|
22147
|
+
const isLast = stepIndex === steps.length - 1;
|
|
22148
|
+
const next = () => {
|
|
22149
|
+
if (isLast) onClose(true);
|
|
22150
|
+
else setStepIndex((i) => i + 1);
|
|
22151
|
+
};
|
|
22152
|
+
const overlayRef = useRef(null);
|
|
22153
|
+
const nextRef = useRef(null);
|
|
22154
|
+
const onCloseRef = useRef(onClose);
|
|
22155
|
+
onCloseRef.current = onClose;
|
|
22156
|
+
useEffect(() => {
|
|
22157
|
+
const previous = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
22158
|
+
previous?.blur();
|
|
22159
|
+
const onKeyDown = (e) => {
|
|
22160
|
+
const overlay = overlayRef.current;
|
|
22161
|
+
if (e.key === "Escape") {
|
|
22162
|
+
e.preventDefault();
|
|
22163
|
+
e.stopPropagation();
|
|
22164
|
+
onCloseRef.current(false);
|
|
22165
|
+
return;
|
|
22166
|
+
}
|
|
22167
|
+
if (e.key === "Tab" && overlay) {
|
|
22168
|
+
e.preventDefault();
|
|
22169
|
+
const buttons = Array.from(overlay.querySelectorAll("button"));
|
|
22170
|
+
if (buttons.length === 0) return;
|
|
22171
|
+
const at = buttons.indexOf(document.activeElement);
|
|
22172
|
+
const to = (at + (e.shiftKey ? -1 : 1) + buttons.length) % buttons.length;
|
|
22173
|
+
buttons[to]?.focus();
|
|
22174
|
+
return;
|
|
22175
|
+
}
|
|
22176
|
+
if (overlay && e.target instanceof Node && overlay.contains(e.target)) return;
|
|
22177
|
+
e.preventDefault();
|
|
22178
|
+
e.stopPropagation();
|
|
22179
|
+
};
|
|
22180
|
+
document.addEventListener("keydown", onKeyDown, true);
|
|
22181
|
+
return () => {
|
|
22182
|
+
document.removeEventListener("keydown", onKeyDown, true);
|
|
22183
|
+
if (previous?.isConnected) previous.focus();
|
|
22184
|
+
};
|
|
22185
|
+
}, []);
|
|
22186
|
+
const bubbleShown = layout !== null;
|
|
22187
|
+
useEffect(() => {
|
|
22188
|
+
nextRef.current?.focus({ focusVisible: false });
|
|
22189
|
+
}, [stepIndex, bubbleShown]);
|
|
22190
|
+
const maskId = `met-tutorial-mask-${useId()}`;
|
|
22191
|
+
if (typeof document === "undefined") return null;
|
|
22192
|
+
const holes = (layout?.spotlight ?? []).filter((r) => r !== null);
|
|
22193
|
+
const tapTarget = layout?.taps.length ? layout.taps[tapIndex % layout.taps.length] ?? null : null;
|
|
22194
|
+
const Scene = step.Scene;
|
|
22195
|
+
return createPortal(
|
|
22196
|
+
/* @__PURE__ */ jsxs("div", { ref: overlayRef, className: "met-tutorial", role: "dialog", "aria-modal": "true", "aria-label": text, children: [
|
|
22197
|
+
/* @__PURE__ */ jsxs("svg", { className: "met-tutorial-dim", "aria-hidden": "true", children: [
|
|
22198
|
+
/* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsxs("mask", { id: maskId, children: [
|
|
22199
|
+
/* @__PURE__ */ jsx("rect", { width: "100%", height: "100%", fill: "white" }),
|
|
22200
|
+
holes.map((r, i) => /* @__PURE__ */ jsx(
|
|
22201
|
+
"rect",
|
|
22202
|
+
{
|
|
22203
|
+
x: r.x - HOLE_PAD,
|
|
22204
|
+
y: r.y - HOLE_PAD,
|
|
22205
|
+
width: r.width + HOLE_PAD * 2,
|
|
22206
|
+
height: r.height + HOLE_PAD * 2,
|
|
22207
|
+
rx: HOLE_RADIUS,
|
|
22208
|
+
fill: "black"
|
|
22209
|
+
},
|
|
22210
|
+
i
|
|
22211
|
+
))
|
|
22212
|
+
] }) }),
|
|
22213
|
+
/* @__PURE__ */ jsx("rect", { width: "100%", height: "100%", mask: `url(#${maskId})` })
|
|
22214
|
+
] }),
|
|
22215
|
+
holes.map((r, i) => /* @__PURE__ */ jsx(
|
|
22216
|
+
"div",
|
|
22217
|
+
{
|
|
22218
|
+
className: "met-tutorial-glow",
|
|
22219
|
+
style: {
|
|
22220
|
+
left: r.x - HOLE_PAD,
|
|
22221
|
+
top: r.y - HOLE_PAD,
|
|
22222
|
+
width: r.width + HOLE_PAD * 2,
|
|
22223
|
+
height: r.height + HOLE_PAD * 2,
|
|
22224
|
+
borderRadius: HOLE_RADIUS
|
|
22225
|
+
}
|
|
22226
|
+
},
|
|
22227
|
+
i
|
|
22228
|
+
)),
|
|
22229
|
+
Scene && layout && /* @__PURE__ */ jsx(Scene, { ctx, spotlight: layout.spotlight, taps: layout.taps, tapCount }),
|
|
22230
|
+
tapTarget && /* @__PURE__ */ jsx(Hand, { target: tapTarget, tapKey: `${stepIndex}-${tapIndex}` }),
|
|
22231
|
+
layout && /* @__PURE__ */ jsx(
|
|
22232
|
+
Bubble,
|
|
22233
|
+
{
|
|
22234
|
+
layout,
|
|
22235
|
+
text,
|
|
22236
|
+
stepIndex,
|
|
22237
|
+
stepCount: steps.length,
|
|
22238
|
+
nextLabel: options.nextLabel ?? DEFAULT_NEXT_LABEL,
|
|
22239
|
+
onSpeak: options.onSpeak && (options.canSpeak?.(step.id) ?? true) ? () => optionsRef.current.onSpeak?.(step.id, textRef.current) : void 0,
|
|
22240
|
+
onNext: next,
|
|
22241
|
+
nextRef
|
|
22242
|
+
}
|
|
22243
|
+
)
|
|
22244
|
+
] }),
|
|
22245
|
+
document.body
|
|
22246
|
+
);
|
|
22247
|
+
};
|
|
22248
|
+
const Hand = ({ target, tapKey }) => {
|
|
22249
|
+
const c = centerOf(target);
|
|
22250
|
+
return /* @__PURE__ */ jsx(
|
|
22251
|
+
"div",
|
|
22252
|
+
{
|
|
22253
|
+
className: "met-tutorial-hand",
|
|
22254
|
+
style: {
|
|
22255
|
+
width: HAND_W,
|
|
22256
|
+
height: HAND_H,
|
|
22257
|
+
transform: `translate(${c.x - TUTORIAL_HAND_TIP.x * HAND_W}px, ${c.y - TUTORIAL_HAND_TIP.y * HAND_H}px)`
|
|
22258
|
+
},
|
|
22259
|
+
children: /* @__PURE__ */ jsxs("div", { className: "met-tutorial-hand-press", children: [
|
|
22260
|
+
/* @__PURE__ */ jsx(
|
|
22261
|
+
"span",
|
|
22262
|
+
{
|
|
22263
|
+
className: "met-tutorial-ripple",
|
|
22264
|
+
style: { left: TUTORIAL_HAND_TIP.x * HAND_W, top: TUTORIAL_HAND_TIP.y * HAND_H }
|
|
22265
|
+
}
|
|
22266
|
+
),
|
|
22267
|
+
/* @__PURE__ */ jsx(LionHand, {})
|
|
22268
|
+
] }, tapKey)
|
|
22269
|
+
}
|
|
22270
|
+
);
|
|
22271
|
+
};
|
|
22272
|
+
const Bubble = ({ layout, text, stepIndex, stepCount, nextLabel, onSpeak, onNext, nextRef }) => {
|
|
22273
|
+
const { anchor, root, vw, vh } = layout;
|
|
22274
|
+
const clampLeft = (x) => Math.min(Math.max(EDGE, x), Math.max(EDGE, vw - BUBBLE_W - EDGE));
|
|
22275
|
+
const style = anchor ? { left: clampLeft(anchor.x), bottom: Math.max(EDGE, vh - anchor.y + BUBBLE_GAP), width: BUBBLE_W } : { left: clampLeft((root?.x ?? 0) + EDGE * 2), top: (root?.y ?? 0) + EDGE * 2, width: BUBBLE_W };
|
|
22276
|
+
return /* @__PURE__ */ jsxs("div", { className: `met-tutorial-bubble${anchor ? " met-tutorial-bubble--anchored" : ""}`, style, children: [
|
|
22277
|
+
/* @__PURE__ */ jsx("p", { className: "met-tutorial-text", dir: "auto", children: text }),
|
|
22278
|
+
/* @__PURE__ */ jsxs("div", { className: "met-tutorial-actions", children: [
|
|
22279
|
+
onSpeak && /* @__PURE__ */ jsx("button", { type: "button", className: "met-tutorial-speak", "aria-label": "🔊", onClick: onSpeak, children: /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", width: "24", height: "24", "aria-hidden": "true", children: [
|
|
22280
|
+
/* @__PURE__ */ jsx("path", { d: "M4 9v6h4l5 4V5L8 9H4z", fill: "currentColor" }),
|
|
22281
|
+
/* @__PURE__ */ jsx("path", { d: "M16 8.5a5 5 0 0 1 0 7M18.5 6a8.5 8.5 0 0 1 0 12", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" })
|
|
22282
|
+
] }) }),
|
|
22283
|
+
/* @__PURE__ */ jsx("button", { ref: nextRef, type: "button", className: "met-tutorial-next", onClick: onNext, children: nextLabel })
|
|
22284
|
+
] }),
|
|
22285
|
+
stepCount > 1 && /* @__PURE__ */ jsx("div", { className: "met-tutorial-dots", "aria-hidden": "true", children: Array.from({ length: stepCount }, (_, i) => /* @__PURE__ */ jsx("span", { className: i === stepIndex ? "is-current" : i < stepIndex ? "is-done" : "" }, i)) })
|
|
22286
|
+
] });
|
|
22287
|
+
};
|
|
22288
|
+
const DEMO_KEYS = ["2", "5", "8"];
|
|
22289
|
+
const DEFAULT_SLOTS = 3;
|
|
22290
|
+
const MAX_SLOTS = 6;
|
|
22291
|
+
const STAND_IN_CHECK_SIZE = 52;
|
|
22292
|
+
const STAND_IN_CHECK_GAP = 20;
|
|
22293
|
+
function keypad(ctx) {
|
|
22294
|
+
return ctx.root.querySelector('[data-tutorial="keypad"]');
|
|
22295
|
+
}
|
|
22296
|
+
function key(ctx, k) {
|
|
22297
|
+
return ctx.root.querySelector(`[data-tutorial="keypad"] [data-key="${k}"]`);
|
|
22298
|
+
}
|
|
22299
|
+
function answerBox(ctx) {
|
|
22300
|
+
const inputs = getTextInputs(ctx.root);
|
|
22301
|
+
return inputs.find(canTakeInput) ?? inputs[0] ?? null;
|
|
22302
|
+
}
|
|
22303
|
+
function answerSlots(ctx) {
|
|
22304
|
+
const id = answerBox(ctx)?.id.replace(/-input$/, "");
|
|
22305
|
+
const max = id ? getMaxInputLength(ctx.json, id) : void 0;
|
|
22306
|
+
return Math.min(MAX_SLOTS, Math.max(1, max ?? DEFAULT_SLOTS));
|
|
22307
|
+
}
|
|
22308
|
+
function checkTarget(ctx) {
|
|
22309
|
+
if (ctx.submitTarget) return ctx.submitTarget;
|
|
22310
|
+
const box = answerBox(ctx)?.getBoundingClientRect();
|
|
22311
|
+
if (!box) return null;
|
|
22312
|
+
return new DOMRect(
|
|
22313
|
+
box.right + STAND_IN_CHECK_GAP,
|
|
22314
|
+
box.top + box.height / 2 - STAND_IN_CHECK_SIZE / 2,
|
|
22315
|
+
STAND_IN_CHECK_SIZE,
|
|
22316
|
+
STAND_IN_CHECK_SIZE
|
|
22317
|
+
);
|
|
22318
|
+
}
|
|
22319
|
+
const demoKeys = (ctx) => DEMO_KEYS.map((k) => key(ctx, k));
|
|
22320
|
+
const StandInFill = ({ box, slots, filled }) => /* @__PURE__ */ jsx(
|
|
22321
|
+
"div",
|
|
22322
|
+
{
|
|
22323
|
+
className: "met-tutorial-fill",
|
|
22324
|
+
style: { left: box.x, top: box.y, width: box.width, height: box.height },
|
|
22325
|
+
children: Array.from({ length: slots }, (_, i) => /* @__PURE__ */ jsx("span", { className: i < filled ? "met-tutorial-fill-dot" : "met-tutorial-fill-slot", children: i < filled ? "●" : "_" }, i))
|
|
22326
|
+
}
|
|
22327
|
+
);
|
|
22328
|
+
const center = (r) => ({ x: r.x + r.width / 2, y: r.y + r.height / 2 });
|
|
22329
|
+
const HopScene = ({ ctx, spotlight, taps, tapCount }) => {
|
|
22330
|
+
const box = spotlight[1];
|
|
22331
|
+
if (!box) return null;
|
|
22332
|
+
const slots = answerSlots(ctx);
|
|
22333
|
+
const filled = tapCount === 0 ? 0 : (tapCount - 1) % slots + 1;
|
|
22334
|
+
const from = tapCount > 0 ? taps[(tapCount - 1) % taps.length] : null;
|
|
22335
|
+
let arc = null;
|
|
22336
|
+
if (from) {
|
|
22337
|
+
const a = center(from);
|
|
22338
|
+
const b = center(box);
|
|
22339
|
+
const lift = Math.max(40, Math.hypot(b.x - a.x, b.y - a.y) * 0.35);
|
|
22340
|
+
arc = `M ${a.x} ${a.y} Q ${(a.x + b.x) / 2} ${Math.min(a.y, b.y) - lift} ${b.x} ${b.y}`;
|
|
22341
|
+
}
|
|
22342
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
22343
|
+
arc && /* @__PURE__ */ jsx("svg", { className: "met-tutorial-arc", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: arc }) }),
|
|
22344
|
+
arc && /* @__PURE__ */ jsx("div", { className: "met-tutorial-hop", style: { offsetPath: `path('${arc}')` } }, tapCount),
|
|
22345
|
+
/* @__PURE__ */ jsx(StandInFill, { box, slots, filled })
|
|
22346
|
+
] });
|
|
22347
|
+
};
|
|
22348
|
+
const CheckScene = ({ ctx, spotlight }) => {
|
|
22349
|
+
const box = spotlight[0];
|
|
22350
|
+
const check = spotlight[1];
|
|
22351
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
22352
|
+
box && /* @__PURE__ */ jsx(StandInFill, { box, slots: answerSlots(ctx), filled: answerSlots(ctx) }),
|
|
22353
|
+
check && !ctx.submitTarget && /* @__PURE__ */ jsx(
|
|
22354
|
+
"div",
|
|
22355
|
+
{
|
|
22356
|
+
className: "met-tutorial-stand-in-check",
|
|
22357
|
+
style: { left: check.x, top: check.y, width: check.width, height: check.height },
|
|
22358
|
+
children: /* @__PURE__ */ jsx("span", { children: "✓" })
|
|
22359
|
+
}
|
|
22360
|
+
)
|
|
22361
|
+
] });
|
|
22362
|
+
};
|
|
22363
|
+
const keypadTutorial = {
|
|
22364
|
+
id: "keypad",
|
|
22365
|
+
appliesTo: (json) => json.widgets[NUMERIC_KEYPAD_WIDGET_ID]?.type === JSON_TYPES.NUMERIC_KEYPAD,
|
|
22366
|
+
steps: [
|
|
22367
|
+
{
|
|
22368
|
+
id: "keypad.tap-number",
|
|
22369
|
+
text: "اضغط على رقم",
|
|
22370
|
+
spotlight: (ctx) => [keypad(ctx), answerBox(ctx)],
|
|
22371
|
+
taps: demoKeys
|
|
22372
|
+
},
|
|
22373
|
+
{
|
|
22374
|
+
id: "keypad.hop",
|
|
22375
|
+
text: "يظهر الرقم في الخانة",
|
|
22376
|
+
spotlight: (ctx) => [keypad(ctx), answerBox(ctx)],
|
|
22377
|
+
taps: demoKeys,
|
|
22378
|
+
Scene: HopScene
|
|
22379
|
+
},
|
|
22380
|
+
{
|
|
22381
|
+
id: "keypad.tap-check",
|
|
22382
|
+
text: "اضغط على زر التحقق",
|
|
22383
|
+
spotlight: (ctx) => [answerBox(ctx), checkTarget(ctx)],
|
|
22384
|
+
taps: (ctx) => [checkTarget(ctx)],
|
|
22385
|
+
Scene: CheckScene
|
|
22386
|
+
}
|
|
22387
|
+
]
|
|
22388
|
+
};
|
|
22389
|
+
const WIDGET_TUTORIALS = [keypadTutorial];
|
|
22390
|
+
function findWidgetTutorial(json) {
|
|
22391
|
+
return WIDGET_TUTORIALS.find((t) => t.appliesTo(json)) ?? null;
|
|
22392
|
+
}
|
|
21186
22393
|
const TOOL_ASPECT_RATIO = {
|
|
21187
22394
|
ruler: RULER_ASPECT_RATIO,
|
|
21188
22395
|
protractor: PROTRACTOR_ASPECT_RATIO,
|
|
@@ -21221,6 +22428,11 @@ function buildInitialValues(json) {
|
|
|
21221
22428
|
function toBaseId(widgetId) {
|
|
21222
22429
|
return parseFractionKey(widgetId)?.widgetId ?? widgetId;
|
|
21223
22430
|
}
|
|
22431
|
+
function hasJustFilledBox(json, key2, previous, value) {
|
|
22432
|
+
const maxLength = getMaxInputLength(json, key2);
|
|
22433
|
+
if (maxLength === void 0) return false;
|
|
22434
|
+
return String(previous ?? "").length < maxLength && String(value).length >= maxLength;
|
|
22435
|
+
}
|
|
21224
22436
|
function buildInitialValuesSignature(json) {
|
|
21225
22437
|
return JSON.stringify({
|
|
21226
22438
|
widgets: json.widgets,
|
|
@@ -21270,6 +22482,27 @@ function buildRevealedValues(json) {
|
|
|
21270
22482
|
}
|
|
21271
22483
|
return revealed;
|
|
21272
22484
|
}
|
|
22485
|
+
function buildRevealedDropSources(json, revealedValues) {
|
|
22486
|
+
const pool = [];
|
|
22487
|
+
const bank = json.widgets[DRAG_OPTIONS_WIDGET_ID];
|
|
22488
|
+
if (bank?.type === JSON_TYPES.DRAG_OPTIONS) {
|
|
22489
|
+
bank.options.forEach((value, idx) => pool.push({ optionId: `drag-option-${idx}`, value: String(value) }));
|
|
22490
|
+
}
|
|
22491
|
+
for (const [widgetId, widget] of Object.entries(json.widgets)) {
|
|
22492
|
+
if (widget.type === JSON_TYPES.INLINE_DRAG_OPTION) pool.push({ optionId: widgetId, value: widget.value });
|
|
22493
|
+
}
|
|
22494
|
+
const sources = /* @__PURE__ */ new Map();
|
|
22495
|
+
if (pool.length === 0) return sources;
|
|
22496
|
+
const used = /* @__PURE__ */ new Set();
|
|
22497
|
+
for (const [inputId, value] of Object.entries(revealedValues)) {
|
|
22498
|
+
if (typeof value !== "string" || value === "") continue;
|
|
22499
|
+
const match = pool.find((o) => !used.has(o.optionId) && normalize(o.value) === normalize(value));
|
|
22500
|
+
if (!match) continue;
|
|
22501
|
+
used.add(match.optionId);
|
|
22502
|
+
sources.set(inputId, match.optionId);
|
|
22503
|
+
}
|
|
22504
|
+
return sources;
|
|
22505
|
+
}
|
|
21273
22506
|
const ExerciseRenderer = forwardRef(({
|
|
21274
22507
|
json,
|
|
21275
22508
|
onSubmit,
|
|
@@ -21292,6 +22525,8 @@ const ExerciseRenderer = forwardRef(({
|
|
|
21292
22525
|
const rootRef = useRef(null);
|
|
21293
22526
|
const [resetKey, setResetKey] = useState(0);
|
|
21294
22527
|
const [revealed, setRevealed] = useState(false);
|
|
22528
|
+
const tutorial = useMemo(() => findWidgetTutorial(json), [json]);
|
|
22529
|
+
const [tutorialRun, setTutorialRun] = useState(null);
|
|
21295
22530
|
const themeStyle = useMemo(() => {
|
|
21296
22531
|
const style = {};
|
|
21297
22532
|
if (theme?.correct) style["--exercise-correct"] = theme.correct;
|
|
@@ -21312,6 +22547,7 @@ const ExerciseRenderer = forwardRef(({
|
|
|
21312
22547
|
setResetKey((k) => k + 1);
|
|
21313
22548
|
setLoadedValuesSignature(initialValuesSignature);
|
|
21314
22549
|
touchedRef.current = /* @__PURE__ */ new Set();
|
|
22550
|
+
setTutorialRun(null);
|
|
21315
22551
|
}, [json, initialValuesSignature, loadedValuesSignature]);
|
|
21316
22552
|
const locked = validationDetails !== null || externallyLocked;
|
|
21317
22553
|
const lastResultRef = useRef(null);
|
|
@@ -21395,7 +22631,7 @@ const ExerciseRenderer = forwardRef(({
|
|
|
21395
22631
|
const next = { ...valuesRef.current, ...revealedValues };
|
|
21396
22632
|
valuesRef.current = next;
|
|
21397
22633
|
setValues(next);
|
|
21398
|
-
setDroppedSources(
|
|
22634
|
+
setDroppedSources(buildRevealedDropSources(json, revealedValues));
|
|
21399
22635
|
setValidationDetails(details);
|
|
21400
22636
|
setRevealed(true);
|
|
21401
22637
|
setResetKey((k) => k + 1);
|
|
@@ -21403,20 +22639,29 @@ const ExerciseRenderer = forwardRef(({
|
|
|
21403
22639
|
focusFirstInput: () => {
|
|
21404
22640
|
const container = rootRef.current ?? document.querySelector(".math-exercise-renderer");
|
|
21405
22641
|
if (!container) return;
|
|
21406
|
-
const candidates =
|
|
21407
|
-
container.querySelectorAll('input[type="text"], input[type="number"], textarea, select')
|
|
21408
|
-
);
|
|
21409
|
-
const canTakeInput = (el) => !el.readOnly && !el.disabled;
|
|
22642
|
+
const candidates = getTextInputs(container);
|
|
21410
22643
|
const target = candidates.find(canTakeInput) ?? (locked ? void 0 : candidates[0]);
|
|
21411
22644
|
target?.focus();
|
|
21412
|
-
}
|
|
21413
|
-
|
|
22645
|
+
},
|
|
22646
|
+
hasTutorial: () => tutorial !== null,
|
|
22647
|
+
startTutorial: (options = {}) => {
|
|
22648
|
+
if (!tutorial || !rootRef.current) return false;
|
|
22649
|
+
setTutorialRun((prev) => ({ options, run: (prev?.run ?? 0) + 1 }));
|
|
22650
|
+
return true;
|
|
22651
|
+
},
|
|
22652
|
+
stopTutorial: () => setTutorialRun(null)
|
|
22653
|
+
}), [json, values, onSubmit, playSound, locked, initialValuesSignature, tutorial]);
|
|
22654
|
+
const focusNextInput = (currentKey) => {
|
|
22655
|
+
if (!rootRef.current) return;
|
|
22656
|
+
findNextInput(rootRef.current, currentKey)?.focus();
|
|
22657
|
+
};
|
|
21414
22658
|
const handleChange = (widgetId, value, fromDrop = false, sourceOptionId, silent = false) => {
|
|
21415
22659
|
if (locked) return;
|
|
21416
22660
|
if (!silent) {
|
|
21417
22661
|
setRevealed(false);
|
|
21418
22662
|
touchedRef.current.add(toBaseId(widgetId));
|
|
21419
22663
|
}
|
|
22664
|
+
const previous = valuesRef.current[widgetId];
|
|
21420
22665
|
const next = { ...valuesRef.current, [widgetId]: value };
|
|
21421
22666
|
valuesRef.current = next;
|
|
21422
22667
|
setValues(next);
|
|
@@ -21430,6 +22675,9 @@ const ExerciseRenderer = forwardRef(({
|
|
|
21430
22675
|
return next2;
|
|
21431
22676
|
});
|
|
21432
22677
|
onChange?.(next);
|
|
22678
|
+
if (!fromDrop && !silent && hasJustFilledBox(json, widgetId, previous, value)) {
|
|
22679
|
+
focusNextInput(widgetId);
|
|
22680
|
+
}
|
|
21433
22681
|
};
|
|
21434
22682
|
const handleSubmit = () => {
|
|
21435
22683
|
runSubmit();
|
|
@@ -21544,7 +22792,7 @@ const ExerciseRenderer = forwardRef(({
|
|
|
21544
22792
|
const rulerWidget = json.widgets[RULER_WIDGET_ID];
|
|
21545
22793
|
const protractorWidget = json.widgets[PROTRACTOR_WIDGET_ID];
|
|
21546
22794
|
const setSquareWidget = json.widgets[SET_SQUARE_WIDGET_ID];
|
|
21547
|
-
const numericKeypadWidget = json.widgets[
|
|
22795
|
+
const numericKeypadWidget = json.widgets[NUMERIC_KEYPAD_WIDGET_ID];
|
|
21548
22796
|
const { tools, toolsMinHeight } = useMemo(() => {
|
|
21549
22797
|
const list = [];
|
|
21550
22798
|
const PAD = 12;
|
|
@@ -21563,7 +22811,7 @@ const ExerciseRenderer = forwardRef(({
|
|
|
21563
22811
|
if (setSquareWidget) add2("set-square-1", "set-square", Math.round(getSize(setSquareWidget) * 160));
|
|
21564
22812
|
return { tools: list, toolsMinHeight: maxH };
|
|
21565
22813
|
}, [rulerWidget, protractorWidget, setSquareWidget]);
|
|
21566
|
-
return /* @__PURE__ */ jsx(SoundPlayerProvider, { play: playSound, children: /* @__PURE__ */ jsx(ExerciseLockProvider, { locked, children: /* @__PURE__ */ jsx(ExerciseDndProvider, { droppedSources, children: /* @__PURE__ */ jsxs("div", { ref: rootRef, className: `math-exercise-renderer ${revealed ? "revealed" : ""} ${locked ? "locked" : ""} ${className}`, style: themeStyle, children: [
|
|
22814
|
+
return /* @__PURE__ */ jsx(SoundPlayerProvider, { play: playSound, children: /* @__PURE__ */ jsx(ExerciseLockProvider, { locked, children: /* @__PURE__ */ jsx(ExerciseDndProvider, { droppedSources, exerciseKey: loadedValuesSignature, children: /* @__PURE__ */ jsxs("div", { ref: rootRef, className: `math-exercise-renderer ${revealed ? "revealed" : ""} ${locked ? "locked" : ""} ${className}`, style: themeStyle, children: [
|
|
21567
22815
|
/* @__PURE__ */ jsxs(
|
|
21568
22816
|
"div",
|
|
21569
22817
|
{
|
|
@@ -21659,7 +22907,21 @@ const ExerciseRenderer = forwardRef(({
|
|
|
21659
22907
|
}
|
|
21660
22908
|
}
|
|
21661
22909
|
) }),
|
|
21662
|
-
showSubmitButton && /* @__PURE__ */ jsx("button", { className: "exercise-submit-btn", disabled: locked, onClick: handleSubmit, children: "Check Answer" })
|
|
22910
|
+
showSubmitButton && /* @__PURE__ */ jsx("button", { className: "exercise-submit-btn", disabled: locked, onClick: handleSubmit, children: "Check Answer" }),
|
|
22911
|
+
tutorial && tutorialRun && rootRef.current && /* @__PURE__ */ jsx(
|
|
22912
|
+
TutorialOverlay,
|
|
22913
|
+
{
|
|
22914
|
+
tutorial,
|
|
22915
|
+
root: rootRef.current,
|
|
22916
|
+
json,
|
|
22917
|
+
options: tutorialRun.options,
|
|
22918
|
+
onClose: (finished) => {
|
|
22919
|
+
setTutorialRun(null);
|
|
22920
|
+
if (finished) tutorialRun.options.onDone?.();
|
|
22921
|
+
}
|
|
22922
|
+
},
|
|
22923
|
+
tutorialRun.run
|
|
22924
|
+
)
|
|
21663
22925
|
] }) }) }) });
|
|
21664
22926
|
});
|
|
21665
22927
|
ExerciseRenderer.displayName = "ExerciseRenderer";
|
|
@@ -21728,7 +22990,7 @@ export {
|
|
|
21728
22990
|
isTableRowItem as Z,
|
|
21729
22991
|
isTableCellItem as _,
|
|
21730
22992
|
compileWithAST as a,
|
|
21731
|
-
|
|
22993
|
+
validateExerciseJSONSafe as a$,
|
|
21732
22994
|
isVerticalOperationItem as a0,
|
|
21733
22995
|
isClockItem as a1,
|
|
21734
22996
|
isRulerWidget as a2,
|
|
@@ -21747,25 +23009,25 @@ export {
|
|
|
21747
23009
|
ChartAxisJSONSchema as aF,
|
|
21748
23010
|
ChartBarJSONSchema as aG,
|
|
21749
23011
|
ChartPictoJSONSchema as aH,
|
|
21750
|
-
|
|
21751
|
-
|
|
21752
|
-
|
|
21753
|
-
|
|
21754
|
-
|
|
21755
|
-
|
|
21756
|
-
|
|
21757
|
-
|
|
21758
|
-
|
|
21759
|
-
|
|
21760
|
-
|
|
21761
|
-
|
|
21762
|
-
|
|
21763
|
-
|
|
21764
|
-
|
|
21765
|
-
|
|
21766
|
-
|
|
21767
|
-
|
|
21768
|
-
|
|
23012
|
+
ChartSeriesJSONSchema as aI,
|
|
23013
|
+
ChartSeriesBarJSONSchema as aJ,
|
|
23014
|
+
ChartGroupJSONSchema as aK,
|
|
23015
|
+
WidgetJSONSchema as aL,
|
|
23016
|
+
InlineInputJSONSchema as aM,
|
|
23017
|
+
DragOptionsJSONSchema as aN,
|
|
23018
|
+
RadioOptionsJSONSchema as aO,
|
|
23019
|
+
NumericKeypadJSONSchema as aP,
|
|
23020
|
+
FractionJSONSchema as aQ,
|
|
23021
|
+
MatcherWidgetJSONSchema as aR,
|
|
23022
|
+
PairMatcherWidgetJSONSchema as aS,
|
|
23023
|
+
GridSymmetryJSONSchema as aT,
|
|
23024
|
+
ChartBarWidgetJSONSchema as aU,
|
|
23025
|
+
ChartPictoWidgetJSONSchema as aV,
|
|
23026
|
+
RulerWidgetSchema as aW,
|
|
23027
|
+
ProtractorWidgetSchema as aX,
|
|
23028
|
+
SetSquareWidgetSchema as aY,
|
|
23029
|
+
GeometryJSONSchema as aZ,
|
|
23030
|
+
validateExerciseJSON as a_,
|
|
21769
23031
|
QuestionTextSchema as aa,
|
|
21770
23032
|
TextItemSchema as ab,
|
|
21771
23033
|
WidgetRefSchema as ac,
|
|
@@ -21793,178 +23055,196 @@ export {
|
|
|
21793
23055
|
VopSeparatorItemSchema as ay,
|
|
21794
23056
|
VopCellItemSchema as az,
|
|
21795
23057
|
tokenizeSafe as b,
|
|
21796
|
-
|
|
21797
|
-
|
|
21798
|
-
|
|
21799
|
-
|
|
21800
|
-
|
|
21801
|
-
|
|
21802
|
-
|
|
21803
|
-
|
|
21804
|
-
|
|
21805
|
-
|
|
21806
|
-
|
|
21807
|
-
|
|
21808
|
-
|
|
21809
|
-
|
|
21810
|
-
|
|
21811
|
-
|
|
21812
|
-
|
|
21813
|
-
|
|
21814
|
-
|
|
21815
|
-
|
|
21816
|
-
|
|
21817
|
-
|
|
21818
|
-
|
|
21819
|
-
|
|
21820
|
-
|
|
21821
|
-
|
|
21822
|
-
|
|
21823
|
-
|
|
21824
|
-
|
|
21825
|
-
|
|
21826
|
-
|
|
21827
|
-
|
|
21828
|
-
|
|
21829
|
-
|
|
21830
|
-
|
|
21831
|
-
|
|
21832
|
-
|
|
21833
|
-
|
|
21834
|
-
|
|
21835
|
-
|
|
21836
|
-
|
|
21837
|
-
|
|
21838
|
-
|
|
21839
|
-
|
|
21840
|
-
|
|
21841
|
-
|
|
21842
|
-
|
|
21843
|
-
|
|
21844
|
-
|
|
21845
|
-
|
|
21846
|
-
|
|
21847
|
-
|
|
21848
|
-
|
|
21849
|
-
|
|
21850
|
-
|
|
21851
|
-
|
|
21852
|
-
|
|
21853
|
-
|
|
21854
|
-
|
|
21855
|
-
|
|
21856
|
-
|
|
21857
|
-
|
|
21858
|
-
|
|
21859
|
-
|
|
23058
|
+
TextBlockSchema as b$,
|
|
23059
|
+
validateWidgetJSON as b0,
|
|
23060
|
+
validateWidgetJSONSafe as b1,
|
|
23061
|
+
EngineError as b2,
|
|
23062
|
+
LexerError as b3,
|
|
23063
|
+
ParseError as b4,
|
|
23064
|
+
TransformError as b5,
|
|
23065
|
+
ValidationError as b6,
|
|
23066
|
+
ExerciseRenderer as b7,
|
|
23067
|
+
LayoutRenderer as b8,
|
|
23068
|
+
WidgetRenderer as b9,
|
|
23069
|
+
CLOCK_MODES as bA,
|
|
23070
|
+
CLOCK_FORMATS as bB,
|
|
23071
|
+
CLOCK_TIME_REGEX as bC,
|
|
23072
|
+
ClockInlineSchema as bD,
|
|
23073
|
+
InlineNodeSchema as bE,
|
|
23074
|
+
createFreeText as bF,
|
|
23075
|
+
createSpace as bG,
|
|
23076
|
+
createHighlightedText as bH,
|
|
23077
|
+
createColorText as bI,
|
|
23078
|
+
createBoldText as bJ,
|
|
23079
|
+
createStringInput as bK,
|
|
23080
|
+
createNumericInput as bL,
|
|
23081
|
+
createImage as bM,
|
|
23082
|
+
createFraction as bN,
|
|
23083
|
+
createVisualFraction as bO,
|
|
23084
|
+
createInlineDragOption as bP,
|
|
23085
|
+
createDisplayClock as bQ,
|
|
23086
|
+
createInteractiveClock as bR,
|
|
23087
|
+
isFreeText as bS,
|
|
23088
|
+
isSpace as bT,
|
|
23089
|
+
isInlineInput as bU,
|
|
23090
|
+
isFraction as bV,
|
|
23091
|
+
isImage as bW,
|
|
23092
|
+
isClock as bX,
|
|
23093
|
+
isInlineNode as bY,
|
|
23094
|
+
isVisualFractionInline as bZ,
|
|
23095
|
+
isInlineDragOption as b_,
|
|
23096
|
+
renderExercise as ba,
|
|
23097
|
+
SoundProvider as bb,
|
|
23098
|
+
useSound as bc,
|
|
23099
|
+
ExerciseLockProvider as bd,
|
|
23100
|
+
WIDGET_TUTORIALS as be,
|
|
23101
|
+
findWidgetTutorial as bf,
|
|
23102
|
+
isPairMatcherWidget as bg,
|
|
23103
|
+
isMatcherWidget as bh,
|
|
23104
|
+
VISUAL_FRACTION_SHAPES as bi,
|
|
23105
|
+
VISUAL_FRACTION_SHAPE as bj,
|
|
23106
|
+
VISUAL_FRACTION_DEFAULT_SIZE as bk,
|
|
23107
|
+
VISUAL_FRACTION_MAX_DIVISIONS as bl,
|
|
23108
|
+
INLINE_INPUT_MAX_WIDTH as bm,
|
|
23109
|
+
InlineInputWidthSchema as bn,
|
|
23110
|
+
FreeTextInlineSchema as bo,
|
|
23111
|
+
SpaceInlineSchema as bp,
|
|
23112
|
+
HighlightedTextInlineSchema as bq,
|
|
23113
|
+
ColorTextInlineSchema as br,
|
|
23114
|
+
BoldTextInlineSchema as bs,
|
|
23115
|
+
InlineInputWidgetSchema as bt,
|
|
23116
|
+
FractionInlineSchema as bu,
|
|
23117
|
+
ImageInlineSchema as bv,
|
|
23118
|
+
VisualFractionInlineSchema as bw,
|
|
23119
|
+
validateVisualFractionCapacity as bx,
|
|
23120
|
+
InlineDragOptionSchema as by,
|
|
23121
|
+
CLOCK_TYPES as bz,
|
|
21860
23122
|
compile as c,
|
|
21861
|
-
|
|
21862
|
-
|
|
21863
|
-
|
|
21864
|
-
|
|
21865
|
-
|
|
21866
|
-
|
|
21867
|
-
|
|
21868
|
-
|
|
21869
|
-
|
|
21870
|
-
|
|
21871
|
-
|
|
21872
|
-
|
|
21873
|
-
|
|
21874
|
-
|
|
21875
|
-
|
|
21876
|
-
|
|
21877
|
-
|
|
21878
|
-
|
|
21879
|
-
|
|
21880
|
-
|
|
21881
|
-
|
|
21882
|
-
|
|
21883
|
-
|
|
21884
|
-
|
|
21885
|
-
|
|
21886
|
-
|
|
21887
|
-
|
|
21888
|
-
|
|
21889
|
-
|
|
21890
|
-
|
|
21891
|
-
|
|
21892
|
-
|
|
21893
|
-
|
|
21894
|
-
|
|
21895
|
-
|
|
21896
|
-
|
|
21897
|
-
|
|
21898
|
-
|
|
21899
|
-
|
|
21900
|
-
|
|
21901
|
-
|
|
21902
|
-
|
|
21903
|
-
|
|
21904
|
-
|
|
21905
|
-
|
|
21906
|
-
|
|
21907
|
-
|
|
21908
|
-
|
|
21909
|
-
|
|
21910
|
-
|
|
21911
|
-
|
|
21912
|
-
|
|
21913
|
-
|
|
21914
|
-
|
|
21915
|
-
|
|
21916
|
-
|
|
21917
|
-
|
|
21918
|
-
|
|
21919
|
-
|
|
21920
|
-
|
|
21921
|
-
|
|
21922
|
-
|
|
21923
|
-
|
|
21924
|
-
|
|
23123
|
+
createVopEmptyCell as c$,
|
|
23124
|
+
LineBlockSchema as c0,
|
|
23125
|
+
createTextBlock as c1,
|
|
23126
|
+
createLineBlock as c2,
|
|
23127
|
+
isTextBlock as c3,
|
|
23128
|
+
isLineBlock as c4,
|
|
23129
|
+
DisplayInlineNodeSchema as c5,
|
|
23130
|
+
MatcherItemSchema as c6,
|
|
23131
|
+
createMatcherItem as c7,
|
|
23132
|
+
isMatcherItem as c8,
|
|
23133
|
+
MatcherRowSchema as c9,
|
|
23134
|
+
ExpressionValidationSchema as cA,
|
|
23135
|
+
MixedValidationSchema as cB,
|
|
23136
|
+
ValidationNodeSchema as cC,
|
|
23137
|
+
createSimpleValidation as cD,
|
|
23138
|
+
createExpressionValidation as cE,
|
|
23139
|
+
createMixedValidation as cF,
|
|
23140
|
+
isSimpleValidation as cG,
|
|
23141
|
+
isExpressionValidation as cH,
|
|
23142
|
+
isMixedValidation as cI,
|
|
23143
|
+
isValidationNode as cJ,
|
|
23144
|
+
ContentBlockSchema as cK,
|
|
23145
|
+
ExerciseDocumentSchema as cL,
|
|
23146
|
+
createExerciseDocument as cM,
|
|
23147
|
+
isExerciseDocument as cN,
|
|
23148
|
+
VOP_OPERATORS as cO,
|
|
23149
|
+
normalizeVopOperator as cP,
|
|
23150
|
+
VopDigitCellSchema as cQ,
|
|
23151
|
+
VopLabelCellSchema as cR,
|
|
23152
|
+
VopInputCellSchema as cS,
|
|
23153
|
+
VopEmptyCellSchema as cT,
|
|
23154
|
+
VopColorCellSchema as cU,
|
|
23155
|
+
VopBoldCellSchema as cV,
|
|
23156
|
+
VopHighlightCellSchema as cW,
|
|
23157
|
+
VopCellSchema as cX,
|
|
23158
|
+
createVopDigitCell as cY,
|
|
23159
|
+
createVopInputCell as cZ,
|
|
23160
|
+
createVopLabelCell as c_,
|
|
23161
|
+
createMatcherRow as ca,
|
|
23162
|
+
isMatcherRow as cb,
|
|
23163
|
+
MatcherBlockSchema as cc,
|
|
23164
|
+
createMatcherBlock as cd,
|
|
23165
|
+
isMatcherBlock as ce,
|
|
23166
|
+
PairMatcherBlockSchema as cf,
|
|
23167
|
+
createPairMatcherBlock as cg,
|
|
23168
|
+
isPairMatcherBlock as ch,
|
|
23169
|
+
DragOptionsToolSchema as ci,
|
|
23170
|
+
NumericKeypadToolSchema as cj,
|
|
23171
|
+
RulerToolSchema as ck,
|
|
23172
|
+
ProtractorToolSchema as cl,
|
|
23173
|
+
SetSquareToolSchema as cm,
|
|
23174
|
+
ToolNodeSchema as cn,
|
|
23175
|
+
createDragOptions as co,
|
|
23176
|
+
createNumericKeypad as cp,
|
|
23177
|
+
createRulerTool as cq,
|
|
23178
|
+
createProtractorTool as cr,
|
|
23179
|
+
createSetSquareTool as cs,
|
|
23180
|
+
isDragOptions as ct,
|
|
23181
|
+
isNumericKeypad as cu,
|
|
23182
|
+
isRulerTool as cv,
|
|
23183
|
+
isProtractorTool as cw,
|
|
23184
|
+
isSetSquareTool as cx,
|
|
23185
|
+
isToolNode as cy,
|
|
23186
|
+
SimpleValidationSchema as cz,
|
|
21925
23187
|
parse as d,
|
|
21926
|
-
|
|
21927
|
-
|
|
21928
|
-
|
|
21929
|
-
|
|
21930
|
-
|
|
21931
|
-
|
|
21932
|
-
|
|
21933
|
-
|
|
21934
|
-
|
|
21935
|
-
|
|
21936
|
-
|
|
21937
|
-
|
|
21938
|
-
|
|
21939
|
-
|
|
21940
|
-
|
|
21941
|
-
|
|
21942
|
-
|
|
21943
|
-
|
|
21944
|
-
|
|
21945
|
-
|
|
21946
|
-
|
|
21947
|
-
|
|
21948
|
-
|
|
21949
|
-
|
|
21950
|
-
|
|
21951
|
-
|
|
21952
|
-
|
|
21953
|
-
|
|
21954
|
-
|
|
21955
|
-
|
|
21956
|
-
|
|
21957
|
-
|
|
21958
|
-
|
|
21959
|
-
|
|
21960
|
-
|
|
21961
|
-
|
|
21962
|
-
|
|
21963
|
-
|
|
21964
|
-
|
|
21965
|
-
|
|
21966
|
-
|
|
21967
|
-
|
|
23188
|
+
createVopColorCell as d0,
|
|
23189
|
+
createVopBoldCell as d1,
|
|
23190
|
+
createVopHighlightCell as d2,
|
|
23191
|
+
VopRowSchema as d3,
|
|
23192
|
+
createVopRow as d4,
|
|
23193
|
+
VopCarryRowSchema as d5,
|
|
23194
|
+
createVopCarryRow as d6,
|
|
23195
|
+
VopSeparatorSchema as d7,
|
|
23196
|
+
createVopSeparator as d8,
|
|
23197
|
+
VerticalOperationBlockSchema as d9,
|
|
23198
|
+
isChartAxis as dA,
|
|
23199
|
+
ChartPictoSchema as dB,
|
|
23200
|
+
createChartPicto as dC,
|
|
23201
|
+
isChartPicto as dD,
|
|
23202
|
+
ChartBarSchema as dE,
|
|
23203
|
+
createChartBar as dF,
|
|
23204
|
+
isChartBar as dG,
|
|
23205
|
+
ChartSeriesSchema as dH,
|
|
23206
|
+
createChartSeries as dI,
|
|
23207
|
+
isChartSeries as dJ,
|
|
23208
|
+
ChartSeriesBarSchema as dK,
|
|
23209
|
+
createChartSeriesBar as dL,
|
|
23210
|
+
isChartSeriesBar as dM,
|
|
23211
|
+
ChartGroupSchema as dN,
|
|
23212
|
+
createChartGroup as dO,
|
|
23213
|
+
isChartGroup as dP,
|
|
23214
|
+
alignGroupBarsToSeries as dQ,
|
|
23215
|
+
ChartBlockSchema as dR,
|
|
23216
|
+
resolveChartAxes as dS,
|
|
23217
|
+
validateUniqueIds as dT,
|
|
23218
|
+
createChartBlock as dU,
|
|
23219
|
+
isChartBlock as dV,
|
|
23220
|
+
GeometryBlockSchema as dW,
|
|
23221
|
+
createGeometryBlock as dX,
|
|
23222
|
+
isGeometryBlock as dY,
|
|
23223
|
+
createVerticalOperationBlock as da,
|
|
23224
|
+
isVopCell as db,
|
|
23225
|
+
isVopRow as dc,
|
|
23226
|
+
isVopCarryRow as dd,
|
|
23227
|
+
isVopSeparator as de,
|
|
23228
|
+
isVerticalOperationBlock as df,
|
|
23229
|
+
GRID_SYMMETRY_AXES as dg,
|
|
23230
|
+
GRID_SYMMETRY_INTERACTIVE_AREAS as dh,
|
|
23231
|
+
GRID_SYMMETRY_MAX_ROWS as di,
|
|
23232
|
+
GRID_SYMMETRY_MAX_COLS as dj,
|
|
23233
|
+
GRID_SYMMETRY_MIN_CELL_SIZE as dk,
|
|
23234
|
+
GRID_SYMMETRY_MAX_CELL_SIZE as dl,
|
|
23235
|
+
isCellInInteractiveArea as dm,
|
|
23236
|
+
reflectGridSymmetryCell as dn,
|
|
23237
|
+
computeGridSymmetryExpectedCells as dp,
|
|
23238
|
+
computeGridSymmetryCellsToFill as dq,
|
|
23239
|
+
GridSymmetryBlockSchema as dr,
|
|
23240
|
+
createGridSymmetryBlock as ds,
|
|
23241
|
+
isGridSymmetryBlock as dt,
|
|
23242
|
+
CHART_TYPES as du,
|
|
23243
|
+
CHART_AXIS_DEFAULTS as dv,
|
|
23244
|
+
CHART_MAX_AXIS_TICKS as dw,
|
|
23245
|
+
CHART_MAX_PICTO_SLOTS as dx,
|
|
23246
|
+
ChartAxisSchema as dy,
|
|
23247
|
+
createChartAxis as dz,
|
|
21968
23248
|
parseSafe as e,
|
|
21969
23249
|
toAST as f,
|
|
21970
23250
|
getFractionKey as g,
|
|
@@ -21988,4 +23268,4 @@ export {
|
|
|
21988
23268
|
extractVariables as y,
|
|
21989
23269
|
validateBooleanExpression as z
|
|
21990
23270
|
};
|
|
21991
|
-
//# sourceMappingURL=index-
|
|
23271
|
+
//# sourceMappingURL=index-6z_8j1tM.mjs.map
|