@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
|
@@ -101,6 +101,12 @@ const AST_TYPES = {
|
|
|
101
101
|
CHART_BAR: "ChartBar",
|
|
102
102
|
/** A single pictograph item inside a CHART[type="pictograph"] block */
|
|
103
103
|
CHART_PICTO: "ChartPicto",
|
|
104
|
+
/** A named series (color + legend label) inside a CHART[type="grouped-bar"] block */
|
|
105
|
+
CHART_SERIES: "ChartSeries",
|
|
106
|
+
/** One category cluster inside a CHART[type="grouped-bar"] block */
|
|
107
|
+
CHART_GROUP: "ChartGroup",
|
|
108
|
+
/** A single bar inside a GROUP, referencing a declared SERIES by id */
|
|
109
|
+
CHART_SERIES_BAR: "ChartSeriesBar",
|
|
104
110
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
105
111
|
// Tools
|
|
106
112
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -204,6 +210,10 @@ const JSON_TYPES = {
|
|
|
204
210
|
CHART_BAR: "chart-bar",
|
|
205
211
|
/** A single pictograph item rendered inside a chart */
|
|
206
212
|
CHART_PICTO: "chart-picto",
|
|
213
|
+
/** A single bar rendered inside a grouped-bar chart's GROUP — distinct from
|
|
214
|
+
* CHART_BAR so the JSON self-identifies which shape it is (has `seriesId`),
|
|
215
|
+
* independent of the widget type used for validation. */
|
|
216
|
+
CHART_SERIES_BAR: "chart-series-bar",
|
|
207
217
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
208
218
|
/** Inline input widget (string or numeric) */
|
|
209
219
|
INLINE_INPUT: "inline-input",
|
|
@@ -231,6 +241,7 @@ const JSON_TYPES = {
|
|
|
231
241
|
GEOMETRY: "geometry"
|
|
232
242
|
};
|
|
233
243
|
const DRAG_OPTIONS_WIDGET_ID = "_drag_options";
|
|
244
|
+
const NUMERIC_KEYPAD_WIDGET_ID = "_numeric_keypad";
|
|
234
245
|
const RULER_WIDGET_ID = "_ruler";
|
|
235
246
|
const PROTRACTOR_WIDGET_ID = "_protractor";
|
|
236
247
|
const SET_SQUARE_WIDGET_ID = "_set_square";
|
|
@@ -263,7 +274,8 @@ const INPUT_TYPES = {
|
|
|
263
274
|
/** Numeric input (digits only) */
|
|
264
275
|
NUMERIC: "numeric"
|
|
265
276
|
};
|
|
266
|
-
const NUMBER_LINE_STYLES = ["arrow-right", "arrow-left", "arrow-both", "line"];
|
|
277
|
+
const NUMBER_LINE_STYLES = ["arrow-right", "arrow-left", "arrow-both", "line", "line-left"];
|
|
278
|
+
const DEFAULT_NUMBER_LINE_STYLE = "line-left";
|
|
267
279
|
const INTERNAL_PRECISION = 9;
|
|
268
280
|
const DISPLAY_PRECISION = 2;
|
|
269
281
|
const SIDE_PAD = 7;
|
|
@@ -437,7 +449,7 @@ const MATCHER = chevrotain.createToken({
|
|
|
437
449
|
});
|
|
438
450
|
const GROUP = chevrotain.createToken({
|
|
439
451
|
name: "GROUP",
|
|
440
|
-
pattern: /GROUP/
|
|
452
|
+
pattern: /GROUP(?=\s*[[{])/
|
|
441
453
|
});
|
|
442
454
|
const ITEM = chevrotain.createToken({
|
|
443
455
|
name: "ITEM",
|
|
@@ -487,6 +499,18 @@ const PICTO = chevrotain.createToken({
|
|
|
487
499
|
name: "PICTO",
|
|
488
500
|
pattern: /PICTO/
|
|
489
501
|
});
|
|
502
|
+
const LABEL = chevrotain.createToken({
|
|
503
|
+
name: "LABEL",
|
|
504
|
+
pattern: /LABEL(?=\s*\{)/
|
|
505
|
+
});
|
|
506
|
+
const SERIES = chevrotain.createToken({
|
|
507
|
+
name: "SERIES",
|
|
508
|
+
pattern: /SERIES(?=\s*\[)/
|
|
509
|
+
});
|
|
510
|
+
const SERIES_BAR = chevrotain.createToken({
|
|
511
|
+
name: "SERIES_BAR",
|
|
512
|
+
pattern: /SERIES_BAR(?=\s*\[)/
|
|
513
|
+
});
|
|
490
514
|
const SEP = chevrotain.createToken({
|
|
491
515
|
name: "SEP",
|
|
492
516
|
pattern: /SEP(?![a-zA-Z0-9_])/
|
|
@@ -685,6 +709,9 @@ const allTokens = [
|
|
|
685
709
|
Y_AXIS,
|
|
686
710
|
BAR,
|
|
687
711
|
PICTO,
|
|
712
|
+
LABEL,
|
|
713
|
+
SERIES_BAR,
|
|
714
|
+
SERIES,
|
|
688
715
|
SEP,
|
|
689
716
|
CARRY,
|
|
690
717
|
INPUT,
|
|
@@ -1771,22 +1798,15 @@ class MathExerciseParser extends chevrotain.CstParser {
|
|
|
1771
1798
|
});
|
|
1772
1799
|
this.MANY6({
|
|
1773
1800
|
GATE: () => {
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
const t = this.LA(k).tokenType;
|
|
1777
|
-
return t === BAR || t === PICTO;
|
|
1801
|
+
const t = this.LA(1).tokenType;
|
|
1802
|
+
return t === BAR || t === PICTO || t === SERIES || t === GROUP;
|
|
1778
1803
|
},
|
|
1779
1804
|
DEF: () => {
|
|
1780
1805
|
this.OR6([
|
|
1781
|
-
{
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
return this.LA(k).tokenType === BAR;
|
|
1786
|
-
},
|
|
1787
|
-
ALT: () => this.SUBRULE(this.chartBarItem)
|
|
1788
|
-
},
|
|
1789
|
-
{ ALT: () => this.SUBRULE(this.chartPictoItem) }
|
|
1806
|
+
{ GATE: () => this.LA(1).tokenType === BAR, ALT: () => this.SUBRULE(this.chartBarItem) },
|
|
1807
|
+
{ GATE: () => this.LA(1).tokenType === PICTO, ALT: () => this.SUBRULE(this.chartPictoItem) },
|
|
1808
|
+
{ GATE: () => this.LA(1).tokenType === SERIES, ALT: () => this.SUBRULE(this.chartSeriesItem) },
|
|
1809
|
+
{ ALT: () => this.SUBRULE(this.chartGroupItem) }
|
|
1790
1810
|
]);
|
|
1791
1811
|
}
|
|
1792
1812
|
});
|
|
@@ -1907,6 +1927,92 @@ class MathExerciseParser extends chevrotain.CstParser {
|
|
|
1907
1927
|
this.OR5([{ ALT: () => this.CONSUME5(Space) }, { ALT: () => this.CONSUME5(Newline) }]);
|
|
1908
1928
|
});
|
|
1909
1929
|
});
|
|
1930
|
+
this.chartSeriesItem = this.RULE("chartSeriesItem", () => {
|
|
1931
|
+
this.MANY(() => {
|
|
1932
|
+
this.OR([{ ALT: () => this.CONSUME(Space) }, { ALT: () => this.CONSUME(Newline) }]);
|
|
1933
|
+
});
|
|
1934
|
+
this.CONSUME(SERIES);
|
|
1935
|
+
this.CONSUME(LBracket);
|
|
1936
|
+
this.SUBRULE(this.parameterList);
|
|
1937
|
+
this.CONSUME(RBracket);
|
|
1938
|
+
this.MANY2(() => {
|
|
1939
|
+
this.OR2([{ ALT: () => this.CONSUME2(Space) }, { ALT: () => this.CONSUME2(Newline) }]);
|
|
1940
|
+
});
|
|
1941
|
+
this.CONSUME(LBrace);
|
|
1942
|
+
this.MANY3(() => {
|
|
1943
|
+
this.OR3([{ ALT: () => this.CONSUME3(Space) }, { ALT: () => this.CONSUME3(Newline) }]);
|
|
1944
|
+
});
|
|
1945
|
+
this.MANY4(() => {
|
|
1946
|
+
this.OR4([
|
|
1947
|
+
{ ALT: () => this.SUBRULE(this.inlineElement) },
|
|
1948
|
+
{ ALT: () => this.CONSUME4(Newline) }
|
|
1949
|
+
]);
|
|
1950
|
+
});
|
|
1951
|
+
this.CONSUME(RBrace);
|
|
1952
|
+
this.MANY5(() => {
|
|
1953
|
+
this.OR5([{ ALT: () => this.CONSUME5(Space) }, { ALT: () => this.CONSUME5(Newline) }]);
|
|
1954
|
+
});
|
|
1955
|
+
});
|
|
1956
|
+
this.chartGroupLabel = this.RULE("chartGroupLabel", () => {
|
|
1957
|
+
this.CONSUME(LABEL);
|
|
1958
|
+
this.MANY(() => {
|
|
1959
|
+
this.OR([{ ALT: () => this.CONSUME(Space) }, { ALT: () => this.CONSUME(Newline) }]);
|
|
1960
|
+
});
|
|
1961
|
+
this.CONSUME(LBrace);
|
|
1962
|
+
this.MANY2(() => {
|
|
1963
|
+
this.OR2([{ ALT: () => this.CONSUME2(Space) }, { ALT: () => this.CONSUME2(Newline) }]);
|
|
1964
|
+
});
|
|
1965
|
+
this.MANY3(() => {
|
|
1966
|
+
this.OR3([
|
|
1967
|
+
{ ALT: () => this.SUBRULE(this.inlineElement) },
|
|
1968
|
+
{ ALT: () => this.CONSUME3(Newline) }
|
|
1969
|
+
]);
|
|
1970
|
+
});
|
|
1971
|
+
this.CONSUME(RBrace);
|
|
1972
|
+
this.MANY4(() => {
|
|
1973
|
+
this.OR4([{ ALT: () => this.CONSUME4(Space) }, { ALT: () => this.CONSUME4(Newline) }]);
|
|
1974
|
+
});
|
|
1975
|
+
});
|
|
1976
|
+
this.chartSeriesBarItem = this.RULE("chartSeriesBarItem", () => {
|
|
1977
|
+
this.MANY(() => {
|
|
1978
|
+
this.OR([{ ALT: () => this.CONSUME(Space) }, { ALT: () => this.CONSUME(Newline) }]);
|
|
1979
|
+
});
|
|
1980
|
+
this.CONSUME(SERIES_BAR);
|
|
1981
|
+
this.CONSUME(LBracket);
|
|
1982
|
+
this.SUBRULE(this.parameterList);
|
|
1983
|
+
this.CONSUME(RBracket);
|
|
1984
|
+
this.MANY2(() => {
|
|
1985
|
+
this.OR2([{ ALT: () => this.CONSUME2(Space) }, { ALT: () => this.CONSUME2(Newline) }]);
|
|
1986
|
+
});
|
|
1987
|
+
});
|
|
1988
|
+
this.chartGroupItem = this.RULE("chartGroupItem", () => {
|
|
1989
|
+
this.MANY(() => {
|
|
1990
|
+
this.OR([{ ALT: () => this.CONSUME(Space) }, { ALT: () => this.CONSUME(Newline) }]);
|
|
1991
|
+
});
|
|
1992
|
+
this.CONSUME(GROUP);
|
|
1993
|
+
this.CONSUME(LBracket);
|
|
1994
|
+
this.SUBRULE(this.parameterList);
|
|
1995
|
+
this.CONSUME(RBracket);
|
|
1996
|
+
this.MANY2(() => {
|
|
1997
|
+
this.OR2([{ ALT: () => this.CONSUME2(Space) }, { ALT: () => this.CONSUME2(Newline) }]);
|
|
1998
|
+
});
|
|
1999
|
+
this.CONSUME(LBrace);
|
|
2000
|
+
this.MANY3(() => {
|
|
2001
|
+
this.OR3([{ ALT: () => this.CONSUME3(Space) }, { ALT: () => this.CONSUME3(Newline) }]);
|
|
2002
|
+
});
|
|
2003
|
+
this.MANY4({
|
|
2004
|
+
GATE: () => this.LA(1).tokenType === LABEL,
|
|
2005
|
+
DEF: () => this.SUBRULE(this.chartGroupLabel)
|
|
2006
|
+
});
|
|
2007
|
+
this.MANY5({
|
|
2008
|
+
GATE: () => this.LA(1).tokenType === SERIES_BAR,
|
|
2009
|
+
DEF: () => this.SUBRULE(this.chartSeriesBarItem)
|
|
2010
|
+
});
|
|
2011
|
+
this.CONSUME(RBrace);
|
|
2012
|
+
this.MANY6(() => {
|
|
2013
|
+
this.OR4([{ ALT: () => this.CONSUME4(Space) }, { ALT: () => this.CONSUME4(Newline) }]);
|
|
2014
|
+
});
|
|
2015
|
+
});
|
|
1910
2016
|
this.performSelfAnalysis();
|
|
1911
2017
|
}
|
|
1912
2018
|
/** Distinguish `{[number, number], ...}` and the valid empty form `{}` from string lists. */
|
|
@@ -1952,6 +2058,8 @@ const VISUAL_FRACTION_SHAPE = {
|
|
|
1952
2058
|
};
|
|
1953
2059
|
const VISUAL_FRACTION_DEFAULT_SIZE = 1.35;
|
|
1954
2060
|
const VISUAL_FRACTION_MAX_DIVISIONS = 100;
|
|
2061
|
+
const INLINE_INPUT_MAX_WIDTH = 20;
|
|
2062
|
+
const InlineInputWidthSchema = zod.z.number().int().positive().max(INLINE_INPUT_MAX_WIDTH, { message: `width cannot exceed ${INLINE_INPUT_MAX_WIDTH}` }).optional();
|
|
1955
2063
|
const FreeTextInlineSchema = zod.z.object({
|
|
1956
2064
|
type: zod.z.literal(AST_TYPES.FREE_TEXT),
|
|
1957
2065
|
value: zod.z.string()
|
|
@@ -1981,6 +2089,7 @@ const InlineInputWidgetSchema = zod.z.discriminatedUnion("inputType", [
|
|
|
1981
2089
|
name: zod.z.string(),
|
|
1982
2090
|
inputType: zod.z.literal(INPUT_TYPES.STRING),
|
|
1983
2091
|
length: zod.z.number().int().positive(),
|
|
2092
|
+
width: InlineInputWidthSchema,
|
|
1984
2093
|
placeholder: zod.z.string().max(32).optional(),
|
|
1985
2094
|
disabled: zod.z.boolean().optional(),
|
|
1986
2095
|
answer: zod.z.union([zod.z.string(), zod.z.number()]).optional()
|
|
@@ -1990,6 +2099,7 @@ const InlineInputWidgetSchema = zod.z.discriminatedUnion("inputType", [
|
|
|
1990
2099
|
name: zod.z.string(),
|
|
1991
2100
|
inputType: zod.z.literal(INPUT_TYPES.NUMERIC),
|
|
1992
2101
|
digits: zod.z.number().int().positive(),
|
|
2102
|
+
width: InlineInputWidthSchema,
|
|
1993
2103
|
placeholder: zod.z.string().max(32).optional(),
|
|
1994
2104
|
disabled: zod.z.boolean().optional(),
|
|
1995
2105
|
answer: zod.z.union([zod.z.string(), zod.z.number()]).optional()
|
|
@@ -2147,7 +2257,7 @@ function createBoldText(value, highlight) {
|
|
|
2147
2257
|
highlight
|
|
2148
2258
|
});
|
|
2149
2259
|
}
|
|
2150
|
-
function createStringInput(name, length, answer, placeholder, disabled) {
|
|
2260
|
+
function createStringInput(name, length, answer, placeholder, disabled, width) {
|
|
2151
2261
|
return parseOrThrow(
|
|
2152
2262
|
InlineInputWidgetSchema,
|
|
2153
2263
|
{
|
|
@@ -2155,6 +2265,7 @@ function createStringInput(name, length, answer, placeholder, disabled) {
|
|
|
2155
2265
|
name,
|
|
2156
2266
|
inputType: INPUT_TYPES.STRING,
|
|
2157
2267
|
length,
|
|
2268
|
+
width,
|
|
2158
2269
|
placeholder,
|
|
2159
2270
|
disabled,
|
|
2160
2271
|
// A disabled input is never graded, so its answer is dropped.
|
|
@@ -2163,7 +2274,7 @@ function createStringInput(name, length, answer, placeholder, disabled) {
|
|
|
2163
2274
|
"STRING_INPUT"
|
|
2164
2275
|
);
|
|
2165
2276
|
}
|
|
2166
|
-
function createNumericInput(name, digits, answer, placeholder, disabled) {
|
|
2277
|
+
function createNumericInput(name, digits, answer, placeholder, disabled, width) {
|
|
2167
2278
|
return parseOrThrow(
|
|
2168
2279
|
InlineInputWidgetSchema,
|
|
2169
2280
|
{
|
|
@@ -2171,6 +2282,7 @@ function createNumericInput(name, digits, answer, placeholder, disabled) {
|
|
|
2171
2282
|
name,
|
|
2172
2283
|
inputType: INPUT_TYPES.NUMERIC,
|
|
2173
2284
|
digits,
|
|
2285
|
+
width,
|
|
2174
2286
|
placeholder,
|
|
2175
2287
|
disabled,
|
|
2176
2288
|
// A disabled input is never graded, so its answer is dropped.
|
|
@@ -2387,7 +2499,7 @@ const NumberLineBlockSchema = zod.z.object({
|
|
|
2387
2499
|
errorMap: () => ({
|
|
2388
2500
|
message: `NUMBER_LINE: style must be one of ${NUMBER_LINE_STYLES.join(", ")}`
|
|
2389
2501
|
})
|
|
2390
|
-
}).
|
|
2502
|
+
}).default(DEFAULT_NUMBER_LINE_STYLE),
|
|
2391
2503
|
labelVisibility: zod.z.enum(NUMBER_LINE_LABELS, {
|
|
2392
2504
|
errorMap: () => ({
|
|
2393
2505
|
message: `NUMBER_LINE: labelVisibility must be one of ${NUMBER_LINE_LABELS.join(", ")}`
|
|
@@ -3066,7 +3178,7 @@ function isSetSquareTool(value) {
|
|
|
3066
3178
|
function isToolNode(value) {
|
|
3067
3179
|
return ToolNodeSchema.safeParse(value).success;
|
|
3068
3180
|
}
|
|
3069
|
-
const CHART_TYPES = ["bar", "pictograph"];
|
|
3181
|
+
const CHART_TYPES = ["bar", "pictograph", "grouped-bar"];
|
|
3070
3182
|
const CHART_AXIS_DEFAULTS = { min: 0, max: 100, step: 10, visible: true };
|
|
3071
3183
|
const CHART_MAX_AXIS_TICKS = 200;
|
|
3072
3184
|
const CHART_MAX_PICTO_SLOTS = 60;
|
|
@@ -3146,6 +3258,63 @@ function createChartBar(id, opts) {
|
|
|
3146
3258
|
function isChartBar(value) {
|
|
3147
3259
|
return ChartBarSchema.safeParse(value).success;
|
|
3148
3260
|
}
|
|
3261
|
+
const ChartSeriesSchema = zod.z.object({
|
|
3262
|
+
type: zod.z.literal(AST_TYPES.CHART_SERIES),
|
|
3263
|
+
id: zod.z.string().min(1),
|
|
3264
|
+
color: zod.z.string().min(1),
|
|
3265
|
+
inlines: zod.z.array(DisplayInlineNodeSchema).default([])
|
|
3266
|
+
});
|
|
3267
|
+
function createChartSeries(id, opts) {
|
|
3268
|
+
return ChartSeriesSchema.parse({
|
|
3269
|
+
type: AST_TYPES.CHART_SERIES,
|
|
3270
|
+
id,
|
|
3271
|
+
color: opts.color,
|
|
3272
|
+
inlines: opts.inlines ?? []
|
|
3273
|
+
});
|
|
3274
|
+
}
|
|
3275
|
+
function isChartSeries(value) {
|
|
3276
|
+
return ChartSeriesSchema.safeParse(value).success;
|
|
3277
|
+
}
|
|
3278
|
+
const ChartSeriesBarSchema = zod.z.object({
|
|
3279
|
+
type: zod.z.literal(AST_TYPES.CHART_SERIES_BAR),
|
|
3280
|
+
id: zod.z.string().min(1),
|
|
3281
|
+
seriesId: zod.z.string().min(1),
|
|
3282
|
+
value: zod.z.number().default(0),
|
|
3283
|
+
answer: zod.z.number().optional()
|
|
3284
|
+
});
|
|
3285
|
+
function createChartSeriesBar(id, opts) {
|
|
3286
|
+
return ChartSeriesBarSchema.parse({
|
|
3287
|
+
type: AST_TYPES.CHART_SERIES_BAR,
|
|
3288
|
+
id,
|
|
3289
|
+
seriesId: opts.seriesId,
|
|
3290
|
+
value: opts.value ?? 0,
|
|
3291
|
+
answer: opts.answer
|
|
3292
|
+
});
|
|
3293
|
+
}
|
|
3294
|
+
function isChartSeriesBar(value) {
|
|
3295
|
+
return ChartSeriesBarSchema.safeParse(value).success;
|
|
3296
|
+
}
|
|
3297
|
+
const ChartGroupSchema = zod.z.object({
|
|
3298
|
+
type: zod.z.literal(AST_TYPES.CHART_GROUP),
|
|
3299
|
+
id: zod.z.string().min(1),
|
|
3300
|
+
labelInlines: zod.z.array(InlineNodeSchema).default([]),
|
|
3301
|
+
bars: zod.z.array(ChartSeriesBarSchema).default([])
|
|
3302
|
+
});
|
|
3303
|
+
function createChartGroup(id, opts = {}) {
|
|
3304
|
+
return ChartGroupSchema.parse({
|
|
3305
|
+
type: AST_TYPES.CHART_GROUP,
|
|
3306
|
+
id,
|
|
3307
|
+
labelInlines: opts.labelInlines ?? [],
|
|
3308
|
+
bars: opts.bars ?? []
|
|
3309
|
+
});
|
|
3310
|
+
}
|
|
3311
|
+
function isChartGroup(value) {
|
|
3312
|
+
return ChartGroupSchema.safeParse(value).success;
|
|
3313
|
+
}
|
|
3314
|
+
function alignGroupBarsToSeries(group, series) {
|
|
3315
|
+
const bySeriesId = new Map(group.bars.map((bar) => [bar.seriesId, bar]));
|
|
3316
|
+
return series.map((s) => bySeriesId.get(s.id));
|
|
3317
|
+
}
|
|
3149
3318
|
const ChartBlockSchema = zod.z.object({
|
|
3150
3319
|
type: zod.z.literal(AST_TYPES.CHART_BLOCK),
|
|
3151
3320
|
name: zod.z.string().optional(),
|
|
@@ -3157,7 +3326,10 @@ const ChartBlockSchema = zod.z.object({
|
|
|
3157
3326
|
xAxis: ChartAxisSchema.optional(),
|
|
3158
3327
|
yAxis: ChartAxisSchema.optional(),
|
|
3159
3328
|
bars: zod.z.array(ChartBarSchema).default([]),
|
|
3160
|
-
pictos: zod.z.array(ChartPictoSchema).default([])
|
|
3329
|
+
pictos: zod.z.array(ChartPictoSchema).default([]),
|
|
3330
|
+
legend: zod.z.boolean().default(false),
|
|
3331
|
+
series: zod.z.array(ChartSeriesSchema).default([]),
|
|
3332
|
+
groups: zod.z.array(ChartGroupSchema).default([])
|
|
3161
3333
|
}).superRefine((block, ctx) => {
|
|
3162
3334
|
validateChartBlock(block, ctx);
|
|
3163
3335
|
});
|
|
@@ -3182,10 +3354,10 @@ function validateChartBlock(block, ctx) {
|
|
|
3182
3354
|
const { valueAxis } = resolveChartAxes(block);
|
|
3183
3355
|
validateValueAxisRange(valueAxis, ctx);
|
|
3184
3356
|
if (block.chartType === "bar") {
|
|
3185
|
-
if (block.pictos.length > 0) {
|
|
3357
|
+
if (block.pictos.length > 0 || block.series.length > 0 || block.groups.length > 0) {
|
|
3186
3358
|
ctx.addIssue({
|
|
3187
3359
|
code: zod.z.ZodIssueCode.custom,
|
|
3188
|
-
message: 'CHART[type="bar"]: body must contain BAR items only, not PICTO',
|
|
3360
|
+
message: 'CHART[type="bar"]: body must contain BAR items only, not PICTO, SERIES, or GROUP',
|
|
3189
3361
|
path: ["pictos"]
|
|
3190
3362
|
});
|
|
3191
3363
|
return;
|
|
@@ -3198,16 +3370,16 @@ function validateChartBlock(block, ctx) {
|
|
|
3198
3370
|
});
|
|
3199
3371
|
return;
|
|
3200
3372
|
}
|
|
3201
|
-
validateUniqueIds(block.bars, ctx);
|
|
3373
|
+
validateUniqueIds(block.bars, ctx, (i) => ["bars", i]);
|
|
3202
3374
|
validateChartSnap(block.snap, valueAxis.step, ctx);
|
|
3203
|
-
validateBarAnswers(block.bars, valueAxis, block.snap, ctx);
|
|
3375
|
+
validateBarAnswers(block.bars, valueAxis, block.snap, ctx, (i) => ["bars", i]);
|
|
3204
3376
|
return;
|
|
3205
3377
|
}
|
|
3206
3378
|
if (block.chartType === "pictograph") {
|
|
3207
|
-
if (block.bars.length > 0) {
|
|
3379
|
+
if (block.bars.length > 0 || block.series.length > 0 || block.groups.length > 0) {
|
|
3208
3380
|
ctx.addIssue({
|
|
3209
3381
|
code: zod.z.ZodIssueCode.custom,
|
|
3210
|
-
message: 'CHART[type="pictograph"]: body must contain PICTO items only, not BAR',
|
|
3382
|
+
message: 'CHART[type="pictograph"]: body must contain PICTO items only, not BAR, SERIES, or GROUP',
|
|
3211
3383
|
path: ["bars"]
|
|
3212
3384
|
});
|
|
3213
3385
|
return;
|
|
@@ -3228,7 +3400,7 @@ function validateChartBlock(block, ctx) {
|
|
|
3228
3400
|
});
|
|
3229
3401
|
return;
|
|
3230
3402
|
}
|
|
3231
|
-
validateUniqueIds(block.pictos, ctx);
|
|
3403
|
+
validateUniqueIds(block.pictos, ctx, (i) => ["pictos", i]);
|
|
3232
3404
|
if (!axisHasNumericParams(valueAxis.axis)) {
|
|
3233
3405
|
ctx.addIssue({
|
|
3234
3406
|
code: zod.z.ZodIssueCode.custom,
|
|
@@ -3249,6 +3421,63 @@ function validateChartBlock(block, ctx) {
|
|
|
3249
3421
|
validatePictoAnswers(block.pictos, valueAxis, ctx);
|
|
3250
3422
|
return;
|
|
3251
3423
|
}
|
|
3424
|
+
if (block.chartType === "grouped-bar") {
|
|
3425
|
+
if (block.bars.length > 0 || block.pictos.length > 0) {
|
|
3426
|
+
ctx.addIssue({
|
|
3427
|
+
code: zod.z.ZodIssueCode.custom,
|
|
3428
|
+
message: 'CHART[type="grouped-bar"]: body must contain SERIES and GROUP items only, not BAR or PICTO',
|
|
3429
|
+
path: ["bars"]
|
|
3430
|
+
});
|
|
3431
|
+
return;
|
|
3432
|
+
}
|
|
3433
|
+
if (block.series.length === 0) {
|
|
3434
|
+
ctx.addIssue({
|
|
3435
|
+
code: zod.z.ZodIssueCode.custom,
|
|
3436
|
+
message: 'CHART[type="grouped-bar"]: at least one SERIES is required',
|
|
3437
|
+
path: ["series"]
|
|
3438
|
+
});
|
|
3439
|
+
return;
|
|
3440
|
+
}
|
|
3441
|
+
if (block.groups.length === 0) {
|
|
3442
|
+
ctx.addIssue({
|
|
3443
|
+
code: zod.z.ZodIssueCode.custom,
|
|
3444
|
+
message: 'CHART[type="grouped-bar"]: at least one GROUP is required',
|
|
3445
|
+
path: ["groups"]
|
|
3446
|
+
});
|
|
3447
|
+
return;
|
|
3448
|
+
}
|
|
3449
|
+
validateUniqueIds(block.series, ctx, (i) => ["series", i]);
|
|
3450
|
+
validateUniqueIds(block.groups, ctx, (i) => ["groups", i]);
|
|
3451
|
+
const allBars = block.groups.flatMap((g, gi) => g.bars.map((bar, bi) => ({ ...bar, groupIndex: gi, barIndex: bi })));
|
|
3452
|
+
const pathForFlatBar = (i) => ["groups", allBars[i].groupIndex, "bars", allBars[i].barIndex];
|
|
3453
|
+
validateUniqueIds(allBars, ctx, pathForFlatBar);
|
|
3454
|
+
const seriesIds = new Set(block.series.map((s) => s.id));
|
|
3455
|
+
block.groups.forEach((group, gi) => {
|
|
3456
|
+
const seenInGroup = /* @__PURE__ */ new Set();
|
|
3457
|
+
group.bars.forEach((bar, bi) => {
|
|
3458
|
+
if (!seriesIds.has(bar.seriesId)) {
|
|
3459
|
+
ctx.addIssue({
|
|
3460
|
+
code: zod.z.ZodIssueCode.custom,
|
|
3461
|
+
message: `CHART: GROUP "${group.id}" SERIES_BAR "${bar.id}" references unknown series "${bar.seriesId}" — no SERIES with that id is declared`,
|
|
3462
|
+
path: ["groups", gi, "bars", bi, "seriesId"]
|
|
3463
|
+
});
|
|
3464
|
+
return;
|
|
3465
|
+
}
|
|
3466
|
+
if (seenInGroup.has(bar.seriesId)) {
|
|
3467
|
+
ctx.addIssue({
|
|
3468
|
+
code: zod.z.ZodIssueCode.custom,
|
|
3469
|
+
message: `CHART: GROUP "${group.id}" references series "${bar.seriesId}" twice — each SERIES can appear at most once per GROUP`,
|
|
3470
|
+
path: ["groups", gi, "bars", bi, "seriesId"]
|
|
3471
|
+
});
|
|
3472
|
+
return;
|
|
3473
|
+
}
|
|
3474
|
+
seenInGroup.add(bar.seriesId);
|
|
3475
|
+
});
|
|
3476
|
+
});
|
|
3477
|
+
validateChartSnap(block.snap, valueAxis.step, ctx);
|
|
3478
|
+
validateBarAnswers(allBars, valueAxis, block.snap, ctx, pathForFlatBar);
|
|
3479
|
+
return;
|
|
3480
|
+
}
|
|
3252
3481
|
ctx.addIssue({
|
|
3253
3482
|
code: zod.z.ZodIssueCode.custom,
|
|
3254
3483
|
message: `CHART: unsupported chartType "${block.chartType}" — add a validation branch to validateChartBlock`,
|
|
@@ -3269,14 +3498,14 @@ function resolveNumericAxis(axis) {
|
|
|
3269
3498
|
function hasAmbiguousValueAxes(block) {
|
|
3270
3499
|
return axisHasNumericParams(block.xAxis) && axisHasNumericParams(block.yAxis);
|
|
3271
3500
|
}
|
|
3272
|
-
function validateUniqueIds(items, ctx) {
|
|
3501
|
+
function validateUniqueIds(items, ctx, pathFor) {
|
|
3273
3502
|
const seen = /* @__PURE__ */ new Set();
|
|
3274
3503
|
items.forEach((item, index) => {
|
|
3275
3504
|
if (seen.has(item.id)) {
|
|
3276
3505
|
ctx.addIssue({
|
|
3277
3506
|
code: zod.z.ZodIssueCode.custom,
|
|
3278
3507
|
message: `CHART: duplicate id "${item.id}" — ids must be unique within the chart`,
|
|
3279
|
-
path: [index, "id"]
|
|
3508
|
+
path: [...pathFor(index), "id"]
|
|
3280
3509
|
});
|
|
3281
3510
|
return;
|
|
3282
3511
|
}
|
|
@@ -3318,14 +3547,14 @@ function validateChartSnap(snap, step, ctx) {
|
|
|
3318
3547
|
});
|
|
3319
3548
|
}
|
|
3320
3549
|
}
|
|
3321
|
-
function validateBarAnswers(bars, valueAxis, snap, ctx) {
|
|
3550
|
+
function validateBarAnswers(bars, valueAxis, snap, ctx, pathFor) {
|
|
3322
3551
|
const gridStep = snap ?? valueAxis.step;
|
|
3323
3552
|
bars.forEach((bar, index) => {
|
|
3324
3553
|
if (bar.value < valueAxis.min || bar.value > valueAxis.max) {
|
|
3325
3554
|
ctx.addIssue({
|
|
3326
3555
|
code: zod.z.ZodIssueCode.custom,
|
|
3327
3556
|
message: `CHART: BAR "${bar.id}" value (${bar.value}) is outside axis range [${valueAxis.min}, ${valueAxis.max}] — the bar will not render correctly`,
|
|
3328
|
-
path: [
|
|
3557
|
+
path: [...pathFor(index), "value"]
|
|
3329
3558
|
});
|
|
3330
3559
|
}
|
|
3331
3560
|
if (bar.answer === void 0) return;
|
|
@@ -3333,7 +3562,7 @@ function validateBarAnswers(bars, valueAxis, snap, ctx) {
|
|
|
3333
3562
|
ctx.addIssue({
|
|
3334
3563
|
code: zod.z.ZodIssueCode.custom,
|
|
3335
3564
|
message: `CHART: BAR "${bar.id}" answer (${bar.answer}) is outside axis range [${valueAxis.min}, ${valueAxis.max}] — the student can never reach this value`,
|
|
3336
|
-
path: [
|
|
3565
|
+
path: [...pathFor(index), "answer"]
|
|
3337
3566
|
});
|
|
3338
3567
|
return;
|
|
3339
3568
|
}
|
|
@@ -3342,7 +3571,7 @@ function validateBarAnswers(bars, valueAxis, snap, ctx) {
|
|
|
3342
3571
|
ctx.addIssue({
|
|
3343
3572
|
code: zod.z.ZodIssueCode.custom,
|
|
3344
3573
|
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.`,
|
|
3345
|
-
path: [
|
|
3574
|
+
path: [...pathFor(index), "answer"]
|
|
3346
3575
|
});
|
|
3347
3576
|
}
|
|
3348
3577
|
});
|
|
@@ -3404,7 +3633,10 @@ function createChartBlock(chartType, opts = {}) {
|
|
|
3404
3633
|
xAxis: opts.xAxis,
|
|
3405
3634
|
yAxis: opts.yAxis,
|
|
3406
3635
|
bars: opts.bars ?? [],
|
|
3407
|
-
pictos: opts.pictos ?? []
|
|
3636
|
+
pictos: opts.pictos ?? [],
|
|
3637
|
+
series: opts.series ?? [],
|
|
3638
|
+
groups: opts.groups ?? [],
|
|
3639
|
+
legend: opts.legend ?? false
|
|
3408
3640
|
});
|
|
3409
3641
|
}
|
|
3410
3642
|
function isChartBlock(value) {
|
|
@@ -3491,7 +3723,9 @@ const ExerciseDocumentSchema = zod.z.object({
|
|
|
3491
3723
|
};
|
|
3492
3724
|
const visitInlines = (inlines, basePath) => {
|
|
3493
3725
|
inlines.forEach((inline, i) => {
|
|
3494
|
-
if (inline.type === AST_TYPES.INLINE_INPUT || inline.type === AST_TYPES.FRACTION || inline.type === AST_TYPES.IMAGE) {
|
|
3726
|
+
if (inline.type === AST_TYPES.INLINE_INPUT || inline.type === AST_TYPES.FRACTION || inline.type === AST_TYPES.IMAGE || inline.type === AST_TYPES.VISUAL_FRACTION) {
|
|
3727
|
+
record(inline.name, [...basePath, i, "name"]);
|
|
3728
|
+
} else if (inline.type === AST_TYPES.CLOCK && inline.mode === "interactive") {
|
|
3495
3729
|
record(inline.name, [...basePath, i, "name"]);
|
|
3496
3730
|
}
|
|
3497
3731
|
});
|
|
@@ -3523,6 +3757,28 @@ const ExerciseDocumentSchema = zod.z.object({
|
|
|
3523
3757
|
pm.groups.flat().forEach((item, ii) => {
|
|
3524
3758
|
visitInlines(item.inlines, ["lines", bi, "items", ii, "inlines"]);
|
|
3525
3759
|
});
|
|
3760
|
+
} else if (block.type === AST_TYPES.CHART_BLOCK) {
|
|
3761
|
+
const chart = block;
|
|
3762
|
+
const base = ["lines", bi];
|
|
3763
|
+
chart.bars.forEach((bar, i) => {
|
|
3764
|
+
if (bar.answer !== void 0) record(bar.id, [...base, "bars", i, "id"]);
|
|
3765
|
+
visitInlines(bar.inlines, [...base, "bars", i, "inlines"]);
|
|
3766
|
+
});
|
|
3767
|
+
chart.pictos.forEach((picto, i) => {
|
|
3768
|
+
if (picto.answer !== void 0) record(picto.id, [...base, "pictos", i, "id"]);
|
|
3769
|
+
visitInlines(picto.inlines, [...base, "pictos", i, "inlines"]);
|
|
3770
|
+
});
|
|
3771
|
+
chart.groups.forEach((group, gi) => {
|
|
3772
|
+
group.bars.forEach((bar, i) => {
|
|
3773
|
+
if (bar.answer !== void 0) record(bar.id, [...base, "groups", gi, "bars", i, "id"]);
|
|
3774
|
+
});
|
|
3775
|
+
visitInlines(group.labelInlines, [...base, "groups", gi, "labelInlines"]);
|
|
3776
|
+
});
|
|
3777
|
+
chart.series.forEach((series, i) => {
|
|
3778
|
+
visitInlines(series.inlines, [...base, "series", i, "inlines"]);
|
|
3779
|
+
});
|
|
3780
|
+
if (chart.xAxis) visitInlines(chart.xAxis.inlines, [...base, "xAxis", "inlines"]);
|
|
3781
|
+
if (chart.yAxis) visitInlines(chart.yAxis.inlines, [...base, "yAxis", "inlines"]);
|
|
3526
3782
|
} else if (block.type === AST_TYPES.RADIO_OPTIONS) {
|
|
3527
3783
|
record(block.name, ["lines", bi, "name"]);
|
|
3528
3784
|
} else if (block.type === AST_TYPES.GEOMETRY_BLOCK && block.name !== void 0) {
|
|
@@ -4076,12 +4332,19 @@ class NaturalDSLVisitor extends BaseCstVisitor {
|
|
|
4076
4332
|
`${ctx.stringInput ? "STRING_INPUT" : "NUMERIC_INPUT"} "${name}": disabled must be true or false, got ${JSON.stringify(params.disabled)}`
|
|
4077
4333
|
);
|
|
4078
4334
|
}
|
|
4335
|
+
const width = params.width;
|
|
4336
|
+
const maxChars = ctx.stringInput ? params.length : params.digits;
|
|
4337
|
+
if (width !== void 0 && width < maxChars) {
|
|
4338
|
+
throw new Error(
|
|
4339
|
+
`${ctx.stringInput ? "STRING_INPUT" : "NUMERIC_INPUT"} "${name}": width (${width}) cannot be smaller than ${ctx.stringInput ? "length" : "digits"} (${maxChars})`
|
|
4340
|
+
);
|
|
4341
|
+
}
|
|
4079
4342
|
if (ctx.stringInput) {
|
|
4080
4343
|
const length = params.length;
|
|
4081
|
-
return createStringInput(name, length, answer, placeholder, disabled);
|
|
4344
|
+
return createStringInput(name, length, answer, placeholder, disabled, width);
|
|
4082
4345
|
}
|
|
4083
4346
|
const digits = params.digits;
|
|
4084
|
-
return createNumericInput(name, digits, answer, placeholder, disabled);
|
|
4347
|
+
return createNumericInput(name, digits, answer, placeholder, disabled, width);
|
|
4085
4348
|
}
|
|
4086
4349
|
inlineDragOption(ctx) {
|
|
4087
4350
|
const valueToken = ctx.StringLiteral[0];
|
|
@@ -4094,7 +4357,7 @@ class NaturalDSLVisitor extends BaseCstVisitor {
|
|
|
4094
4357
|
chartBlock(ctx) {
|
|
4095
4358
|
const params = this.visit(ctx.parameterList);
|
|
4096
4359
|
const chartType = params.type;
|
|
4097
|
-
if (!chartType) throw new Error(
|
|
4360
|
+
if (!chartType) throw new Error(`CHART: type is required (${CHART_TYPES.map((t) => `"${t}"`).join(" | ")})`);
|
|
4098
4361
|
if (!CHART_TYPES.includes(chartType)) {
|
|
4099
4362
|
throw new Error(`CHART: type must be one of ${CHART_TYPES.join(", ")} (got "${chartType}")`);
|
|
4100
4363
|
}
|
|
@@ -4113,7 +4376,26 @@ class NaturalDSLVisitor extends BaseCstVisitor {
|
|
|
4113
4376
|
const yAxis = ctx.chartYAxis ? this.visit(ctx.chartYAxis) : void 0;
|
|
4114
4377
|
const bars = ctx.chartBarItem ? ctx.chartBarItem.map((b) => this.visit(b)) : [];
|
|
4115
4378
|
const pictos = ctx.chartPictoItem ? ctx.chartPictoItem.map((p) => this.visit(p)) : [];
|
|
4116
|
-
|
|
4379
|
+
const series = ctx.chartSeriesItem ? ctx.chartSeriesItem.map((s) => this.visit(s)) : [];
|
|
4380
|
+
const groups = ctx.chartGroupItem ? ctx.chartGroupItem.map((g) => this.visit(g)) : [];
|
|
4381
|
+
if (params.legend !== void 0 && chartType !== "grouped-bar") {
|
|
4382
|
+
throw new Error(`CHART: "legend" is only supported for type="grouped-bar" (got "${chartType}")`);
|
|
4383
|
+
}
|
|
4384
|
+
const legend = params.legend === true || params.legend === "true";
|
|
4385
|
+
return createChartBlock(chartType, {
|
|
4386
|
+
bars,
|
|
4387
|
+
pictos,
|
|
4388
|
+
series,
|
|
4389
|
+
groups,
|
|
4390
|
+
legend,
|
|
4391
|
+
name,
|
|
4392
|
+
title,
|
|
4393
|
+
grid,
|
|
4394
|
+
scale,
|
|
4395
|
+
snap,
|
|
4396
|
+
xAxis,
|
|
4397
|
+
yAxis
|
|
4398
|
+
});
|
|
4117
4399
|
}
|
|
4118
4400
|
/** X_AXIS and Y_AXIS carry identical params — only the produced AST type differs.
|
|
4119
4401
|
* Chevrotain needs two separate grammar rules, but one visitor body serves both. */
|
|
@@ -4152,6 +4434,52 @@ class NaturalDSLVisitor extends BaseCstVisitor {
|
|
|
4152
4434
|
inlines
|
|
4153
4435
|
});
|
|
4154
4436
|
}
|
|
4437
|
+
chartSeriesItem(ctx) {
|
|
4438
|
+
const params = this.visit(ctx.parameterList);
|
|
4439
|
+
const inlines = ctx.inlineElement ? ctx.inlineElement.map((e) => this.visit(e)) : [];
|
|
4440
|
+
if (inlines.some((n) => !DisplayInlineNodeSchema.safeParse(n).success)) {
|
|
4441
|
+
throw new Error(
|
|
4442
|
+
`CHART: SERIES "${params.id}" label must be display-only — inputs, drag options, clocks and interactive fractions are not allowed in a legend label`
|
|
4443
|
+
);
|
|
4444
|
+
}
|
|
4445
|
+
if (!params.color) {
|
|
4446
|
+
throw new Error(`CHART: SERIES "${params.id}" requires "color"`);
|
|
4447
|
+
}
|
|
4448
|
+
return createChartSeries(params.id, {
|
|
4449
|
+
color: params.color,
|
|
4450
|
+
inlines
|
|
4451
|
+
});
|
|
4452
|
+
}
|
|
4453
|
+
chartGroupLabel(ctx) {
|
|
4454
|
+
return ctx.inlineElement ? ctx.inlineElement.map((e) => this.visit(e)) : [];
|
|
4455
|
+
}
|
|
4456
|
+
chartGroupItem(ctx) {
|
|
4457
|
+
const params = this.visit(ctx.parameterList);
|
|
4458
|
+
if (ctx.chartGroupLabel && ctx.chartGroupLabel.length > 1) {
|
|
4459
|
+
throw new Error(`CHART: GROUP "${params.id}" has more than one LABEL — only one is allowed`);
|
|
4460
|
+
}
|
|
4461
|
+
const labelInlines = ctx.chartGroupLabel ? this.visit(ctx.chartGroupLabel) : [];
|
|
4462
|
+
const bars = ctx.chartSeriesBarItem ? ctx.chartSeriesBarItem.map((b) => this.visit(b)) : [];
|
|
4463
|
+
return createChartGroup(params.id, { labelInlines, bars });
|
|
4464
|
+
}
|
|
4465
|
+
// `params.series` is the raw DSL attribute name (SERIES_BAR[series="..."])
|
|
4466
|
+
// — unrelated to the AST field's own `seriesId` name.
|
|
4467
|
+
chartSeriesBarItem(ctx) {
|
|
4468
|
+
const params = this.visit(ctx.parameterList);
|
|
4469
|
+
if (!params.series) {
|
|
4470
|
+
throw new Error(`CHART: SERIES_BAR "${params.id}" requires "series"`);
|
|
4471
|
+
}
|
|
4472
|
+
if (params.color !== void 0) {
|
|
4473
|
+
throw new Error(
|
|
4474
|
+
`CHART: SERIES_BAR "${params.id}" cannot set "color" — it comes from its SERIES`
|
|
4475
|
+
);
|
|
4476
|
+
}
|
|
4477
|
+
return createChartSeriesBar(params.id, {
|
|
4478
|
+
seriesId: params.series,
|
|
4479
|
+
value: params.value !== void 0 ? Number(params.value) : void 0,
|
|
4480
|
+
answer: params.answer !== void 0 ? Number(params.answer) : void 0
|
|
4481
|
+
});
|
|
4482
|
+
}
|
|
4155
4483
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
4156
4484
|
// Tools
|
|
4157
4485
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -4606,23 +4934,23 @@ const ChartAxisJSONSchema = zod.z.object({
|
|
|
4606
4934
|
});
|
|
4607
4935
|
}
|
|
4608
4936
|
});
|
|
4609
|
-
const
|
|
4610
|
-
zod.z.
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
id: zod.z.string(),
|
|
4620
|
-
type: zod.z.literal(JSON_TYPES.CHART_BAR),
|
|
4937
|
+
const ChartBarJSONBaseSchema = zod.z.object({
|
|
4938
|
+
id: zod.z.string(),
|
|
4939
|
+
type: zod.z.literal(JSON_TYPES.CHART_BAR),
|
|
4940
|
+
value: zod.z.number(),
|
|
4941
|
+
answer: zod.z.number().optional(),
|
|
4942
|
+
color: zod.z.string().optional(),
|
|
4943
|
+
label: zod.z.array(LayoutItemSchema).optional()
|
|
4944
|
+
});
|
|
4945
|
+
const ChartBarJSONSchema = zod.z.union([
|
|
4946
|
+
ChartBarJSONBaseSchema.extend({
|
|
4621
4947
|
interactive: zod.z.literal(true),
|
|
4622
|
-
|
|
4623
|
-
widgetId: zod.z.string()
|
|
4624
|
-
|
|
4625
|
-
|
|
4948
|
+
/** The widgets/answers key, equal to `id` when compiled. */
|
|
4949
|
+
widgetId: zod.z.string()
|
|
4950
|
+
}),
|
|
4951
|
+
ChartBarJSONBaseSchema.extend({
|
|
4952
|
+
interactive: zod.z.literal(false).optional(),
|
|
4953
|
+
widgetId: zod.z.undefined().optional()
|
|
4626
4954
|
})
|
|
4627
4955
|
]);
|
|
4628
4956
|
const ChartPictoJSONSchema = zod.z.object({
|
|
@@ -4636,21 +4964,55 @@ const ChartPictoJSONSchema = zod.z.object({
|
|
|
4636
4964
|
),
|
|
4637
4965
|
label: zod.z.array(LayoutItemSchema).optional()
|
|
4638
4966
|
});
|
|
4639
|
-
const
|
|
4967
|
+
const ChartSeriesJSONSchema = zod.z.object({
|
|
4968
|
+
id: zod.z.string(),
|
|
4969
|
+
color: zod.z.string(),
|
|
4970
|
+
label: zod.z.array(LayoutItemSchema).optional()
|
|
4971
|
+
});
|
|
4972
|
+
const ChartSeriesBarJSONSchema = zod.z.object({
|
|
4973
|
+
id: zod.z.string(),
|
|
4974
|
+
type: zod.z.literal(JSON_TYPES.CHART_SERIES_BAR),
|
|
4975
|
+
value: zod.z.number(),
|
|
4976
|
+
answer: zod.z.number().optional(),
|
|
4977
|
+
color: zod.z.string(),
|
|
4978
|
+
seriesId: zod.z.string()
|
|
4979
|
+
});
|
|
4980
|
+
const ChartGroupJSONSchema = zod.z.object({
|
|
4981
|
+
id: zod.z.string(),
|
|
4982
|
+
label: zod.z.array(LayoutItemSchema).optional(),
|
|
4983
|
+
bars: zod.z.array(ChartSeriesBarJSONSchema)
|
|
4984
|
+
});
|
|
4985
|
+
const ChartItemBaseSchema = zod.z.object({
|
|
4640
4986
|
id: zod.z.string(),
|
|
4641
4987
|
type: zod.z.literal(JSON_TYPES.CHART),
|
|
4642
4988
|
name: zod.z.string().optional(),
|
|
4643
4989
|
title: zod.z.string().optional(),
|
|
4644
|
-
chartType: zod.z.enum(CHART_TYPES),
|
|
4645
4990
|
orientation: zod.z.enum(["vertical", "horizontal"]).default("vertical"),
|
|
4646
4991
|
grid: zod.z.boolean().default(false),
|
|
4647
4992
|
scale: zod.z.number().positive().default(1),
|
|
4648
4993
|
snap: zod.z.number().positive().optional(),
|
|
4649
4994
|
xAxis: ChartAxisJSONSchema.optional(),
|
|
4650
|
-
yAxis: ChartAxisJSONSchema.optional()
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4995
|
+
yAxis: ChartAxisJSONSchema.optional()
|
|
4996
|
+
});
|
|
4997
|
+
const ChartItemBarSchema = ChartItemBaseSchema.extend({
|
|
4998
|
+
chartType: zod.z.literal("bar"),
|
|
4999
|
+
bars: zod.z.array(ChartBarJSONSchema)
|
|
5000
|
+
});
|
|
5001
|
+
const ChartItemPictographSchema = ChartItemBaseSchema.extend({
|
|
5002
|
+
chartType: zod.z.literal("pictograph"),
|
|
5003
|
+
pictos: zod.z.array(ChartPictoJSONSchema)
|
|
5004
|
+
});
|
|
5005
|
+
const ChartItemGroupedBarSchema = ChartItemBaseSchema.extend({
|
|
5006
|
+
chartType: zod.z.literal("grouped-bar"),
|
|
5007
|
+
series: zod.z.array(ChartSeriesJSONSchema),
|
|
5008
|
+
groups: zod.z.array(ChartGroupJSONSchema),
|
|
5009
|
+
legend: zod.z.boolean().default(false)
|
|
5010
|
+
});
|
|
5011
|
+
const ChartItemSchema = zod.z.discriminatedUnion("chartType", [
|
|
5012
|
+
ChartItemBarSchema,
|
|
5013
|
+
ChartItemPictographSchema,
|
|
5014
|
+
ChartItemGroupedBarSchema
|
|
5015
|
+
]).superRefine((item, ctx) => {
|
|
4654
5016
|
if (item.chartType === "bar" && item.bars.length === 0) {
|
|
4655
5017
|
ctx.addIssue({
|
|
4656
5018
|
code: zod.z.ZodIssueCode.custom,
|
|
@@ -4658,18 +5020,63 @@ const ChartItemSchema = zod.z.object({
|
|
|
4658
5020
|
path: ["bars"]
|
|
4659
5021
|
});
|
|
4660
5022
|
}
|
|
4661
|
-
if (item.chartType === "pictograph"
|
|
4662
|
-
|
|
4663
|
-
|
|
4664
|
-
|
|
4665
|
-
|
|
4666
|
-
|
|
5023
|
+
if (item.chartType === "pictograph") {
|
|
5024
|
+
if (item.pictos.length === 0) {
|
|
5025
|
+
ctx.addIssue({
|
|
5026
|
+
code: zod.z.ZodIssueCode.custom,
|
|
5027
|
+
message: 'CHART[type="pictograph"]: at least one PICTO is required',
|
|
5028
|
+
path: ["pictos"]
|
|
5029
|
+
});
|
|
5030
|
+
}
|
|
5031
|
+
if (item.snap !== void 0) {
|
|
5032
|
+
ctx.addIssue({
|
|
5033
|
+
code: zod.z.ZodIssueCode.custom,
|
|
5034
|
+
message: 'CHART[type="pictograph"]: snap is not supported for pictograph charts',
|
|
5035
|
+
path: ["snap"]
|
|
5036
|
+
});
|
|
5037
|
+
}
|
|
4667
5038
|
}
|
|
4668
|
-
if (item.chartType === "
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
5039
|
+
if (item.chartType === "grouped-bar") {
|
|
5040
|
+
if (item.series.length === 0) {
|
|
5041
|
+
ctx.addIssue({
|
|
5042
|
+
code: zod.z.ZodIssueCode.custom,
|
|
5043
|
+
message: 'CHART[type="grouped-bar"]: at least one SERIES is required',
|
|
5044
|
+
path: ["series"]
|
|
5045
|
+
});
|
|
5046
|
+
}
|
|
5047
|
+
if (item.groups.length === 0) {
|
|
5048
|
+
ctx.addIssue({
|
|
5049
|
+
code: zod.z.ZodIssueCode.custom,
|
|
5050
|
+
message: 'CHART[type="grouped-bar"]: at least one GROUP is required',
|
|
5051
|
+
path: ["groups"]
|
|
5052
|
+
});
|
|
5053
|
+
}
|
|
5054
|
+
validateUniqueIds(item.series, ctx, (i) => ["series", i]);
|
|
5055
|
+
validateUniqueIds(item.groups, ctx, (i) => ["groups", i]);
|
|
5056
|
+
const allBars = item.groups.flatMap((g, gi) => g.bars.map((bar, bi) => ({ id: bar.id, gi, bi })));
|
|
5057
|
+
validateUniqueIds(allBars, ctx, (i) => ["groups", allBars[i].gi, "bars", allBars[i].bi]);
|
|
5058
|
+
const seriesIds = new Set(item.series.map((s) => s.id));
|
|
5059
|
+
item.groups.forEach((group, gi) => {
|
|
5060
|
+
const seenInGroup = /* @__PURE__ */ new Set();
|
|
5061
|
+
group.bars.forEach((bar, bi) => {
|
|
5062
|
+
if (!seriesIds.has(bar.seriesId)) {
|
|
5063
|
+
ctx.addIssue({
|
|
5064
|
+
code: zod.z.ZodIssueCode.custom,
|
|
5065
|
+
message: `CHART[type="grouped-bar"]: GROUP "${group.id}" bar "${bar.id}" references unknown seriesId "${bar.seriesId}" — no SERIES with that id is declared`,
|
|
5066
|
+
path: ["groups", gi, "bars", bi, "seriesId"]
|
|
5067
|
+
});
|
|
5068
|
+
return;
|
|
5069
|
+
}
|
|
5070
|
+
if (seenInGroup.has(bar.seriesId)) {
|
|
5071
|
+
ctx.addIssue({
|
|
5072
|
+
code: zod.z.ZodIssueCode.custom,
|
|
5073
|
+
message: `CHART[type="grouped-bar"]: GROUP "${group.id}" has two bars for seriesId "${bar.seriesId}" — each series can appear at most once per group`,
|
|
5074
|
+
path: ["groups", gi, "bars", bi, "seriesId"]
|
|
5075
|
+
});
|
|
5076
|
+
return;
|
|
5077
|
+
}
|
|
5078
|
+
seenInGroup.add(bar.seriesId);
|
|
5079
|
+
});
|
|
4673
5080
|
});
|
|
4674
5081
|
}
|
|
4675
5082
|
});
|
|
@@ -4677,6 +5084,8 @@ const StringInputJSONSchema = zod.z.object({
|
|
|
4677
5084
|
type: zod.z.literal(JSON_TYPES.INLINE_INPUT),
|
|
4678
5085
|
inputType: zod.z.literal(INPUT_TYPES.STRING),
|
|
4679
5086
|
length: zod.z.number().int().positive(),
|
|
5087
|
+
/** Box width in characters; defaults to `length`. */
|
|
5088
|
+
width: InlineInputWidthSchema,
|
|
4680
5089
|
placeholder: zod.z.string().max(32).optional(),
|
|
4681
5090
|
/** Gray, non-interactive, never graded (any answer for it is ignored). */
|
|
4682
5091
|
disabled: zod.z.boolean().optional()
|
|
@@ -4685,6 +5094,8 @@ const NumericInputJSONSchema = zod.z.object({
|
|
|
4685
5094
|
type: zod.z.literal(JSON_TYPES.INLINE_INPUT),
|
|
4686
5095
|
inputType: zod.z.literal(INPUT_TYPES.NUMERIC),
|
|
4687
5096
|
digits: zod.z.number().int().positive(),
|
|
5097
|
+
/** Box width in characters; defaults to `digits`. */
|
|
5098
|
+
width: InlineInputWidthSchema,
|
|
4688
5099
|
placeholder: zod.z.string().max(32).optional(),
|
|
4689
5100
|
/** Gray, non-interactive, never graded (any answer for it is ignored). */
|
|
4690
5101
|
disabled: zod.z.boolean().optional()
|
|
@@ -5675,7 +6086,7 @@ lodash.exports;
|
|
|
5675
6086
|
var reIsNative = RegExp2(
|
|
5676
6087
|
"^" + funcToString.call(hasOwnProperty).replace(reRegExpChar, "\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, "$1.*?") + "$"
|
|
5677
6088
|
);
|
|
5678
|
-
var Buffer = moduleExports ? context.Buffer : undefined$1,
|
|
6089
|
+
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;
|
|
5679
6090
|
var defineProperty = function() {
|
|
5680
6091
|
try {
|
|
5681
6092
|
var func = getNative(Object2, "defineProperty");
|
|
@@ -5690,7 +6101,7 @@ lodash.exports;
|
|
|
5690
6101
|
var metaMap = WeakMap && new WeakMap();
|
|
5691
6102
|
var realNames = {};
|
|
5692
6103
|
var dataViewCtorString = toSource(DataView), mapCtorString = toSource(Map2), promiseCtorString = toSource(Promise2), setCtorString = toSource(Set2), weakMapCtorString = toSource(WeakMap);
|
|
5693
|
-
var symbolProto =
|
|
6104
|
+
var symbolProto = Symbol2 ? Symbol2.prototype : undefined$1, symbolValueOf = symbolProto ? symbolProto.valueOf : undefined$1, symbolToString = symbolProto ? symbolProto.toString : undefined$1;
|
|
5694
6105
|
function lodash2(value) {
|
|
5695
6106
|
if (isObjectLike(value) && !isArray(value) && !(value instanceof LazyWrapper)) {
|
|
5696
6107
|
if (value instanceof LodashWrapper) {
|
|
@@ -10589,6 +11000,7 @@ function toJSON(ast) {
|
|
|
10589
11000
|
type: JSON_TYPES.INLINE_INPUT,
|
|
10590
11001
|
inputType: INPUT_TYPES.STRING,
|
|
10591
11002
|
length: inline.length,
|
|
11003
|
+
...inline.width !== void 0 && { width: inline.width },
|
|
10592
11004
|
placeholder: inline.placeholder,
|
|
10593
11005
|
...inline.disabled && { disabled: true }
|
|
10594
11006
|
};
|
|
@@ -10597,6 +11009,7 @@ function toJSON(ast) {
|
|
|
10597
11009
|
type: JSON_TYPES.INLINE_INPUT,
|
|
10598
11010
|
inputType: INPUT_TYPES.NUMERIC,
|
|
10599
11011
|
digits: inline.digits,
|
|
11012
|
+
...inline.width !== void 0 && { width: inline.width },
|
|
10600
11013
|
placeholder: inline.placeholder,
|
|
10601
11014
|
...inline.disabled && { disabled: true }
|
|
10602
11015
|
};
|
|
@@ -10692,7 +11105,7 @@ function toJSON(ast) {
|
|
|
10692
11105
|
options: lodashExports.shuffle(tool.options)
|
|
10693
11106
|
};
|
|
10694
11107
|
} else if (tool.type === AST_TYPES.NUMERIC_KEYPAD) {
|
|
10695
|
-
widgets[
|
|
11108
|
+
widgets[NUMERIC_KEYPAD_WIDGET_ID] = {
|
|
10696
11109
|
type: JSON_TYPES.NUMERIC_KEYPAD,
|
|
10697
11110
|
includeOperators: tool.includeOperators,
|
|
10698
11111
|
includeDecimal: tool.includeDecimal,
|
|
@@ -10768,6 +11181,8 @@ function collectAllInlines(blocks) {
|
|
|
10768
11181
|
for (const picto of chart.pictos) result.push(...picto.inlines);
|
|
10769
11182
|
if (chart.xAxis) result.push(...chart.xAxis.inlines);
|
|
10770
11183
|
if (chart.yAxis) result.push(...chart.yAxis.inlines);
|
|
11184
|
+
for (const series of chart.series) result.push(...series.inlines);
|
|
11185
|
+
for (const group of chart.groups) result.push(...group.labelInlines);
|
|
10771
11186
|
}
|
|
10772
11187
|
}
|
|
10773
11188
|
return result;
|
|
@@ -10802,27 +11217,19 @@ function transformChart(block, idGenerator, inlineDragIds) {
|
|
|
10802
11217
|
const chartAnswers = {};
|
|
10803
11218
|
const bars = block.bars.map((bar) => {
|
|
10804
11219
|
const label = toLabel(bar.inlines);
|
|
10805
|
-
|
|
10806
|
-
chartWidgets[bar.id] = { type: JSON_TYPES.CHART_BAR, value: bar.value };
|
|
10807
|
-
chartAnswers[bar.id] = bar.answer;
|
|
10808
|
-
return {
|
|
10809
|
-
id: bar.id,
|
|
10810
|
-
type: JSON_TYPES.CHART_BAR,
|
|
10811
|
-
interactive: true,
|
|
10812
|
-
value: bar.value,
|
|
10813
|
-
widgetId: bar.id,
|
|
10814
|
-
...bar.color !== void 0 && { color: bar.color },
|
|
10815
|
-
...label !== void 0 && { label }
|
|
10816
|
-
};
|
|
10817
|
-
}
|
|
10818
|
-
return {
|
|
11220
|
+
const common = {
|
|
10819
11221
|
id: bar.id,
|
|
10820
11222
|
type: JSON_TYPES.CHART_BAR,
|
|
10821
|
-
interactive: false,
|
|
10822
11223
|
value: bar.value,
|
|
10823
11224
|
...bar.color !== void 0 && { color: bar.color },
|
|
10824
11225
|
...label !== void 0 && { label }
|
|
10825
11226
|
};
|
|
11227
|
+
if (bar.answer !== void 0) {
|
|
11228
|
+
chartWidgets[bar.id] = { type: JSON_TYPES.CHART_BAR, value: bar.value };
|
|
11229
|
+
chartAnswers[bar.id] = bar.answer;
|
|
11230
|
+
return { ...common, interactive: true, widgetId: bar.id, answer: bar.answer };
|
|
11231
|
+
}
|
|
11232
|
+
return { ...common, interactive: false };
|
|
10826
11233
|
});
|
|
10827
11234
|
const pictos = block.pictos.map((picto) => {
|
|
10828
11235
|
const label = toLabel(picto.inlines);
|
|
@@ -10839,21 +11246,62 @@ function transformChart(block, idGenerator, inlineDragIds) {
|
|
|
10839
11246
|
...label !== void 0 && { label }
|
|
10840
11247
|
};
|
|
10841
11248
|
});
|
|
10842
|
-
const
|
|
11249
|
+
const series = block.series.map((s) => {
|
|
11250
|
+
const label = toLabel(s.inlines);
|
|
11251
|
+
return {
|
|
11252
|
+
id: s.id,
|
|
11253
|
+
color: s.color,
|
|
11254
|
+
...label !== void 0 && { label }
|
|
11255
|
+
};
|
|
11256
|
+
});
|
|
11257
|
+
const groups = block.groups.map((group) => {
|
|
11258
|
+
const slots = alignGroupBarsToSeries(group, block.series);
|
|
11259
|
+
const groupBars = slots.map((bar, i) => {
|
|
11260
|
+
if (bar === void 0) return void 0;
|
|
11261
|
+
const series2 = block.series[i];
|
|
11262
|
+
if (bar.answer !== void 0) {
|
|
11263
|
+
chartWidgets[bar.id] = { type: JSON_TYPES.CHART_BAR, value: bar.value };
|
|
11264
|
+
chartAnswers[bar.id] = bar.answer;
|
|
11265
|
+
}
|
|
11266
|
+
return {
|
|
11267
|
+
id: bar.id,
|
|
11268
|
+
type: JSON_TYPES.CHART_SERIES_BAR,
|
|
11269
|
+
value: bar.value,
|
|
11270
|
+
...bar.answer !== void 0 && { answer: bar.answer },
|
|
11271
|
+
color: series2.color,
|
|
11272
|
+
seriesId: series2.id
|
|
11273
|
+
};
|
|
11274
|
+
}).filter((bar) => bar !== void 0);
|
|
11275
|
+
const label = toLabel(group.labelInlines);
|
|
11276
|
+
return {
|
|
11277
|
+
id: group.id,
|
|
11278
|
+
...label !== void 0 && { label },
|
|
11279
|
+
bars: groupBars
|
|
11280
|
+
};
|
|
11281
|
+
});
|
|
11282
|
+
const base = {
|
|
10843
11283
|
id: idGenerator.generate(JSON_TYPES.CHART),
|
|
10844
11284
|
type: JSON_TYPES.CHART,
|
|
10845
11285
|
...block.name !== void 0 && { name: block.name },
|
|
10846
11286
|
...block.title !== void 0 && { title: block.title },
|
|
10847
|
-
chartType: block.chartType,
|
|
10848
11287
|
orientation,
|
|
10849
11288
|
grid: block.grid,
|
|
10850
11289
|
scale: block.scale,
|
|
10851
11290
|
...block.snap !== void 0 && { snap: block.snap },
|
|
10852
11291
|
...xAxis !== void 0 && { xAxis },
|
|
10853
|
-
...yAxis !== void 0 && { yAxis }
|
|
10854
|
-
bars,
|
|
10855
|
-
pictos
|
|
11292
|
+
...yAxis !== void 0 && { yAxis }
|
|
10856
11293
|
};
|
|
11294
|
+
let item;
|
|
11295
|
+
if (block.chartType === "bar") {
|
|
11296
|
+
item = { ...base, chartType: "bar", bars };
|
|
11297
|
+
} else if (block.chartType === "pictograph") {
|
|
11298
|
+
item = { ...base, chartType: "pictograph", pictos };
|
|
11299
|
+
} else if (block.chartType === "grouped-bar") {
|
|
11300
|
+
item = { ...base, chartType: "grouped-bar", series, groups, legend: block.legend };
|
|
11301
|
+
} else {
|
|
11302
|
+
const unhandled = block.chartType;
|
|
11303
|
+
throw new Error(`CHART: unsupported chart type "${String(unhandled)}"`);
|
|
11304
|
+
}
|
|
10857
11305
|
return { item, chartWidgets, chartAnswers };
|
|
10858
11306
|
}
|
|
10859
11307
|
function transformNumberLine(numberLine, idGenerator, inlineDragIds) {
|
|
@@ -10920,7 +11368,7 @@ function transformNumberLine(numberLine, idGenerator, inlineDragIds) {
|
|
|
10920
11368
|
max: numberLine.max,
|
|
10921
11369
|
step: numberLine.step,
|
|
10922
11370
|
...numberLine.minorTicks !== void 0 && { minorTicks: numberLine.minorTicks },
|
|
10923
|
-
|
|
11371
|
+
style: numberLine.style,
|
|
10924
11372
|
...numberLine.labelVisibility !== void 0 && { labelVisibility: numberLine.labelVisibility },
|
|
10925
11373
|
...counterItem !== void 0 && { counter: counterItem },
|
|
10926
11374
|
...highlights !== void 0 && { highlights },
|
|
@@ -15577,22 +16025,22 @@ function createDefaultDropAnimation(options) {
|
|
|
15577
16025
|
easing,
|
|
15578
16026
|
fill: "forwards"
|
|
15579
16027
|
});
|
|
15580
|
-
return new Promise((
|
|
16028
|
+
return new Promise((resolve2) => {
|
|
15581
16029
|
animation.onfinish = () => {
|
|
15582
16030
|
cleanup == null ? void 0 : cleanup();
|
|
15583
|
-
|
|
16031
|
+
resolve2();
|
|
15584
16032
|
};
|
|
15585
16033
|
});
|
|
15586
16034
|
};
|
|
15587
16035
|
}
|
|
15588
|
-
let key = 0;
|
|
16036
|
+
let key$1 = 0;
|
|
15589
16037
|
function useKey(id) {
|
|
15590
16038
|
return React.useMemo(() => {
|
|
15591
16039
|
if (id == null) {
|
|
15592
16040
|
return;
|
|
15593
16041
|
}
|
|
15594
|
-
key++;
|
|
15595
|
-
return key;
|
|
16042
|
+
key$1++;
|
|
16043
|
+
return key$1;
|
|
15596
16044
|
}, [id]);
|
|
15597
16045
|
}
|
|
15598
16046
|
const DragOverlay = /* @__PURE__ */ React.memo((_ref) => {
|
|
@@ -15674,6 +16122,7 @@ const DEFAULT_SOUNDS = {
|
|
|
15674
16122
|
"drag-start": dragStart,
|
|
15675
16123
|
"drag-drop": dragDrop,
|
|
15676
16124
|
"drag-cancel": dragDrop,
|
|
16125
|
+
"drag-hint": buttonClick,
|
|
15677
16126
|
"radio-select": buttonClick,
|
|
15678
16127
|
"pair-select": buttonClick,
|
|
15679
16128
|
"pair-link": buttonClick,
|
|
@@ -15777,14 +16226,24 @@ function useExerciseDnd() {
|
|
|
15777
16226
|
}
|
|
15778
16227
|
return ctx;
|
|
15779
16228
|
}
|
|
16229
|
+
const NO_EXERCISE = Symbol("no-exercise");
|
|
15780
16230
|
const ExerciseDndProvider = ({
|
|
15781
16231
|
children,
|
|
15782
16232
|
droppedSources,
|
|
15783
|
-
onDragEnd
|
|
16233
|
+
onDragEnd,
|
|
16234
|
+
exerciseKey
|
|
15784
16235
|
}) => {
|
|
15785
16236
|
const [activeItem, setActiveItem] = React.useState(null);
|
|
16237
|
+
const [draggedExerciseKey, setDraggedExerciseKey] = React.useState(NO_EXERCISE);
|
|
16238
|
+
const hasDraggedOption = draggedExerciseKey === exerciseKey;
|
|
15786
16239
|
const [dropHandlers] = React.useState(() => /* @__PURE__ */ new Map());
|
|
15787
16240
|
const playSound = useSound();
|
|
16241
|
+
const hintSoundExerciseRef = React.useRef(NO_EXERCISE);
|
|
16242
|
+
const playHintSound = React.useCallback(() => {
|
|
16243
|
+
if (hintSoundExerciseRef.current === exerciseKey) return;
|
|
16244
|
+
hintSoundExerciseRef.current = exerciseKey;
|
|
16245
|
+
playSound("drag-hint");
|
|
16246
|
+
}, [playSound, exerciseKey]);
|
|
15788
16247
|
const usedOptionIds = React.useMemo(() => {
|
|
15789
16248
|
return new Set(droppedSources.values());
|
|
15790
16249
|
}, [droppedSources]);
|
|
@@ -15808,6 +16267,7 @@ const ExerciseDndProvider = ({
|
|
|
15808
16267
|
const raw = active.data.current;
|
|
15809
16268
|
const item = raw?.type === "placed-chip" ? { type: "placed-chip", widgetId: raw.widgetId, label: raw.label, sourceOptionId: raw.sourceOptionId } : raw;
|
|
15810
16269
|
setActiveItem(item);
|
|
16270
|
+
if (item.type === "drag-option") setDraggedExerciseKey(exerciseKey);
|
|
15811
16271
|
playSound("drag-start");
|
|
15812
16272
|
};
|
|
15813
16273
|
const handleDragEnd = (event) => {
|
|
@@ -15833,6 +16293,9 @@ const ExerciseDndProvider = ({
|
|
|
15833
16293
|
value: {
|
|
15834
16294
|
activeItem,
|
|
15835
16295
|
isDragging: activeItem !== null,
|
|
16296
|
+
exerciseKey,
|
|
16297
|
+
hasDraggedOption,
|
|
16298
|
+
playHintSound,
|
|
15836
16299
|
usedOptionIds,
|
|
15837
16300
|
droppedSources,
|
|
15838
16301
|
registerDropHandler,
|
|
@@ -16613,9 +17076,40 @@ function isUndoDrop(event, dragId) {
|
|
|
16613
17076
|
const LockContext = React.createContext(false);
|
|
16614
17077
|
const ExerciseLockProvider = ({ locked, children }) => /* @__PURE__ */ jsxRuntime.jsx(LockContext.Provider, { value: locked, children });
|
|
16615
17078
|
const useExerciseLocked = () => React.useContext(LockContext);
|
|
16616
|
-
const
|
|
16617
|
-
|
|
16618
|
-
|
|
17079
|
+
const OVERLAY_STYLE = {
|
|
17080
|
+
position: "absolute",
|
|
17081
|
+
top: 0,
|
|
17082
|
+
left: 0,
|
|
17083
|
+
width: "100%",
|
|
17084
|
+
height: "100%",
|
|
17085
|
+
minWidth: 0,
|
|
17086
|
+
maxWidth: "none",
|
|
17087
|
+
margin: 0,
|
|
17088
|
+
boxSizing: "border-box"
|
|
17089
|
+
};
|
|
17090
|
+
const SlotSizer = ({
|
|
17091
|
+
style,
|
|
17092
|
+
...props
|
|
17093
|
+
}) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
17094
|
+
"input",
|
|
17095
|
+
{
|
|
17096
|
+
...props,
|
|
17097
|
+
type: "text",
|
|
17098
|
+
value: "",
|
|
17099
|
+
readOnly: true,
|
|
17100
|
+
tabIndex: -1,
|
|
17101
|
+
"aria-hidden": "true",
|
|
17102
|
+
style: { ...style, visibility: "hidden" }
|
|
17103
|
+
}
|
|
17104
|
+
);
|
|
17105
|
+
const PlacedInputChip = ({
|
|
17106
|
+
widgetId,
|
|
17107
|
+
value,
|
|
17108
|
+
className = "",
|
|
17109
|
+
onRemove
|
|
17110
|
+
}) => {
|
|
17111
|
+
const { droppedSources } = useExerciseDnd();
|
|
17112
|
+
const locked = useExerciseLocked();
|
|
16619
17113
|
const sourceOptionId = droppedSources.get(widgetId);
|
|
16620
17114
|
const dragId = `${widgetId}__chip`;
|
|
16621
17115
|
const { attributes, listeners, setNodeRef, isDragging } = useDraggable({
|
|
@@ -16637,13 +17131,41 @@ const PlacedInputChip = ({ widgetId, value, className = "", onRemove }) => {
|
|
|
16637
17131
|
if (!locked && e.detail !== 0) onRemove();
|
|
16638
17132
|
},
|
|
16639
17133
|
title: locked ? void 0 : "Drag to move, or click to remove",
|
|
16640
|
-
style: {
|
|
17134
|
+
style: {
|
|
17135
|
+
...OVERLAY_STYLE,
|
|
17136
|
+
touchAction: "none",
|
|
17137
|
+
cursor: locked ? "default" : "grab"
|
|
17138
|
+
},
|
|
16641
17139
|
...listeners,
|
|
16642
17140
|
...attributes,
|
|
16643
17141
|
children: value
|
|
16644
17142
|
}
|
|
16645
17143
|
);
|
|
16646
17144
|
};
|
|
17145
|
+
function getMaxInputLength(exercise, inputKey) {
|
|
17146
|
+
const widget = exercise.widgets[inputKey];
|
|
17147
|
+
if (widget?.type !== JSON_TYPES.INLINE_INPUT) return void 0;
|
|
17148
|
+
return widget.inputType === INPUT_TYPES.NUMERIC ? widget.digits : widget.length;
|
|
17149
|
+
}
|
|
17150
|
+
function getInputElementId(key2) {
|
|
17151
|
+
return `${key2}-input`;
|
|
17152
|
+
}
|
|
17153
|
+
function getTextInputs(container) {
|
|
17154
|
+
return Array.from(
|
|
17155
|
+
container.querySelectorAll(
|
|
17156
|
+
'input[type="text"], input[type="number"], textarea, select'
|
|
17157
|
+
)
|
|
17158
|
+
);
|
|
17159
|
+
}
|
|
17160
|
+
function canTakeInput(el) {
|
|
17161
|
+
return !el.readOnly && !el.disabled;
|
|
17162
|
+
}
|
|
17163
|
+
function findNextInput(container, currentKey) {
|
|
17164
|
+
const inputs = getTextInputs(container);
|
|
17165
|
+
const currentIndex = inputs.findIndex((el) => el.id === getInputElementId(currentKey));
|
|
17166
|
+
if (currentIndex === -1) return null;
|
|
17167
|
+
return inputs.slice(currentIndex + 1).find(canTakeInput) ?? null;
|
|
17168
|
+
}
|
|
16647
17169
|
const InlineInput = ({
|
|
16648
17170
|
widgetId,
|
|
16649
17171
|
value,
|
|
@@ -16652,6 +17174,7 @@ const InlineInput = ({
|
|
|
16652
17174
|
inputType,
|
|
16653
17175
|
length,
|
|
16654
17176
|
digits,
|
|
17177
|
+
width,
|
|
16655
17178
|
placeholder,
|
|
16656
17179
|
disabled = false,
|
|
16657
17180
|
onChange,
|
|
@@ -16665,8 +17188,8 @@ const InlineInput = ({
|
|
|
16665
17188
|
const handleDrop = React.useCallback((item) => {
|
|
16666
17189
|
return applyDropToInput(widgetId, item, onChange);
|
|
16667
17190
|
}, [onChange, widgetId]);
|
|
16668
|
-
const expectedLength = inputType ===
|
|
16669
|
-
const dynamicWidth = `${(expectedLength
|
|
17191
|
+
const expectedLength = inputType === INPUT_TYPES.NUMERIC ? digits : length;
|
|
17192
|
+
const dynamicWidth = `${(width ?? expectedLength ?? 5) * 0.8 + 1}em`;
|
|
16670
17193
|
const statusClass = validationStatus ? ` widget--${validationStatus}` : "";
|
|
16671
17194
|
const disabledClass = disabled ? " inline-input--disabled" : "";
|
|
16672
17195
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -16679,21 +17202,31 @@ const InlineInput = ({
|
|
|
16679
17202
|
className: "inline-widget inline-input-droppable",
|
|
16680
17203
|
children: isDropped && value !== "" ? (
|
|
16681
17204
|
// Show as chip only when value came from drag-drop
|
|
16682
|
-
/* @__PURE__ */ jsxRuntime.
|
|
16683
|
-
|
|
16684
|
-
|
|
16685
|
-
|
|
16686
|
-
|
|
16687
|
-
|
|
16688
|
-
|
|
16689
|
-
|
|
16690
|
-
|
|
17205
|
+
/* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
17206
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
17207
|
+
SlotSizer,
|
|
17208
|
+
{
|
|
17209
|
+
className: `inline-input${disabledClass}`,
|
|
17210
|
+
style: { width: dynamicWidth },
|
|
17211
|
+
placeholder
|
|
17212
|
+
}
|
|
17213
|
+
),
|
|
17214
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
17215
|
+
PlacedInputChip,
|
|
17216
|
+
{
|
|
17217
|
+
widgetId,
|
|
17218
|
+
value,
|
|
17219
|
+
className: statusClass.trim(),
|
|
17220
|
+
onRemove: () => handleChange("", false)
|
|
17221
|
+
}
|
|
17222
|
+
)
|
|
17223
|
+
] })
|
|
16691
17224
|
) : (
|
|
16692
17225
|
// Regular input for typing
|
|
16693
17226
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
16694
17227
|
"input",
|
|
16695
17228
|
{
|
|
16696
|
-
id: widgetId
|
|
17229
|
+
id: getInputElementId(widgetId),
|
|
16697
17230
|
type: "text",
|
|
16698
17231
|
className: `inline-input${statusClass}${disabledClass}`,
|
|
16699
17232
|
value,
|
|
@@ -16713,15 +17246,20 @@ const InlineInput = ({
|
|
|
16713
17246
|
}
|
|
16714
17247
|
);
|
|
16715
17248
|
};
|
|
16716
|
-
const SingleDragOption = ({
|
|
17249
|
+
const SingleDragOption = (props) => {
|
|
17250
|
+
const { exerciseKey } = useExerciseDnd();
|
|
17251
|
+
return /* @__PURE__ */ jsxRuntime.jsx(DragOptionChip, { ...props }, exerciseKey ?? "");
|
|
17252
|
+
};
|
|
17253
|
+
const DragOptionChip = ({
|
|
16717
17254
|
optionId,
|
|
16718
17255
|
value,
|
|
16719
17256
|
onOptionClick,
|
|
16720
17257
|
className = ""
|
|
16721
17258
|
}) => {
|
|
16722
|
-
const { usedOptionIds } = useExerciseDnd();
|
|
17259
|
+
const { usedOptionIds, hasDraggedOption, playHintSound } = useExerciseDnd();
|
|
16723
17260
|
const locked = useExerciseLocked();
|
|
16724
17261
|
const isUsed = usedOptionIds.has(optionId);
|
|
17262
|
+
const { jiggling: hinting, onAnimationEnd: onHintEnd } = useJiggle(hasDraggedOption);
|
|
16725
17263
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `drag-option-slot ${className}`.trim(), children: isUsed ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "drag-option-empty", "aria-hidden": "true" }) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
16726
17264
|
Draggable,
|
|
16727
17265
|
{
|
|
@@ -16733,7 +17271,11 @@ const SingleDragOption = ({
|
|
|
16733
17271
|
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
16734
17272
|
"button",
|
|
16735
17273
|
{
|
|
16736
|
-
className: "drag-option",
|
|
17274
|
+
className: hinting && !locked ? "drag-option drag-option--hint" : "drag-option",
|
|
17275
|
+
onAnimationStart: () => {
|
|
17276
|
+
if (hinting) playHintSound();
|
|
17277
|
+
},
|
|
17278
|
+
onAnimationEnd: onHintEnd,
|
|
16737
17279
|
onClick: () => {
|
|
16738
17280
|
if (!locked) onOptionClick?.(value);
|
|
16739
17281
|
},
|
|
@@ -16862,23 +17404,23 @@ const NumericKeypad = ({
|
|
|
16862
17404
|
playSound("keypad-press");
|
|
16863
17405
|
onChange(value.slice(0, -1));
|
|
16864
17406
|
};
|
|
16865
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `numeric-keypad ${includeOperators ? "numeric-keypad-with-operators" : ""}`, children: [
|
|
16866
|
-
/* @__PURE__ */ jsxRuntime.jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-digit", onClick: () => handleDigit("1"), children: "1" }),
|
|
16867
|
-
/* @__PURE__ */ jsxRuntime.jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-digit", onClick: () => handleDigit("2"), children: "2" }),
|
|
16868
|
-
/* @__PURE__ */ jsxRuntime.jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-digit", onClick: () => handleDigit("3"), children: "3" }),
|
|
16869
|
-
includeOperators && /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-operator", onClick: () => handleDigit("+"), children: "+" }),
|
|
16870
|
-
/* @__PURE__ */ jsxRuntime.jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-digit", onClick: () => handleDigit("4"), children: "4" }),
|
|
16871
|
-
/* @__PURE__ */ jsxRuntime.jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-digit", onClick: () => handleDigit("5"), children: "5" }),
|
|
16872
|
-
/* @__PURE__ */ jsxRuntime.jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-digit", onClick: () => handleDigit("6"), children: "6" }),
|
|
16873
|
-
includeOperators && /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-operator", onClick: () => handleDigit("-"), children: "-" }),
|
|
16874
|
-
/* @__PURE__ */ jsxRuntime.jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-digit", onClick: () => handleDigit("7"), children: "7" }),
|
|
16875
|
-
/* @__PURE__ */ jsxRuntime.jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-digit", onClick: () => handleDigit("8"), children: "8" }),
|
|
16876
|
-
/* @__PURE__ */ jsxRuntime.jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-digit", onClick: () => handleDigit("9"), children: "9" }),
|
|
16877
|
-
includeOperators && /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-operator", onClick: () => handleDigit("×"), children: "×" }),
|
|
16878
|
-
includeDecimal ? /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-decimal", onClick: () => handleDigit(","), children: "," }) : /* @__PURE__ */ jsxRuntime.jsx("div", {}),
|
|
16879
|
-
/* @__PURE__ */ jsxRuntime.jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-digit", onClick: () => handleDigit("0"), children: "0" }),
|
|
16880
|
-
/* @__PURE__ */ jsxRuntime.jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-backspace", onClick: handleBackspace, children: "⌫" }),
|
|
16881
|
-
includeOperators && /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-operator", onClick: () => handleDigit("÷"), children: "÷" })
|
|
17407
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `numeric-keypad ${includeOperators ? "numeric-keypad-with-operators" : ""}`, "data-tutorial": "keypad", children: [
|
|
17408
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-digit", "data-key": "1", onClick: () => handleDigit("1"), children: "1" }),
|
|
17409
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-digit", "data-key": "2", onClick: () => handleDigit("2"), children: "2" }),
|
|
17410
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-digit", "data-key": "3", onClick: () => handleDigit("3"), children: "3" }),
|
|
17411
|
+
includeOperators && /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-operator", "data-key": "+", onClick: () => handleDigit("+"), children: "+" }),
|
|
17412
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-digit", "data-key": "4", onClick: () => handleDigit("4"), children: "4" }),
|
|
17413
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-digit", "data-key": "5", onClick: () => handleDigit("5"), children: "5" }),
|
|
17414
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-digit", "data-key": "6", onClick: () => handleDigit("6"), children: "6" }),
|
|
17415
|
+
includeOperators && /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-operator", "data-key": "-", onClick: () => handleDigit("-"), children: "-" }),
|
|
17416
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-digit", "data-key": "7", onClick: () => handleDigit("7"), children: "7" }),
|
|
17417
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-digit", "data-key": "8", onClick: () => handleDigit("8"), children: "8" }),
|
|
17418
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-digit", "data-key": "9", onClick: () => handleDigit("9"), children: "9" }),
|
|
17419
|
+
includeOperators && /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-operator", "data-key": "×", onClick: () => handleDigit("×"), children: "×" }),
|
|
17420
|
+
includeDecimal ? /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-decimal", "data-key": ",", onClick: () => handleDigit(","), children: "," }) : /* @__PURE__ */ jsxRuntime.jsx("div", {}),
|
|
17421
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-digit", "data-key": "0", onClick: () => handleDigit("0"), children: "0" }),
|
|
17422
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-backspace", "data-key": "backspace", onClick: handleBackspace, children: "⌫" }),
|
|
17423
|
+
includeOperators && /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: locked, className: "keypad-btn keypad-btn-operator", "data-key": "÷", onClick: () => handleDigit("÷"), children: "÷" })
|
|
16882
17424
|
] });
|
|
16883
17425
|
};
|
|
16884
17426
|
const FractionInput = ({
|
|
@@ -16918,18 +17460,28 @@ const FractionInput = ({
|
|
|
16918
17460
|
onDrop: handleDrop(subKey),
|
|
16919
17461
|
disabled: locked,
|
|
16920
17462
|
className: "fraction-slot-droppable",
|
|
16921
|
-
children: isDroppedHere && currentValue !== "" ? /* @__PURE__ */ jsxRuntime.
|
|
16922
|
-
|
|
16923
|
-
|
|
16924
|
-
|
|
16925
|
-
|
|
16926
|
-
|
|
16927
|
-
|
|
16928
|
-
|
|
16929
|
-
|
|
17463
|
+
children: isDroppedHere && currentValue !== "" ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
17464
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
17465
|
+
SlotSizer,
|
|
17466
|
+
{
|
|
17467
|
+
className: "inline-input fraction-input",
|
|
17468
|
+
size: placeholder ? placeholder.length : void 0,
|
|
17469
|
+
placeholder
|
|
17470
|
+
}
|
|
17471
|
+
),
|
|
17472
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
17473
|
+
PlacedInputChip,
|
|
17474
|
+
{
|
|
17475
|
+
widgetId: subKey,
|
|
17476
|
+
value: currentValue,
|
|
17477
|
+
className: `fraction-chip${slotClass}`,
|
|
17478
|
+
onRemove: () => onChange(subKey, "", false)
|
|
17479
|
+
}
|
|
17480
|
+
)
|
|
17481
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
16930
17482
|
"input",
|
|
16931
17483
|
{
|
|
16932
|
-
id:
|
|
17484
|
+
id: getInputElementId(subKey),
|
|
16933
17485
|
type: "text",
|
|
16934
17486
|
className: `inline-input fraction-input${slotClass}`,
|
|
16935
17487
|
value: currentValue,
|
|
@@ -17518,12 +18070,17 @@ const GridSymmetry = ({
|
|
|
17518
18070
|
}
|
|
17519
18071
|
) });
|
|
17520
18072
|
};
|
|
18073
|
+
function interactiveBarKey(bar) {
|
|
18074
|
+
if (bar.answer === void 0 && bar.interactive !== true) return void 0;
|
|
18075
|
+
return bar.widgetId ?? bar.id;
|
|
18076
|
+
}
|
|
17521
18077
|
function getChartClassName(item) {
|
|
17522
18078
|
const isHorizontal = item.orientation === "horizontal";
|
|
17523
18079
|
return [
|
|
17524
18080
|
"chart",
|
|
17525
18081
|
isHorizontal ? "chart--horizontal" : "",
|
|
17526
18082
|
item.chartType === "pictograph" ? "chart--pictograph" : "",
|
|
18083
|
+
item.chartType === "grouped-bar" ? "chart--grouped-bar" : "",
|
|
17527
18084
|
item.xAxis?.visible === false ? "chart--no-xaxis" : "",
|
|
17528
18085
|
item.yAxis?.visible === false ? "chart--no-yaxis" : ""
|
|
17529
18086
|
].filter(Boolean).join(" ");
|
|
@@ -17534,7 +18091,8 @@ function resolveItemValue(id, defaultValue2, values) {
|
|
|
17534
18091
|
return isNaN(parsed) ? defaultValue2 : parsed;
|
|
17535
18092
|
}
|
|
17536
18093
|
function resolveValue(bar, values) {
|
|
17537
|
-
|
|
18094
|
+
const widgetKey = interactiveBarKey(bar);
|
|
18095
|
+
return widgetKey !== void 0 ? resolveItemValue(widgetKey, bar.value, values) : bar.value;
|
|
17538
18096
|
}
|
|
17539
18097
|
function clamp(v, min, max) {
|
|
17540
18098
|
return Math.min(max, Math.max(min, v));
|
|
@@ -17593,6 +18151,8 @@ const CHART_METRICS = {
|
|
|
17593
18151
|
gapWidth: 1,
|
|
17594
18152
|
axisWidth: 2.35,
|
|
17595
18153
|
barWidth: 3.35,
|
|
18154
|
+
// Fixed narrow width so several series fit in one cluster; not derived from count.
|
|
18155
|
+
groupedBarWidth: 1.8,
|
|
17596
18156
|
fontSize: 0.9,
|
|
17597
18157
|
labelFontSize: 0.98,
|
|
17598
18158
|
handleSize: 1.15,
|
|
@@ -17645,6 +18205,41 @@ function computePictographChartStyle(scale, columnCount, totalSlots, orientation
|
|
|
17645
18205
|
"--chart-height": slotSpan
|
|
17646
18206
|
};
|
|
17647
18207
|
}
|
|
18208
|
+
function computeGroupedChartStyle(scale, groupCount, seriesCount, orientation = "vertical", isInteractive = false) {
|
|
18209
|
+
const visualScale = clamp(scale, CHART_METRICS.scaleMin, CHART_METRICS.scaleMax);
|
|
18210
|
+
const barWidth = (isInteractive ? CHART_METRICS.barWidth : CHART_METRICS.groupedBarWidth) * visualScale;
|
|
18211
|
+
const barGap = CHART_METRICS.gapWidth * visualScale * 0.5;
|
|
18212
|
+
const groupGap = CHART_METRICS.gapWidth * visualScale * 2;
|
|
18213
|
+
const seriesPerGroup = Math.max(1, seriesCount);
|
|
18214
|
+
const groupWidth = seriesPerGroup * barWidth + Math.max(0, seriesPerGroup - 1) * barGap;
|
|
18215
|
+
const trackLength = groupCount * groupWidth + Math.max(0, groupCount - 1) * groupGap + groupGap * 2;
|
|
18216
|
+
const cssVars = {
|
|
18217
|
+
"--chart-axis-width": `${CHART_METRICS.axisWidth * visualScale}rem`,
|
|
18218
|
+
"--chart-bar-width": `${barWidth}rem`,
|
|
18219
|
+
// A bar slot inside a group is just the bar; reuse the plain bar-chart var.
|
|
18220
|
+
"--chart-bar-slot-width": `${barWidth}rem`,
|
|
18221
|
+
"--chart-bar-gap": `${barGap}rem`,
|
|
18222
|
+
"--chart-group-gap": `${groupGap}rem`,
|
|
18223
|
+
"--chart-group-width": `${groupWidth}rem`,
|
|
18224
|
+
"--chart-font-size": `${CHART_METRICS.fontSize * visualScale}rem`,
|
|
18225
|
+
"--chart-label-font-size": `${CHART_METRICS.labelFontSize * visualScale}rem`,
|
|
18226
|
+
"--chart-handle-size": `${CHART_METRICS.handleSize * visualScale}rem`
|
|
18227
|
+
};
|
|
18228
|
+
if (orientation === "horizontal") {
|
|
18229
|
+
const valueWidth = CHART_METRICS.chartHeight * visualScale;
|
|
18230
|
+
return {
|
|
18231
|
+
...cssVars,
|
|
18232
|
+
"--chart-height": `${trackLength}rem`,
|
|
18233
|
+
"--chart-h-value-width": `${valueWidth}rem`,
|
|
18234
|
+
"--chart-content-width": `${valueWidth}rem`
|
|
18235
|
+
};
|
|
18236
|
+
}
|
|
18237
|
+
return {
|
|
18238
|
+
...cssVars,
|
|
18239
|
+
"--chart-height": `${CHART_METRICS.chartHeight * visualScale}rem`,
|
|
18240
|
+
"--chart-content-width": `${trackLength}rem`
|
|
18241
|
+
};
|
|
18242
|
+
}
|
|
17648
18243
|
function computeHorizontalChartStyle(scale, barCount) {
|
|
17649
18244
|
const { trackLength, visualScale, cssVars } = computeBaseChartVars(scale, barCount);
|
|
17650
18245
|
const valueWidth = CHART_METRICS.chartHeight * visualScale;
|
|
@@ -17696,6 +18291,7 @@ const DraggableBar = ({
|
|
|
17696
18291
|
const axisMax = axis.max ?? CHART_AXIS_DEFAULTS.max;
|
|
17697
18292
|
const axisStep = axis.step ?? CHART_AXIS_DEFAULTS.step;
|
|
17698
18293
|
const snapStep = snap ?? axisStep;
|
|
18294
|
+
const widgetKey = bar.widgetId ?? bar.id;
|
|
17699
18295
|
const fillFromPointer = React.useCallback(
|
|
17700
18296
|
(clientY, clientX) => {
|
|
17701
18297
|
const track = trackRef.current;
|
|
@@ -17713,10 +18309,10 @@ const DraggableBar = ({
|
|
|
17713
18309
|
if (snapped !== lastSnap.current) {
|
|
17714
18310
|
lastSnap.current = snapped;
|
|
17715
18311
|
playSound("bar-step");
|
|
17716
|
-
onChange(
|
|
18312
|
+
onChange(widgetKey, String(snapped));
|
|
17717
18313
|
}
|
|
17718
18314
|
},
|
|
17719
|
-
[axisMin, axisMax, snapStep,
|
|
18315
|
+
[axisMin, axisMax, snapStep, widgetKey, onChange, orientation, playSound]
|
|
17720
18316
|
);
|
|
17721
18317
|
const onPointerDown = React.useCallback(
|
|
17722
18318
|
(e) => {
|
|
@@ -17749,8 +18345,8 @@ const DraggableBar = ({
|
|
|
17749
18345
|
if (!dragging.current) return;
|
|
17750
18346
|
dragging.current = false;
|
|
17751
18347
|
setIsDragging(false);
|
|
17752
|
-
onChange(
|
|
17753
|
-
}, [
|
|
18348
|
+
onChange(widgetKey, String(preDragValue.current));
|
|
18349
|
+
}, [widgetKey, onChange]);
|
|
17754
18350
|
const onKeyDown = React.useCallback(
|
|
17755
18351
|
(e) => {
|
|
17756
18352
|
if (exerciseLocked) return;
|
|
@@ -17759,22 +18355,22 @@ const DraggableBar = ({
|
|
|
17759
18355
|
if (isInc) {
|
|
17760
18356
|
e.preventDefault();
|
|
17761
18357
|
playSound("bar-step");
|
|
17762
|
-
onChange(
|
|
18358
|
+
onChange(widgetKey, String(nextStepValue(currentValue, axis, 1, snap)));
|
|
17763
18359
|
} else if (isDec) {
|
|
17764
18360
|
e.preventDefault();
|
|
17765
18361
|
playSound("bar-step");
|
|
17766
|
-
onChange(
|
|
18362
|
+
onChange(widgetKey, String(nextStepValue(currentValue, axis, -1, snap)));
|
|
17767
18363
|
} else if (e.key === "Home") {
|
|
17768
18364
|
e.preventDefault();
|
|
17769
18365
|
playSound("bar-step");
|
|
17770
|
-
onChange(
|
|
18366
|
+
onChange(widgetKey, String(axisMin));
|
|
17771
18367
|
} else if (e.key === "End") {
|
|
17772
18368
|
e.preventDefault();
|
|
17773
18369
|
playSound("bar-step");
|
|
17774
|
-
onChange(
|
|
18370
|
+
onChange(widgetKey, String(axisMax));
|
|
17775
18371
|
}
|
|
17776
18372
|
},
|
|
17777
|
-
[axisMin, axisMax,
|
|
18373
|
+
[axisMin, axisMax, widgetKey, currentValue, axis, snap, onChange, playSound, exerciseLocked]
|
|
17778
18374
|
);
|
|
17779
18375
|
const fill = toFill(currentValue, axis);
|
|
17780
18376
|
const isAtMin = currentValue <= axisMin;
|
|
@@ -17850,7 +18446,7 @@ const StaticBar = ({
|
|
|
17850
18446
|
}
|
|
17851
18447
|
) });
|
|
17852
18448
|
};
|
|
17853
|
-
const EMPTY_SET$
|
|
18449
|
+
const EMPTY_SET$4 = /* @__PURE__ */ new Set();
|
|
17854
18450
|
const XAxisLabel = ({
|
|
17855
18451
|
bar,
|
|
17856
18452
|
widgets,
|
|
@@ -17865,14 +18461,14 @@ const XAxisLabel = ({
|
|
|
17865
18461
|
item: subItem,
|
|
17866
18462
|
widgets,
|
|
17867
18463
|
values,
|
|
17868
|
-
droppedInputs: EMPTY_SET$
|
|
18464
|
+
droppedInputs: EMPTY_SET$4,
|
|
17869
18465
|
validationDetails,
|
|
17870
18466
|
onChange
|
|
17871
18467
|
},
|
|
17872
18468
|
subItem.id
|
|
17873
18469
|
)) }) });
|
|
17874
18470
|
};
|
|
17875
|
-
const EMPTY_SET$
|
|
18471
|
+
const EMPTY_SET$3 = /* @__PURE__ */ new Set();
|
|
17876
18472
|
const BarChart = ({
|
|
17877
18473
|
item,
|
|
17878
18474
|
widgets,
|
|
@@ -17880,6 +18476,7 @@ const BarChart = ({
|
|
|
17880
18476
|
validationDetails,
|
|
17881
18477
|
onChange
|
|
17882
18478
|
}) => {
|
|
18479
|
+
if (item.chartType !== "bar") return null;
|
|
17883
18480
|
const { bars, grid } = item;
|
|
17884
18481
|
const isHorizontal = item.orientation === "horizontal";
|
|
17885
18482
|
const valueAxis = isHorizontal ? item.xAxis : item.yAxis;
|
|
@@ -17893,7 +18490,7 @@ const BarChart = ({
|
|
|
17893
18490
|
item: subItem,
|
|
17894
18491
|
widgets,
|
|
17895
18492
|
values,
|
|
17896
|
-
droppedInputs: EMPTY_SET$
|
|
18493
|
+
droppedInputs: EMPTY_SET$3,
|
|
17897
18494
|
validationDetails,
|
|
17898
18495
|
onChange
|
|
17899
18496
|
},
|
|
@@ -17903,9 +18500,10 @@ const BarChart = ({
|
|
|
17903
18500
|
const fillAxis = valueAxis ?? CHART_AXIS_DEFAULTS;
|
|
17904
18501
|
const renderBar = (bar, orient = "vertical") => {
|
|
17905
18502
|
const currentValue = resolveValue(bar, values);
|
|
17906
|
-
const
|
|
18503
|
+
const widgetKey = interactiveBarKey(bar);
|
|
18504
|
+
const vStatus = widgetKey !== void 0 ? validationDetails?.[widgetKey] : void 0;
|
|
17907
18505
|
const validationStatus = vStatus?.isCorrect === true ? "correct" : vStatus?.isCorrect === false ? "incorrect" : null;
|
|
17908
|
-
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "chart-bar-wrapper", children:
|
|
18506
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "chart-bar-wrapper", children: widgetKey !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
17909
18507
|
DraggableBar,
|
|
17910
18508
|
{
|
|
17911
18509
|
bar,
|
|
@@ -17938,7 +18536,7 @@ const BarChart = ({
|
|
|
17938
18536
|
item: subItem,
|
|
17939
18537
|
widgets,
|
|
17940
18538
|
values,
|
|
17941
|
-
droppedInputs: EMPTY_SET$
|
|
18539
|
+
droppedInputs: EMPTY_SET$3,
|
|
17942
18540
|
validationDetails,
|
|
17943
18541
|
onChange
|
|
17944
18542
|
},
|
|
@@ -18143,7 +18741,7 @@ const PictographGrid = ({
|
|
|
18143
18741
|
))
|
|
18144
18742
|
] });
|
|
18145
18743
|
};
|
|
18146
|
-
const EMPTY_SET = /* @__PURE__ */ new Set();
|
|
18744
|
+
const EMPTY_SET$2 = /* @__PURE__ */ new Set();
|
|
18147
18745
|
const PictographChart = ({
|
|
18148
18746
|
item,
|
|
18149
18747
|
widgets,
|
|
@@ -18151,7 +18749,8 @@ const PictographChart = ({
|
|
|
18151
18749
|
validationDetails,
|
|
18152
18750
|
onChange
|
|
18153
18751
|
}) => {
|
|
18154
|
-
|
|
18752
|
+
if (item.chartType !== "pictograph") return null;
|
|
18753
|
+
const { pictos, grid } = item;
|
|
18155
18754
|
const isHorizontal = item.orientation === "horizontal";
|
|
18156
18755
|
const valueAxis = isHorizontal ? item.xAxis : item.yAxis;
|
|
18157
18756
|
const categoryAxis = isHorizontal ? item.yAxis : item.xAxis;
|
|
@@ -18164,7 +18763,7 @@ const PictographChart = ({
|
|
|
18164
18763
|
item: subItem,
|
|
18165
18764
|
widgets,
|
|
18166
18765
|
values,
|
|
18167
|
-
droppedInputs: EMPTY_SET,
|
|
18766
|
+
droppedInputs: EMPTY_SET$2,
|
|
18168
18767
|
validationDetails,
|
|
18169
18768
|
onChange
|
|
18170
18769
|
},
|
|
@@ -18202,7 +18801,7 @@ const PictographChart = ({
|
|
|
18202
18801
|
item: subItem,
|
|
18203
18802
|
widgets,
|
|
18204
18803
|
values,
|
|
18205
|
-
droppedInputs: EMPTY_SET,
|
|
18804
|
+
droppedInputs: EMPTY_SET$2,
|
|
18206
18805
|
validationDetails,
|
|
18207
18806
|
onChange
|
|
18208
18807
|
},
|
|
@@ -18223,7 +18822,7 @@ const PictographChart = ({
|
|
|
18223
18822
|
item: subItem,
|
|
18224
18823
|
widgets,
|
|
18225
18824
|
values,
|
|
18226
|
-
droppedInputs: EMPTY_SET,
|
|
18825
|
+
droppedInputs: EMPTY_SET$2,
|
|
18227
18826
|
validationDetails,
|
|
18228
18827
|
onChange
|
|
18229
18828
|
},
|
|
@@ -18242,9 +18841,183 @@ const PictographChart = ({
|
|
|
18242
18841
|
xAxisTitle
|
|
18243
18842
|
] }) }) });
|
|
18244
18843
|
};
|
|
18844
|
+
const GroupSlot = ({
|
|
18845
|
+
group,
|
|
18846
|
+
series,
|
|
18847
|
+
axis,
|
|
18848
|
+
snap,
|
|
18849
|
+
orientation = "vertical",
|
|
18850
|
+
values,
|
|
18851
|
+
validationDetails,
|
|
18852
|
+
onChange
|
|
18853
|
+
}) => {
|
|
18854
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "chart-group", children: series.map((s) => {
|
|
18855
|
+
const bar = group.bars.find((b) => b.seriesId === s.id);
|
|
18856
|
+
if (!bar) {
|
|
18857
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "chart-group-bar-gap", "aria-hidden": "true" }, s.id);
|
|
18858
|
+
}
|
|
18859
|
+
const currentValue = resolveValue(bar, values);
|
|
18860
|
+
const vStatus = bar.answer !== void 0 ? validationDetails?.[bar.id] : void 0;
|
|
18861
|
+
const validationStatus = vStatus?.isCorrect === true ? "correct" : vStatus?.isCorrect === false ? "incorrect" : null;
|
|
18862
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "chart-bar-wrapper", children: bar.answer !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
18863
|
+
DraggableBar,
|
|
18864
|
+
{
|
|
18865
|
+
bar: { ...bar, answer: bar.answer },
|
|
18866
|
+
currentValue,
|
|
18867
|
+
axis,
|
|
18868
|
+
snap,
|
|
18869
|
+
validationStatus,
|
|
18870
|
+
orientation,
|
|
18871
|
+
onChange
|
|
18872
|
+
}
|
|
18873
|
+
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
18874
|
+
StaticBar,
|
|
18875
|
+
{
|
|
18876
|
+
bar,
|
|
18877
|
+
currentValue,
|
|
18878
|
+
axis,
|
|
18879
|
+
validationStatus,
|
|
18880
|
+
orientation
|
|
18881
|
+
}
|
|
18882
|
+
) }, bar.id);
|
|
18883
|
+
}) });
|
|
18884
|
+
};
|
|
18885
|
+
const EMPTY_SET$1 = /* @__PURE__ */ new Set();
|
|
18886
|
+
const ChartLegend = ({
|
|
18887
|
+
series,
|
|
18888
|
+
widgets,
|
|
18889
|
+
values,
|
|
18890
|
+
validationDetails,
|
|
18891
|
+
onChange
|
|
18892
|
+
}) => {
|
|
18893
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "chart-legend", children: series.map((s) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "chart-legend-item", children: [
|
|
18894
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "chart-legend-swatch", "aria-hidden": "true", style: { backgroundColor: s.color } }),
|
|
18895
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "chart-legend-label", children: s.label?.map((subItem) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
18896
|
+
LayoutRenderer,
|
|
18897
|
+
{
|
|
18898
|
+
item: subItem,
|
|
18899
|
+
widgets,
|
|
18900
|
+
values,
|
|
18901
|
+
droppedInputs: EMPTY_SET$1,
|
|
18902
|
+
validationDetails,
|
|
18903
|
+
onChange
|
|
18904
|
+
},
|
|
18905
|
+
subItem.id
|
|
18906
|
+
)) })
|
|
18907
|
+
] }, s.id)) });
|
|
18908
|
+
};
|
|
18909
|
+
const EMPTY_SET = /* @__PURE__ */ new Set();
|
|
18910
|
+
const GroupedBarChart = ({
|
|
18911
|
+
item,
|
|
18912
|
+
widgets,
|
|
18913
|
+
values,
|
|
18914
|
+
validationDetails,
|
|
18915
|
+
onChange
|
|
18916
|
+
}) => {
|
|
18917
|
+
if (item.chartType !== "grouped-bar") return null;
|
|
18918
|
+
const { groups, series, grid } = item;
|
|
18919
|
+
const isHorizontal = item.orientation === "horizontal";
|
|
18920
|
+
const isInteractive = groups.some((g) => g.bars.some((bar) => bar.answer !== void 0));
|
|
18921
|
+
const valueAxis = isHorizontal ? item.xAxis : item.yAxis;
|
|
18922
|
+
const categoryAxis = isHorizontal ? item.yAxis : item.xAxis;
|
|
18923
|
+
const chartClass = getChartClassName(item);
|
|
18924
|
+
const fillAxis = valueAxis ?? CHART_AXIS_DEFAULTS;
|
|
18925
|
+
const renderAxisTitle = (title) => {
|
|
18926
|
+
if (!title?.length) return null;
|
|
18927
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: title.map((subItem) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
18928
|
+
LayoutRenderer,
|
|
18929
|
+
{
|
|
18930
|
+
item: subItem,
|
|
18931
|
+
widgets,
|
|
18932
|
+
values,
|
|
18933
|
+
droppedInputs: EMPTY_SET,
|
|
18934
|
+
validationDetails,
|
|
18935
|
+
onChange
|
|
18936
|
+
},
|
|
18937
|
+
subItem.id
|
|
18938
|
+
)) });
|
|
18939
|
+
};
|
|
18940
|
+
const renderGroupSlots = (orient) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "chart-groups", children: groups.map((group) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
18941
|
+
GroupSlot,
|
|
18942
|
+
{
|
|
18943
|
+
group,
|
|
18944
|
+
series,
|
|
18945
|
+
axis: fillAxis,
|
|
18946
|
+
snap: item.snap,
|
|
18947
|
+
orientation: orient,
|
|
18948
|
+
values,
|
|
18949
|
+
validationDetails,
|
|
18950
|
+
onChange
|
|
18951
|
+
},
|
|
18952
|
+
group.id
|
|
18953
|
+
)) });
|
|
18954
|
+
const legend = item.legend ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
18955
|
+
ChartLegend,
|
|
18956
|
+
{
|
|
18957
|
+
series,
|
|
18958
|
+
widgets,
|
|
18959
|
+
values,
|
|
18960
|
+
validationDetails,
|
|
18961
|
+
onChange
|
|
18962
|
+
}
|
|
18963
|
+
) : null;
|
|
18964
|
+
if (isHorizontal) {
|
|
18965
|
+
const chartStyle2 = computeGroupedChartStyle(item.scale, groups.length, series.length, "horizontal", isInteractive);
|
|
18966
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "chart-container", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "chart-scroll", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: chartClass, dir: "ltr", style: chartStyle2, children: [
|
|
18967
|
+
item.title && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "chart-title", children: item.title }),
|
|
18968
|
+
categoryAxis?.visible && categoryAxis.title?.length ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "chart-axis-title chart-axis-title--y-h", children: renderAxisTitle(categoryAxis.title) }) : null,
|
|
18969
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "chart-axis-y--categories", children: groups.map((group) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "chart-bar-y-label", children: group.label?.map((subItem) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
18970
|
+
LayoutRenderer,
|
|
18971
|
+
{
|
|
18972
|
+
item: subItem,
|
|
18973
|
+
widgets,
|
|
18974
|
+
values,
|
|
18975
|
+
droppedInputs: EMPTY_SET,
|
|
18976
|
+
validationDetails,
|
|
18977
|
+
onChange
|
|
18978
|
+
},
|
|
18979
|
+
subItem.id
|
|
18980
|
+
)) }, group.id)) }),
|
|
18981
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "chart-plot", children: [
|
|
18982
|
+
grid && /* @__PURE__ */ jsxRuntime.jsx(Grid, { axis: fillAxis, orientation: "horizontal" }),
|
|
18983
|
+
renderGroupSlots("horizontal")
|
|
18984
|
+
] }),
|
|
18985
|
+
valueAxis && valueAxis.visible && /* @__PURE__ */ jsxRuntime.jsx(XAxis, { axis: valueAxis, title: renderAxisTitle(valueAxis.title) }),
|
|
18986
|
+
legend
|
|
18987
|
+
] }) }) });
|
|
18988
|
+
}
|
|
18989
|
+
const chartStyle = computeGroupedChartStyle(item.scale, groups.length, series.length, "vertical", isInteractive);
|
|
18990
|
+
const hasAnyGroupLabel = groups.some((g) => g.label && g.label.length > 0);
|
|
18991
|
+
const groupLabelRow = hasAnyGroupLabel ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "chart-axis-x", children: groups.map((group) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "chart-axis-tick-x", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "chart-axis-tick-x-inner", children: group.label?.map((subItem) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
18992
|
+
LayoutRenderer,
|
|
18993
|
+
{
|
|
18994
|
+
item: subItem,
|
|
18995
|
+
widgets,
|
|
18996
|
+
values,
|
|
18997
|
+
droppedInputs: EMPTY_SET,
|
|
18998
|
+
validationDetails,
|
|
18999
|
+
onChange
|
|
19000
|
+
},
|
|
19001
|
+
subItem.id
|
|
19002
|
+
)) }) }, group.id)) }) : null;
|
|
19003
|
+
const xAxisTitle = categoryAxis?.visible && categoryAxis.title?.length ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "chart-axis-title chart-axis-title--x-category", children: renderAxisTitle(categoryAxis.title) }) : null;
|
|
19004
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "chart-container", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "chart-scroll", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: chartClass, dir: "ltr", style: chartStyle, children: [
|
|
19005
|
+
item.title && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "chart-title", children: item.title }),
|
|
19006
|
+
valueAxis?.visible && valueAxis.title?.length ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "chart-axis-title chart-axis-title--y", children: renderAxisTitle(valueAxis.title) }) : null,
|
|
19007
|
+
valueAxis && valueAxis.visible && /* @__PURE__ */ jsxRuntime.jsx(YAxis, { axis: valueAxis }),
|
|
19008
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "chart-plot", children: [
|
|
19009
|
+
grid && /* @__PURE__ */ jsxRuntime.jsx(Grid, { axis: fillAxis }),
|
|
19010
|
+
renderGroupSlots("vertical")
|
|
19011
|
+
] }),
|
|
19012
|
+
groupLabelRow,
|
|
19013
|
+
xAxisTitle,
|
|
19014
|
+
legend
|
|
19015
|
+
] }) }) });
|
|
19016
|
+
};
|
|
18245
19017
|
const CHART_TYPE_COMPONENTS = {
|
|
18246
19018
|
bar: BarChart,
|
|
18247
|
-
pictograph: PictographChart
|
|
19019
|
+
pictograph: PictographChart,
|
|
19020
|
+
"grouped-bar": GroupedBarChart
|
|
18248
19021
|
// line: LineChart,
|
|
18249
19022
|
// pie: PieChart,
|
|
18250
19023
|
};
|
|
@@ -18286,11 +19059,12 @@ const RadioOptions = ({
|
|
|
18286
19059
|
};
|
|
18287
19060
|
const Btn = ({
|
|
18288
19061
|
label,
|
|
19062
|
+
dataKey = label,
|
|
18289
19063
|
extraClass = "keypad-btn-digit",
|
|
18290
19064
|
onClick
|
|
18291
19065
|
}) => {
|
|
18292
19066
|
const locked = useExerciseLocked();
|
|
18293
|
-
return /* @__PURE__ */ jsxRuntime.jsx("button", { className: `keypad-btn ${extraClass}`, disabled: locked, onClick, children: /* @__PURE__ */ jsxRuntime.jsx("span", { children: label }) });
|
|
19067
|
+
return /* @__PURE__ */ jsxRuntime.jsx("button", { className: `keypad-btn ${extraClass}`, "data-key": dataKey, disabled: locked, onClick, children: /* @__PURE__ */ jsxRuntime.jsx("span", { children: label }) });
|
|
18294
19068
|
};
|
|
18295
19069
|
const DiamondKeypad = ({ value, onChange }) => {
|
|
18296
19070
|
const playSound = useSound();
|
|
@@ -18302,7 +19076,7 @@ const DiamondKeypad = ({ value, onChange }) => {
|
|
|
18302
19076
|
playSound("keypad-press");
|
|
18303
19077
|
onChange(value.slice(0, -1));
|
|
18304
19078
|
};
|
|
18305
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "diamond-keypad", children: [
|
|
19079
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "diamond-keypad", "data-tutorial": "keypad", children: [
|
|
18306
19080
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "diamond-keypad-row", children: [
|
|
18307
19081
|
/* @__PURE__ */ jsxRuntime.jsx(Btn, { label: "1", onClick: () => append("1") }),
|
|
18308
19082
|
/* @__PURE__ */ jsxRuntime.jsx(Btn, { label: "2", onClick: () => append("2") }),
|
|
@@ -18321,7 +19095,7 @@ const DiamondKeypad = ({ value, onChange }) => {
|
|
|
18321
19095
|
] }),
|
|
18322
19096
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "diamond-keypad-row", children: [
|
|
18323
19097
|
/* @__PURE__ */ jsxRuntime.jsx(Btn, { label: ".", extraClass: "keypad-btn-decimal", onClick: () => append(".") }),
|
|
18324
|
-
/* @__PURE__ */ jsxRuntime.jsx(Btn, { label: "⌫", extraClass: "keypad-btn-backspace", onClick: backspace })
|
|
19098
|
+
/* @__PURE__ */ jsxRuntime.jsx(Btn, { label: "⌫", dataKey: "backspace", extraClass: "keypad-btn-backspace", onClick: backspace })
|
|
18325
19099
|
] })
|
|
18326
19100
|
] });
|
|
18327
19101
|
};
|
|
@@ -18927,7 +19701,7 @@ function isGeometryWidget(widget) {
|
|
|
18927
19701
|
return widget.type === JSON_TYPES.GEOMETRY;
|
|
18928
19702
|
}
|
|
18929
19703
|
const GeometryWidget = React.lazy(async () => ({
|
|
18930
|
-
default: (await Promise.resolve().then(() => require("./GeometryWidget-
|
|
19704
|
+
default: (await Promise.resolve().then(() => require("./GeometryWidget-BTPjXJz-.js"))).GeometryWidget
|
|
18931
19705
|
}));
|
|
18932
19706
|
const WidgetRenderer = ({
|
|
18933
19707
|
widget,
|
|
@@ -18955,8 +19729,9 @@ const WidgetRenderer = ({
|
|
|
18955
19729
|
isDropped,
|
|
18956
19730
|
validationStatus,
|
|
18957
19731
|
inputType: widget.inputType,
|
|
18958
|
-
length: widget.inputType ===
|
|
18959
|
-
digits: widget.inputType ===
|
|
19732
|
+
length: widget.inputType === INPUT_TYPES.STRING ? widget.length : void 0,
|
|
19733
|
+
digits: widget.inputType === INPUT_TYPES.NUMERIC ? widget.digits : void 0,
|
|
19734
|
+
width: widget.width,
|
|
18960
19735
|
placeholder: widget.placeholder,
|
|
18961
19736
|
disabled: widget.disabled,
|
|
18962
19737
|
onChange,
|
|
@@ -19327,6 +20102,13 @@ const EPSILON = 10 ** -INTERNAL_PRECISION;
|
|
|
19327
20102
|
const cleanFloat = (n) => parseFloat(n.toFixed(INTERNAL_PRECISION));
|
|
19328
20103
|
const roundTick = (n) => parseFloat(n.toFixed(DISPLAY_PRECISION));
|
|
19329
20104
|
const almostEqual = (a, b) => Math.abs(a - b) <= EPSILON;
|
|
20105
|
+
const NUMBER_LINE_AXIS_ENDS = {
|
|
20106
|
+
"arrow-right": { left: "tick", right: "arrow" },
|
|
20107
|
+
"arrow-left": { left: "arrow", right: "tick" },
|
|
20108
|
+
"arrow-both": { left: "arrow", right: "arrow" },
|
|
20109
|
+
"line": { left: "line", right: "line" },
|
|
20110
|
+
"line-left": { left: "line", right: "arrow" }
|
|
20111
|
+
};
|
|
19330
20112
|
const toPercent = (value, min, max) => {
|
|
19331
20113
|
const raw = SIDE_PAD + (value - min) / (max - min) * (100 - 2 * SIDE_PAD);
|
|
19332
20114
|
return Math.max(SIDE_PAD, Math.min(100 - SIDE_PAD, raw));
|
|
@@ -20374,7 +21156,7 @@ const NumberLineRenderer = ({
|
|
|
20374
21156
|
max,
|
|
20375
21157
|
step,
|
|
20376
21158
|
minorTicks = 0,
|
|
20377
|
-
style: lineStyle =
|
|
21159
|
+
style: lineStyle = DEFAULT_NUMBER_LINE_STYLE,
|
|
20378
21160
|
labelVisibility,
|
|
20379
21161
|
highlights,
|
|
20380
21162
|
arrows,
|
|
@@ -20438,8 +21220,9 @@ const NumberLineRenderer = ({
|
|
|
20438
21220
|
() => getMinorTickValues(majorTicks, minorTicks),
|
|
20439
21221
|
[majorTicks, minorTicks]
|
|
20440
21222
|
);
|
|
20441
|
-
const
|
|
20442
|
-
const showArrowLeft =
|
|
21223
|
+
const axisEnds = NUMBER_LINE_AXIS_ENDS[lineStyle] ?? NUMBER_LINE_AXIS_ENDS[DEFAULT_NUMBER_LINE_STYLE];
|
|
21224
|
+
const showArrowLeft = axisEnds.left === "arrow";
|
|
21225
|
+
const showArrowRight = axisEnds.right === "arrow";
|
|
20443
21226
|
const hasDragOptions = Boolean(widgets[DRAG_OPTIONS_WIDGET_ID]);
|
|
20444
21227
|
const isAxisDragMode = hasDragOptions && item.dragMode === "axis";
|
|
20445
21228
|
const inputTickSet = React.useMemo(() => {
|
|
@@ -20487,7 +21270,7 @@ const NumberLineRenderer = ({
|
|
|
20487
21270
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
20488
21271
|
"div",
|
|
20489
21272
|
{
|
|
20490
|
-
className: `number-line${isAxisDragMode ? " number-line--drag-mode" : ""}${
|
|
21273
|
+
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}` : ""}`,
|
|
20491
21274
|
style: {
|
|
20492
21275
|
[CSS_VAR_SIDE_PAD]: `${SIDE_PAD}%`,
|
|
20493
21276
|
...positionContentHeight && { "--nl-position-content-height": positionContentHeight }
|
|
@@ -20860,6 +21643,35 @@ function groupLineItems(items) {
|
|
|
20860
21643
|
}
|
|
20861
21644
|
return groups;
|
|
20862
21645
|
}
|
|
21646
|
+
function placeTableCells(rows, cols) {
|
|
21647
|
+
const placements = /* @__PURE__ */ new Map();
|
|
21648
|
+
const occupied = rows.map(() => []);
|
|
21649
|
+
const totalRows = rows.length;
|
|
21650
|
+
rows.forEach((row, rowIndex) => {
|
|
21651
|
+
const taken = occupied[rowIndex] ?? [];
|
|
21652
|
+
let col = 0;
|
|
21653
|
+
for (const cell of row.cells) {
|
|
21654
|
+
while (taken[col]) col++;
|
|
21655
|
+
const colspan = cell.colspan || 1;
|
|
21656
|
+
const rowspan = Math.min(cell.rowspan || 1, totalRows - rowIndex);
|
|
21657
|
+
for (let r = rowIndex; r < rowIndex + rowspan; r++) {
|
|
21658
|
+
const line = occupied[r];
|
|
21659
|
+
if (!line) continue;
|
|
21660
|
+
for (let c = col; c < col + colspan; c++) line[c] = true;
|
|
21661
|
+
}
|
|
21662
|
+
placements.set(cell.id, {
|
|
21663
|
+
column: col + 1,
|
|
21664
|
+
row: rowIndex + 1,
|
|
21665
|
+
colspan,
|
|
21666
|
+
rowspan,
|
|
21667
|
+
isLastColumn: col + colspan >= cols,
|
|
21668
|
+
isLastRow: rowIndex + rowspan >= totalRows
|
|
21669
|
+
});
|
|
21670
|
+
col += colspan;
|
|
21671
|
+
}
|
|
21672
|
+
});
|
|
21673
|
+
return placements;
|
|
21674
|
+
}
|
|
20863
21675
|
const toValidationStatus = (v) => v === true ? "correct" : v === false ? "incorrect" : null;
|
|
20864
21676
|
const isNumericText = (value) => /^[\d.,\s]+$/.test(value);
|
|
20865
21677
|
const numberClass = (value) => isNumericText(value) ? "text-item-number" : "";
|
|
@@ -20983,21 +21795,28 @@ const LayoutRenderer = ({
|
|
|
20983
21795
|
subItem.id
|
|
20984
21796
|
)) });
|
|
20985
21797
|
case JSON_TYPES.TABLE: {
|
|
20986
|
-
const
|
|
21798
|
+
const placements = placeTableCells(item.rows, item.cols);
|
|
20987
21799
|
const tableStyle = {
|
|
20988
|
-
"--exercise-table-border-color": item.borderColor ?? DEFAULT_TABLE_BORDER_COLOR
|
|
21800
|
+
"--exercise-table-border-color": item.borderColor ?? DEFAULT_TABLE_BORDER_COLOR,
|
|
21801
|
+
"--exercise-table-cols": item.cols
|
|
20989
21802
|
};
|
|
20990
|
-
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "exercise-table-container", style: tableStyle, children: /* @__PURE__ */ jsxRuntime.jsx("
|
|
20991
|
-
const
|
|
20992
|
-
const
|
|
21803
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "exercise-table-container", style: tableStyle, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "exercise-table", role: "table", children: item.rows.map((row) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "exercise-table-row", role: "row", children: row.cells.map((cell) => {
|
|
21804
|
+
const placement = placements.get(cell.id);
|
|
21805
|
+
const modifiers = [
|
|
21806
|
+
placement?.isLastRow ? " exercise-table-cell--last-row" : "",
|
|
21807
|
+
placement?.isLastColumn ? " exercise-table-cell--last-col" : ""
|
|
21808
|
+
].join("");
|
|
20993
21809
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
20994
|
-
"
|
|
21810
|
+
"div",
|
|
20995
21811
|
{
|
|
20996
|
-
className: `exercise-table-cell${
|
|
20997
|
-
|
|
20998
|
-
|
|
20999
|
-
|
|
21000
|
-
|
|
21812
|
+
className: `exercise-table-cell${modifiers}`,
|
|
21813
|
+
role: "cell",
|
|
21814
|
+
style: {
|
|
21815
|
+
backgroundColor: cell.backgroundColor ?? row.backgroundColor,
|
|
21816
|
+
gridColumn: placement && `${placement.column} / span ${placement.colspan}`,
|
|
21817
|
+
gridRow: placement && `${placement.row} / span ${placement.rowspan}`
|
|
21818
|
+
},
|
|
21819
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "exercise-table-cell-content", children: cell.items.map((subItem) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
21001
21820
|
LayoutRenderer,
|
|
21002
21821
|
{
|
|
21003
21822
|
item: subItem,
|
|
@@ -21010,11 +21829,11 @@ const LayoutRenderer = ({
|
|
|
21010
21829
|
resetKey
|
|
21011
21830
|
},
|
|
21012
21831
|
subItem.id
|
|
21013
|
-
))
|
|
21832
|
+
)) })
|
|
21014
21833
|
},
|
|
21015
21834
|
cell.id
|
|
21016
21835
|
);
|
|
21017
|
-
}) }, row.id)) }) })
|
|
21836
|
+
}) }, row.id)) }) });
|
|
21018
21837
|
}
|
|
21019
21838
|
case JSON_TYPES.NUMBER_LINE:
|
|
21020
21839
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -21184,6 +22003,394 @@ const LayoutRenderer = ({
|
|
|
21184
22003
|
return null;
|
|
21185
22004
|
}
|
|
21186
22005
|
};
|
|
22006
|
+
function LionHand({ className }) {
|
|
22007
|
+
const id = React.useId();
|
|
22008
|
+
const shadowId = `${id}-shadow`;
|
|
22009
|
+
const maskId = `${id}-mask`;
|
|
22010
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
22011
|
+
"svg",
|
|
22012
|
+
{
|
|
22013
|
+
"aria-hidden": "true",
|
|
22014
|
+
focusable: "false",
|
|
22015
|
+
width: "75",
|
|
22016
|
+
height: "78",
|
|
22017
|
+
viewBox: "0 0 75 78",
|
|
22018
|
+
fill: "none",
|
|
22019
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
22020
|
+
className,
|
|
22021
|
+
children: [
|
|
22022
|
+
/* @__PURE__ */ jsxRuntime.jsxs("g", { filter: `url(#${shadowId})`, children: [
|
|
22023
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
22024
|
+
"mask",
|
|
22025
|
+
{
|
|
22026
|
+
id: maskId,
|
|
22027
|
+
style: { maskType: "alpha" },
|
|
22028
|
+
maskUnits: "userSpaceOnUse",
|
|
22029
|
+
x: "9",
|
|
22030
|
+
y: "19",
|
|
22031
|
+
width: "49",
|
|
22032
|
+
height: "42",
|
|
22033
|
+
children: /* @__PURE__ */ jsxRuntime.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" })
|
|
22034
|
+
}
|
|
22035
|
+
),
|
|
22036
|
+
/* @__PURE__ */ jsxRuntime.jsxs("g", { mask: `url(#${maskId})`, children: [
|
|
22037
|
+
/* @__PURE__ */ jsxRuntime.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" }),
|
|
22038
|
+
/* @__PURE__ */ jsxRuntime.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" }),
|
|
22039
|
+
/* @__PURE__ */ jsxRuntime.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" }),
|
|
22040
|
+
/* @__PURE__ */ jsxRuntime.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" })
|
|
22041
|
+
] })
|
|
22042
|
+
] }),
|
|
22043
|
+
/* @__PURE__ */ jsxRuntime.jsx("defs", { children: /* @__PURE__ */ jsxRuntime.jsxs("filter", { id: shadowId, x: "-1.99995", y: "0", width: "78.3658", height: "81.8518", filterUnits: "userSpaceOnUse", colorInterpolationFilters: "sRGB", children: [
|
|
22044
|
+
/* @__PURE__ */ jsxRuntime.jsx("feFlood", { floodOpacity: "0", result: "BackgroundImageFix" }),
|
|
22045
|
+
/* @__PURE__ */ jsxRuntime.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" }),
|
|
22046
|
+
/* @__PURE__ */ jsxRuntime.jsx("feOffset", { dy: "2" }),
|
|
22047
|
+
/* @__PURE__ */ jsxRuntime.jsx("feGaussianBlur", { stdDeviation: "1" }),
|
|
22048
|
+
/* @__PURE__ */ jsxRuntime.jsx("feComposite", { in2: "hardAlpha", operator: "out" }),
|
|
22049
|
+
/* @__PURE__ */ jsxRuntime.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" }),
|
|
22050
|
+
/* @__PURE__ */ jsxRuntime.jsx("feBlend", { mode: "normal", in2: "BackgroundImageFix", result: "effect1_dropShadow" }),
|
|
22051
|
+
/* @__PURE__ */ jsxRuntime.jsx("feBlend", { mode: "normal", in: "SourceGraphic", in2: "effect1_dropShadow", result: "shape" })
|
|
22052
|
+
] }) })
|
|
22053
|
+
]
|
|
22054
|
+
}
|
|
22055
|
+
);
|
|
22056
|
+
}
|
|
22057
|
+
const TUTORIAL_HAND_TIP = { x: 56 / 75, y: 22 / 78 };
|
|
22058
|
+
const CYCLE_MS = 1400;
|
|
22059
|
+
const PRESS_AT_MS = 650;
|
|
22060
|
+
const PRESS_HOLD_MS = 220;
|
|
22061
|
+
const HOLE_PAD = 10;
|
|
22062
|
+
const HOLE_RADIUS = 18;
|
|
22063
|
+
const HAND_W = 72;
|
|
22064
|
+
const HAND_H = 75;
|
|
22065
|
+
const BUBBLE_W = 240;
|
|
22066
|
+
const BUBBLE_GAP = 14;
|
|
22067
|
+
const EDGE = 8;
|
|
22068
|
+
const DEFAULT_NEXT_LABEL = "فهمت";
|
|
22069
|
+
function resolve(t) {
|
|
22070
|
+
return (typeof t === "function" ? t() : t) ?? null;
|
|
22071
|
+
}
|
|
22072
|
+
function toRect(t) {
|
|
22073
|
+
if (!t) return null;
|
|
22074
|
+
const r = t instanceof Element ? t.getBoundingClientRect() : t;
|
|
22075
|
+
if (r.width <= 0 || r.height <= 0) return null;
|
|
22076
|
+
return { x: Math.round(r.x), y: Math.round(r.y), width: Math.round(r.width), height: Math.round(r.height) };
|
|
22077
|
+
}
|
|
22078
|
+
const centerOf = (r) => ({ x: r.x + r.width / 2, y: r.y + r.height / 2 });
|
|
22079
|
+
const TutorialOverlay = ({ tutorial, root, json, options, onClose }) => {
|
|
22080
|
+
const { steps } = tutorial;
|
|
22081
|
+
const [stepIndex, setStepIndex] = React.useState(
|
|
22082
|
+
() => Math.min(Math.max(0, Math.floor(options.startStep ?? 0)), steps.length - 1)
|
|
22083
|
+
);
|
|
22084
|
+
const step = steps[stepIndex];
|
|
22085
|
+
const optionsRef = React.useRef(options);
|
|
22086
|
+
optionsRef.current = options;
|
|
22087
|
+
const ctx = { root, json, submitTarget: resolve(options.submitTarget) };
|
|
22088
|
+
const ctxRef = React.useRef(ctx);
|
|
22089
|
+
ctxRef.current = ctx;
|
|
22090
|
+
const [layout, setLayout] = React.useState(null);
|
|
22091
|
+
React.useEffect(() => {
|
|
22092
|
+
let raf = 0;
|
|
22093
|
+
let last = "";
|
|
22094
|
+
const tick = () => {
|
|
22095
|
+
const c = ctxRef.current;
|
|
22096
|
+
const next2 = {
|
|
22097
|
+
spotlight: step.spotlight(c).map(toRect),
|
|
22098
|
+
taps: step.taps(c).map(toRect),
|
|
22099
|
+
anchor: toRect(resolve(optionsRef.current.bubbleAnchor)),
|
|
22100
|
+
root: toRect(c.root),
|
|
22101
|
+
vw: window.innerWidth,
|
|
22102
|
+
vh: window.innerHeight
|
|
22103
|
+
};
|
|
22104
|
+
const key2 = JSON.stringify(next2);
|
|
22105
|
+
if (key2 !== last) {
|
|
22106
|
+
last = key2;
|
|
22107
|
+
setLayout(next2);
|
|
22108
|
+
}
|
|
22109
|
+
raf = requestAnimationFrame(tick);
|
|
22110
|
+
};
|
|
22111
|
+
tick();
|
|
22112
|
+
return () => cancelAnimationFrame(raf);
|
|
22113
|
+
}, [step]);
|
|
22114
|
+
const [tapIndex, setTapIndex] = React.useState(0);
|
|
22115
|
+
const [tapCount, setTapCount] = React.useState(0);
|
|
22116
|
+
const [shownStep, setShownStep] = React.useState(stepIndex);
|
|
22117
|
+
if (shownStep !== stepIndex) {
|
|
22118
|
+
setShownStep(stepIndex);
|
|
22119
|
+
setTapIndex(0);
|
|
22120
|
+
setTapCount(0);
|
|
22121
|
+
}
|
|
22122
|
+
React.useEffect(() => {
|
|
22123
|
+
let release;
|
|
22124
|
+
const press = setTimeout(() => {
|
|
22125
|
+
setTapCount((n) => n + 1);
|
|
22126
|
+
const targets = step.taps(ctxRef.current);
|
|
22127
|
+
const el = targets[tapIndex % Math.max(1, targets.length)];
|
|
22128
|
+
if (el instanceof Element) {
|
|
22129
|
+
el.classList.add("met-tutorial-pressed");
|
|
22130
|
+
release = setTimeout(() => el.classList.remove("met-tutorial-pressed"), PRESS_HOLD_MS);
|
|
22131
|
+
}
|
|
22132
|
+
}, PRESS_AT_MS);
|
|
22133
|
+
const advance = setTimeout(() => setTapIndex((i) => i + 1), CYCLE_MS);
|
|
22134
|
+
return () => {
|
|
22135
|
+
clearTimeout(press);
|
|
22136
|
+
clearTimeout(advance);
|
|
22137
|
+
if (release) clearTimeout(release);
|
|
22138
|
+
root.querySelectorAll(".met-tutorial-pressed").forEach((el) => el.classList.remove("met-tutorial-pressed"));
|
|
22139
|
+
};
|
|
22140
|
+
}, [step, tapIndex, root]);
|
|
22141
|
+
const text = options.texts?.[step.id] ?? step.text;
|
|
22142
|
+
const textRef = React.useRef(text);
|
|
22143
|
+
textRef.current = text;
|
|
22144
|
+
React.useEffect(() => {
|
|
22145
|
+
optionsRef.current.onStep?.(stepIndex, step.id);
|
|
22146
|
+
optionsRef.current.onSpeak?.(step.id, textRef.current);
|
|
22147
|
+
}, [stepIndex, step]);
|
|
22148
|
+
const isLast = stepIndex === steps.length - 1;
|
|
22149
|
+
const next = () => {
|
|
22150
|
+
if (isLast) onClose(true);
|
|
22151
|
+
else setStepIndex((i) => i + 1);
|
|
22152
|
+
};
|
|
22153
|
+
const overlayRef = React.useRef(null);
|
|
22154
|
+
const nextRef = React.useRef(null);
|
|
22155
|
+
const onCloseRef = React.useRef(onClose);
|
|
22156
|
+
onCloseRef.current = onClose;
|
|
22157
|
+
React.useEffect(() => {
|
|
22158
|
+
const previous = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
22159
|
+
previous?.blur();
|
|
22160
|
+
const onKeyDown = (e) => {
|
|
22161
|
+
const overlay = overlayRef.current;
|
|
22162
|
+
if (e.key === "Escape") {
|
|
22163
|
+
e.preventDefault();
|
|
22164
|
+
e.stopPropagation();
|
|
22165
|
+
onCloseRef.current(false);
|
|
22166
|
+
return;
|
|
22167
|
+
}
|
|
22168
|
+
if (e.key === "Tab" && overlay) {
|
|
22169
|
+
e.preventDefault();
|
|
22170
|
+
const buttons = Array.from(overlay.querySelectorAll("button"));
|
|
22171
|
+
if (buttons.length === 0) return;
|
|
22172
|
+
const at = buttons.indexOf(document.activeElement);
|
|
22173
|
+
const to = (at + (e.shiftKey ? -1 : 1) + buttons.length) % buttons.length;
|
|
22174
|
+
buttons[to]?.focus();
|
|
22175
|
+
return;
|
|
22176
|
+
}
|
|
22177
|
+
if (overlay && e.target instanceof Node && overlay.contains(e.target)) return;
|
|
22178
|
+
e.preventDefault();
|
|
22179
|
+
e.stopPropagation();
|
|
22180
|
+
};
|
|
22181
|
+
document.addEventListener("keydown", onKeyDown, true);
|
|
22182
|
+
return () => {
|
|
22183
|
+
document.removeEventListener("keydown", onKeyDown, true);
|
|
22184
|
+
if (previous?.isConnected) previous.focus();
|
|
22185
|
+
};
|
|
22186
|
+
}, []);
|
|
22187
|
+
const bubbleShown = layout !== null;
|
|
22188
|
+
React.useEffect(() => {
|
|
22189
|
+
nextRef.current?.focus({ focusVisible: false });
|
|
22190
|
+
}, [stepIndex, bubbleShown]);
|
|
22191
|
+
const maskId = `met-tutorial-mask-${React.useId()}`;
|
|
22192
|
+
if (typeof document === "undefined") return null;
|
|
22193
|
+
const holes = (layout?.spotlight ?? []).filter((r) => r !== null);
|
|
22194
|
+
const tapTarget = layout?.taps.length ? layout.taps[tapIndex % layout.taps.length] ?? null : null;
|
|
22195
|
+
const Scene = step.Scene;
|
|
22196
|
+
return reactDom.createPortal(
|
|
22197
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { ref: overlayRef, className: "met-tutorial", role: "dialog", "aria-modal": "true", "aria-label": text, children: [
|
|
22198
|
+
/* @__PURE__ */ jsxRuntime.jsxs("svg", { className: "met-tutorial-dim", "aria-hidden": "true", children: [
|
|
22199
|
+
/* @__PURE__ */ jsxRuntime.jsx("defs", { children: /* @__PURE__ */ jsxRuntime.jsxs("mask", { id: maskId, children: [
|
|
22200
|
+
/* @__PURE__ */ jsxRuntime.jsx("rect", { width: "100%", height: "100%", fill: "white" }),
|
|
22201
|
+
holes.map((r, i) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
22202
|
+
"rect",
|
|
22203
|
+
{
|
|
22204
|
+
x: r.x - HOLE_PAD,
|
|
22205
|
+
y: r.y - HOLE_PAD,
|
|
22206
|
+
width: r.width + HOLE_PAD * 2,
|
|
22207
|
+
height: r.height + HOLE_PAD * 2,
|
|
22208
|
+
rx: HOLE_RADIUS,
|
|
22209
|
+
fill: "black"
|
|
22210
|
+
},
|
|
22211
|
+
i
|
|
22212
|
+
))
|
|
22213
|
+
] }) }),
|
|
22214
|
+
/* @__PURE__ */ jsxRuntime.jsx("rect", { width: "100%", height: "100%", mask: `url(#${maskId})` })
|
|
22215
|
+
] }),
|
|
22216
|
+
holes.map((r, i) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
22217
|
+
"div",
|
|
22218
|
+
{
|
|
22219
|
+
className: "met-tutorial-glow",
|
|
22220
|
+
style: {
|
|
22221
|
+
left: r.x - HOLE_PAD,
|
|
22222
|
+
top: r.y - HOLE_PAD,
|
|
22223
|
+
width: r.width + HOLE_PAD * 2,
|
|
22224
|
+
height: r.height + HOLE_PAD * 2,
|
|
22225
|
+
borderRadius: HOLE_RADIUS
|
|
22226
|
+
}
|
|
22227
|
+
},
|
|
22228
|
+
i
|
|
22229
|
+
)),
|
|
22230
|
+
Scene && layout && /* @__PURE__ */ jsxRuntime.jsx(Scene, { ctx, spotlight: layout.spotlight, taps: layout.taps, tapCount }),
|
|
22231
|
+
tapTarget && /* @__PURE__ */ jsxRuntime.jsx(Hand, { target: tapTarget, tapKey: `${stepIndex}-${tapIndex}` }),
|
|
22232
|
+
layout && /* @__PURE__ */ jsxRuntime.jsx(
|
|
22233
|
+
Bubble,
|
|
22234
|
+
{
|
|
22235
|
+
layout,
|
|
22236
|
+
text,
|
|
22237
|
+
stepIndex,
|
|
22238
|
+
stepCount: steps.length,
|
|
22239
|
+
nextLabel: options.nextLabel ?? DEFAULT_NEXT_LABEL,
|
|
22240
|
+
onSpeak: options.onSpeak && (options.canSpeak?.(step.id) ?? true) ? () => optionsRef.current.onSpeak?.(step.id, textRef.current) : void 0,
|
|
22241
|
+
onNext: next,
|
|
22242
|
+
nextRef
|
|
22243
|
+
}
|
|
22244
|
+
)
|
|
22245
|
+
] }),
|
|
22246
|
+
document.body
|
|
22247
|
+
);
|
|
22248
|
+
};
|
|
22249
|
+
const Hand = ({ target, tapKey }) => {
|
|
22250
|
+
const c = centerOf(target);
|
|
22251
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
22252
|
+
"div",
|
|
22253
|
+
{
|
|
22254
|
+
className: "met-tutorial-hand",
|
|
22255
|
+
style: {
|
|
22256
|
+
width: HAND_W,
|
|
22257
|
+
height: HAND_H,
|
|
22258
|
+
transform: `translate(${c.x - TUTORIAL_HAND_TIP.x * HAND_W}px, ${c.y - TUTORIAL_HAND_TIP.y * HAND_H}px)`
|
|
22259
|
+
},
|
|
22260
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "met-tutorial-hand-press", children: [
|
|
22261
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
22262
|
+
"span",
|
|
22263
|
+
{
|
|
22264
|
+
className: "met-tutorial-ripple",
|
|
22265
|
+
style: { left: TUTORIAL_HAND_TIP.x * HAND_W, top: TUTORIAL_HAND_TIP.y * HAND_H }
|
|
22266
|
+
}
|
|
22267
|
+
),
|
|
22268
|
+
/* @__PURE__ */ jsxRuntime.jsx(LionHand, {})
|
|
22269
|
+
] }, tapKey)
|
|
22270
|
+
}
|
|
22271
|
+
);
|
|
22272
|
+
};
|
|
22273
|
+
const Bubble = ({ layout, text, stepIndex, stepCount, nextLabel, onSpeak, onNext, nextRef }) => {
|
|
22274
|
+
const { anchor, root, vw, vh } = layout;
|
|
22275
|
+
const clampLeft = (x) => Math.min(Math.max(EDGE, x), Math.max(EDGE, vw - BUBBLE_W - EDGE));
|
|
22276
|
+
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 };
|
|
22277
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `met-tutorial-bubble${anchor ? " met-tutorial-bubble--anchored" : ""}`, style, children: [
|
|
22278
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "met-tutorial-text", dir: "auto", children: text }),
|
|
22279
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "met-tutorial-actions", children: [
|
|
22280
|
+
onSpeak && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "met-tutorial-speak", "aria-label": "🔊", onClick: onSpeak, children: /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: "0 0 24 24", width: "24", height: "24", "aria-hidden": "true", children: [
|
|
22281
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M4 9v6h4l5 4V5L8 9H4z", fill: "currentColor" }),
|
|
22282
|
+
/* @__PURE__ */ jsxRuntime.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" })
|
|
22283
|
+
] }) }),
|
|
22284
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { ref: nextRef, type: "button", className: "met-tutorial-next", onClick: onNext, children: nextLabel })
|
|
22285
|
+
] }),
|
|
22286
|
+
stepCount > 1 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "met-tutorial-dots", "aria-hidden": "true", children: Array.from({ length: stepCount }, (_, i) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: i === stepIndex ? "is-current" : i < stepIndex ? "is-done" : "" }, i)) })
|
|
22287
|
+
] });
|
|
22288
|
+
};
|
|
22289
|
+
const DEMO_KEYS = ["2", "5", "8"];
|
|
22290
|
+
const DEFAULT_SLOTS = 3;
|
|
22291
|
+
const MAX_SLOTS = 6;
|
|
22292
|
+
const STAND_IN_CHECK_SIZE = 52;
|
|
22293
|
+
const STAND_IN_CHECK_GAP = 20;
|
|
22294
|
+
function keypad(ctx) {
|
|
22295
|
+
return ctx.root.querySelector('[data-tutorial="keypad"]');
|
|
22296
|
+
}
|
|
22297
|
+
function key(ctx, k) {
|
|
22298
|
+
return ctx.root.querySelector(`[data-tutorial="keypad"] [data-key="${k}"]`);
|
|
22299
|
+
}
|
|
22300
|
+
function answerBox(ctx) {
|
|
22301
|
+
const inputs = getTextInputs(ctx.root);
|
|
22302
|
+
return inputs.find(canTakeInput) ?? inputs[0] ?? null;
|
|
22303
|
+
}
|
|
22304
|
+
function answerSlots(ctx) {
|
|
22305
|
+
const id = answerBox(ctx)?.id.replace(/-input$/, "");
|
|
22306
|
+
const max = id ? getMaxInputLength(ctx.json, id) : void 0;
|
|
22307
|
+
return Math.min(MAX_SLOTS, Math.max(1, max ?? DEFAULT_SLOTS));
|
|
22308
|
+
}
|
|
22309
|
+
function checkTarget(ctx) {
|
|
22310
|
+
if (ctx.submitTarget) return ctx.submitTarget;
|
|
22311
|
+
const box = answerBox(ctx)?.getBoundingClientRect();
|
|
22312
|
+
if (!box) return null;
|
|
22313
|
+
return new DOMRect(
|
|
22314
|
+
box.right + STAND_IN_CHECK_GAP,
|
|
22315
|
+
box.top + box.height / 2 - STAND_IN_CHECK_SIZE / 2,
|
|
22316
|
+
STAND_IN_CHECK_SIZE,
|
|
22317
|
+
STAND_IN_CHECK_SIZE
|
|
22318
|
+
);
|
|
22319
|
+
}
|
|
22320
|
+
const demoKeys = (ctx) => DEMO_KEYS.map((k) => key(ctx, k));
|
|
22321
|
+
const StandInFill = ({ box, slots, filled }) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
22322
|
+
"div",
|
|
22323
|
+
{
|
|
22324
|
+
className: "met-tutorial-fill",
|
|
22325
|
+
style: { left: box.x, top: box.y, width: box.width, height: box.height },
|
|
22326
|
+
children: Array.from({ length: slots }, (_, i) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: i < filled ? "met-tutorial-fill-dot" : "met-tutorial-fill-slot", children: i < filled ? "●" : "_" }, i))
|
|
22327
|
+
}
|
|
22328
|
+
);
|
|
22329
|
+
const center = (r) => ({ x: r.x + r.width / 2, y: r.y + r.height / 2 });
|
|
22330
|
+
const HopScene = ({ ctx, spotlight, taps, tapCount }) => {
|
|
22331
|
+
const box = spotlight[1];
|
|
22332
|
+
if (!box) return null;
|
|
22333
|
+
const slots = answerSlots(ctx);
|
|
22334
|
+
const filled = tapCount === 0 ? 0 : (tapCount - 1) % slots + 1;
|
|
22335
|
+
const from = tapCount > 0 ? taps[(tapCount - 1) % taps.length] : null;
|
|
22336
|
+
let arc = null;
|
|
22337
|
+
if (from) {
|
|
22338
|
+
const a = center(from);
|
|
22339
|
+
const b = center(box);
|
|
22340
|
+
const lift = Math.max(40, Math.hypot(b.x - a.x, b.y - a.y) * 0.35);
|
|
22341
|
+
arc = `M ${a.x} ${a.y} Q ${(a.x + b.x) / 2} ${Math.min(a.y, b.y) - lift} ${b.x} ${b.y}`;
|
|
22342
|
+
}
|
|
22343
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
22344
|
+
arc && /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "met-tutorial-arc", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: arc }) }),
|
|
22345
|
+
arc && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "met-tutorial-hop", style: { offsetPath: `path('${arc}')` } }, tapCount),
|
|
22346
|
+
/* @__PURE__ */ jsxRuntime.jsx(StandInFill, { box, slots, filled })
|
|
22347
|
+
] });
|
|
22348
|
+
};
|
|
22349
|
+
const CheckScene = ({ ctx, spotlight }) => {
|
|
22350
|
+
const box = spotlight[0];
|
|
22351
|
+
const check = spotlight[1];
|
|
22352
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
22353
|
+
box && /* @__PURE__ */ jsxRuntime.jsx(StandInFill, { box, slots: answerSlots(ctx), filled: answerSlots(ctx) }),
|
|
22354
|
+
check && !ctx.submitTarget && /* @__PURE__ */ jsxRuntime.jsx(
|
|
22355
|
+
"div",
|
|
22356
|
+
{
|
|
22357
|
+
className: "met-tutorial-stand-in-check",
|
|
22358
|
+
style: { left: check.x, top: check.y, width: check.width, height: check.height },
|
|
22359
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("span", { children: "✓" })
|
|
22360
|
+
}
|
|
22361
|
+
)
|
|
22362
|
+
] });
|
|
22363
|
+
};
|
|
22364
|
+
const keypadTutorial = {
|
|
22365
|
+
id: "keypad",
|
|
22366
|
+
appliesTo: (json) => json.widgets[NUMERIC_KEYPAD_WIDGET_ID]?.type === JSON_TYPES.NUMERIC_KEYPAD,
|
|
22367
|
+
steps: [
|
|
22368
|
+
{
|
|
22369
|
+
id: "keypad.tap-number",
|
|
22370
|
+
text: "اضغط على رقم",
|
|
22371
|
+
spotlight: (ctx) => [keypad(ctx), answerBox(ctx)],
|
|
22372
|
+
taps: demoKeys
|
|
22373
|
+
},
|
|
22374
|
+
{
|
|
22375
|
+
id: "keypad.hop",
|
|
22376
|
+
text: "يظهر الرقم في الخانة",
|
|
22377
|
+
spotlight: (ctx) => [keypad(ctx), answerBox(ctx)],
|
|
22378
|
+
taps: demoKeys,
|
|
22379
|
+
Scene: HopScene
|
|
22380
|
+
},
|
|
22381
|
+
{
|
|
22382
|
+
id: "keypad.tap-check",
|
|
22383
|
+
text: "اضغط على زر التحقق",
|
|
22384
|
+
spotlight: (ctx) => [answerBox(ctx), checkTarget(ctx)],
|
|
22385
|
+
taps: (ctx) => [checkTarget(ctx)],
|
|
22386
|
+
Scene: CheckScene
|
|
22387
|
+
}
|
|
22388
|
+
]
|
|
22389
|
+
};
|
|
22390
|
+
const WIDGET_TUTORIALS = [keypadTutorial];
|
|
22391
|
+
function findWidgetTutorial(json) {
|
|
22392
|
+
return WIDGET_TUTORIALS.find((t) => t.appliesTo(json)) ?? null;
|
|
22393
|
+
}
|
|
21187
22394
|
const TOOL_ASPECT_RATIO = {
|
|
21188
22395
|
ruler: RULER_ASPECT_RATIO,
|
|
21189
22396
|
protractor: PROTRACTOR_ASPECT_RATIO,
|
|
@@ -21222,6 +22429,11 @@ function buildInitialValues(json) {
|
|
|
21222
22429
|
function toBaseId(widgetId) {
|
|
21223
22430
|
return parseFractionKey(widgetId)?.widgetId ?? widgetId;
|
|
21224
22431
|
}
|
|
22432
|
+
function hasJustFilledBox(json, key2, previous, value) {
|
|
22433
|
+
const maxLength = getMaxInputLength(json, key2);
|
|
22434
|
+
if (maxLength === void 0) return false;
|
|
22435
|
+
return String(previous ?? "").length < maxLength && String(value).length >= maxLength;
|
|
22436
|
+
}
|
|
21225
22437
|
function buildInitialValuesSignature(json) {
|
|
21226
22438
|
return JSON.stringify({
|
|
21227
22439
|
widgets: json.widgets,
|
|
@@ -21271,6 +22483,27 @@ function buildRevealedValues(json) {
|
|
|
21271
22483
|
}
|
|
21272
22484
|
return revealed;
|
|
21273
22485
|
}
|
|
22486
|
+
function buildRevealedDropSources(json, revealedValues) {
|
|
22487
|
+
const pool = [];
|
|
22488
|
+
const bank = json.widgets[DRAG_OPTIONS_WIDGET_ID];
|
|
22489
|
+
if (bank?.type === JSON_TYPES.DRAG_OPTIONS) {
|
|
22490
|
+
bank.options.forEach((value, idx) => pool.push({ optionId: `drag-option-${idx}`, value: String(value) }));
|
|
22491
|
+
}
|
|
22492
|
+
for (const [widgetId, widget] of Object.entries(json.widgets)) {
|
|
22493
|
+
if (widget.type === JSON_TYPES.INLINE_DRAG_OPTION) pool.push({ optionId: widgetId, value: widget.value });
|
|
22494
|
+
}
|
|
22495
|
+
const sources = /* @__PURE__ */ new Map();
|
|
22496
|
+
if (pool.length === 0) return sources;
|
|
22497
|
+
const used = /* @__PURE__ */ new Set();
|
|
22498
|
+
for (const [inputId, value] of Object.entries(revealedValues)) {
|
|
22499
|
+
if (typeof value !== "string" || value === "") continue;
|
|
22500
|
+
const match = pool.find((o) => !used.has(o.optionId) && normalize(o.value) === normalize(value));
|
|
22501
|
+
if (!match) continue;
|
|
22502
|
+
used.add(match.optionId);
|
|
22503
|
+
sources.set(inputId, match.optionId);
|
|
22504
|
+
}
|
|
22505
|
+
return sources;
|
|
22506
|
+
}
|
|
21274
22507
|
const ExerciseRenderer = React.forwardRef(({
|
|
21275
22508
|
json,
|
|
21276
22509
|
onSubmit,
|
|
@@ -21293,6 +22526,8 @@ const ExerciseRenderer = React.forwardRef(({
|
|
|
21293
22526
|
const rootRef = React.useRef(null);
|
|
21294
22527
|
const [resetKey, setResetKey] = React.useState(0);
|
|
21295
22528
|
const [revealed, setRevealed] = React.useState(false);
|
|
22529
|
+
const tutorial = React.useMemo(() => findWidgetTutorial(json), [json]);
|
|
22530
|
+
const [tutorialRun, setTutorialRun] = React.useState(null);
|
|
21296
22531
|
const themeStyle = React.useMemo(() => {
|
|
21297
22532
|
const style = {};
|
|
21298
22533
|
if (theme?.correct) style["--exercise-correct"] = theme.correct;
|
|
@@ -21313,6 +22548,7 @@ const ExerciseRenderer = React.forwardRef(({
|
|
|
21313
22548
|
setResetKey((k) => k + 1);
|
|
21314
22549
|
setLoadedValuesSignature(initialValuesSignature);
|
|
21315
22550
|
touchedRef.current = /* @__PURE__ */ new Set();
|
|
22551
|
+
setTutorialRun(null);
|
|
21316
22552
|
}, [json, initialValuesSignature, loadedValuesSignature]);
|
|
21317
22553
|
const locked = validationDetails !== null || externallyLocked;
|
|
21318
22554
|
const lastResultRef = React.useRef(null);
|
|
@@ -21396,7 +22632,7 @@ const ExerciseRenderer = React.forwardRef(({
|
|
|
21396
22632
|
const next = { ...valuesRef.current, ...revealedValues };
|
|
21397
22633
|
valuesRef.current = next;
|
|
21398
22634
|
setValues(next);
|
|
21399
|
-
setDroppedSources(
|
|
22635
|
+
setDroppedSources(buildRevealedDropSources(json, revealedValues));
|
|
21400
22636
|
setValidationDetails(details);
|
|
21401
22637
|
setRevealed(true);
|
|
21402
22638
|
setResetKey((k) => k + 1);
|
|
@@ -21404,20 +22640,29 @@ const ExerciseRenderer = React.forwardRef(({
|
|
|
21404
22640
|
focusFirstInput: () => {
|
|
21405
22641
|
const container = rootRef.current ?? document.querySelector(".math-exercise-renderer");
|
|
21406
22642
|
if (!container) return;
|
|
21407
|
-
const candidates =
|
|
21408
|
-
container.querySelectorAll('input[type="text"], input[type="number"], textarea, select')
|
|
21409
|
-
);
|
|
21410
|
-
const canTakeInput = (el) => !el.readOnly && !el.disabled;
|
|
22643
|
+
const candidates = getTextInputs(container);
|
|
21411
22644
|
const target = candidates.find(canTakeInput) ?? (locked ? void 0 : candidates[0]);
|
|
21412
22645
|
target?.focus();
|
|
21413
|
-
}
|
|
21414
|
-
|
|
22646
|
+
},
|
|
22647
|
+
hasTutorial: () => tutorial !== null,
|
|
22648
|
+
startTutorial: (options = {}) => {
|
|
22649
|
+
if (!tutorial || !rootRef.current) return false;
|
|
22650
|
+
setTutorialRun((prev) => ({ options, run: (prev?.run ?? 0) + 1 }));
|
|
22651
|
+
return true;
|
|
22652
|
+
},
|
|
22653
|
+
stopTutorial: () => setTutorialRun(null)
|
|
22654
|
+
}), [json, values, onSubmit, playSound, locked, initialValuesSignature, tutorial]);
|
|
22655
|
+
const focusNextInput = (currentKey) => {
|
|
22656
|
+
if (!rootRef.current) return;
|
|
22657
|
+
findNextInput(rootRef.current, currentKey)?.focus();
|
|
22658
|
+
};
|
|
21415
22659
|
const handleChange = (widgetId, value, fromDrop = false, sourceOptionId, silent = false) => {
|
|
21416
22660
|
if (locked) return;
|
|
21417
22661
|
if (!silent) {
|
|
21418
22662
|
setRevealed(false);
|
|
21419
22663
|
touchedRef.current.add(toBaseId(widgetId));
|
|
21420
22664
|
}
|
|
22665
|
+
const previous = valuesRef.current[widgetId];
|
|
21421
22666
|
const next = { ...valuesRef.current, [widgetId]: value };
|
|
21422
22667
|
valuesRef.current = next;
|
|
21423
22668
|
setValues(next);
|
|
@@ -21431,6 +22676,9 @@ const ExerciseRenderer = React.forwardRef(({
|
|
|
21431
22676
|
return next2;
|
|
21432
22677
|
});
|
|
21433
22678
|
onChange?.(next);
|
|
22679
|
+
if (!fromDrop && !silent && hasJustFilledBox(json, widgetId, previous, value)) {
|
|
22680
|
+
focusNextInput(widgetId);
|
|
22681
|
+
}
|
|
21434
22682
|
};
|
|
21435
22683
|
const handleSubmit = () => {
|
|
21436
22684
|
runSubmit();
|
|
@@ -21545,7 +22793,7 @@ const ExerciseRenderer = React.forwardRef(({
|
|
|
21545
22793
|
const rulerWidget = json.widgets[RULER_WIDGET_ID];
|
|
21546
22794
|
const protractorWidget = json.widgets[PROTRACTOR_WIDGET_ID];
|
|
21547
22795
|
const setSquareWidget = json.widgets[SET_SQUARE_WIDGET_ID];
|
|
21548
|
-
const numericKeypadWidget = json.widgets[
|
|
22796
|
+
const numericKeypadWidget = json.widgets[NUMERIC_KEYPAD_WIDGET_ID];
|
|
21549
22797
|
const { tools, toolsMinHeight } = React.useMemo(() => {
|
|
21550
22798
|
const list = [];
|
|
21551
22799
|
const PAD = 12;
|
|
@@ -21564,7 +22812,7 @@ const ExerciseRenderer = React.forwardRef(({
|
|
|
21564
22812
|
if (setSquareWidget) add2("set-square-1", "set-square", Math.round(getSize(setSquareWidget) * 160));
|
|
21565
22813
|
return { tools: list, toolsMinHeight: maxH };
|
|
21566
22814
|
}, [rulerWidget, protractorWidget, setSquareWidget]);
|
|
21567
|
-
return /* @__PURE__ */ jsxRuntime.jsx(SoundPlayerProvider, { play: playSound, children: /* @__PURE__ */ jsxRuntime.jsx(ExerciseLockProvider, { locked, children: /* @__PURE__ */ jsxRuntime.jsx(ExerciseDndProvider, { droppedSources, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: rootRef, className: `math-exercise-renderer ${revealed ? "revealed" : ""} ${locked ? "locked" : ""} ${className}`, style: themeStyle, children: [
|
|
22815
|
+
return /* @__PURE__ */ jsxRuntime.jsx(SoundPlayerProvider, { play: playSound, children: /* @__PURE__ */ jsxRuntime.jsx(ExerciseLockProvider, { locked, children: /* @__PURE__ */ jsxRuntime.jsx(ExerciseDndProvider, { droppedSources, exerciseKey: loadedValuesSignature, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: rootRef, className: `math-exercise-renderer ${revealed ? "revealed" : ""} ${locked ? "locked" : ""} ${className}`, style: themeStyle, children: [
|
|
21568
22816
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
21569
22817
|
"div",
|
|
21570
22818
|
{
|
|
@@ -21660,7 +22908,21 @@ const ExerciseRenderer = React.forwardRef(({
|
|
|
21660
22908
|
}
|
|
21661
22909
|
}
|
|
21662
22910
|
) }),
|
|
21663
|
-
showSubmitButton && /* @__PURE__ */ jsxRuntime.jsx("button", { className: "exercise-submit-btn", disabled: locked, onClick: handleSubmit, children: "Check Answer" })
|
|
22911
|
+
showSubmitButton && /* @__PURE__ */ jsxRuntime.jsx("button", { className: "exercise-submit-btn", disabled: locked, onClick: handleSubmit, children: "Check Answer" }),
|
|
22912
|
+
tutorial && tutorialRun && rootRef.current && /* @__PURE__ */ jsxRuntime.jsx(
|
|
22913
|
+
TutorialOverlay,
|
|
22914
|
+
{
|
|
22915
|
+
tutorial,
|
|
22916
|
+
root: rootRef.current,
|
|
22917
|
+
json,
|
|
22918
|
+
options: tutorialRun.options,
|
|
22919
|
+
onClose: (finished) => {
|
|
22920
|
+
setTutorialRun(null);
|
|
22921
|
+
if (finished) tutorialRun.options.onDone?.();
|
|
22922
|
+
}
|
|
22923
|
+
},
|
|
22924
|
+
tutorialRun.run
|
|
22925
|
+
)
|
|
21664
22926
|
] }) }) }) });
|
|
21665
22927
|
});
|
|
21666
22928
|
ExerciseRenderer.displayName = "ExerciseRenderer";
|
|
@@ -21716,10 +22978,16 @@ exports.ChartBarJSONSchema = ChartBarJSONSchema;
|
|
|
21716
22978
|
exports.ChartBarSchema = ChartBarSchema;
|
|
21717
22979
|
exports.ChartBarWidgetJSONSchema = ChartBarWidgetJSONSchema;
|
|
21718
22980
|
exports.ChartBlockSchema = ChartBlockSchema;
|
|
22981
|
+
exports.ChartGroupJSONSchema = ChartGroupJSONSchema;
|
|
22982
|
+
exports.ChartGroupSchema = ChartGroupSchema;
|
|
21719
22983
|
exports.ChartItemSchema = ChartItemSchema;
|
|
21720
22984
|
exports.ChartPictoJSONSchema = ChartPictoJSONSchema;
|
|
21721
22985
|
exports.ChartPictoSchema = ChartPictoSchema;
|
|
21722
22986
|
exports.ChartPictoWidgetJSONSchema = ChartPictoWidgetJSONSchema;
|
|
22987
|
+
exports.ChartSeriesBarJSONSchema = ChartSeriesBarJSONSchema;
|
|
22988
|
+
exports.ChartSeriesBarSchema = ChartSeriesBarSchema;
|
|
22989
|
+
exports.ChartSeriesJSONSchema = ChartSeriesJSONSchema;
|
|
22990
|
+
exports.ChartSeriesSchema = ChartSeriesSchema;
|
|
21723
22991
|
exports.ClockInlineSchema = ClockInlineSchema;
|
|
21724
22992
|
exports.ClockItemSchema = ClockItemSchema;
|
|
21725
22993
|
exports.ColorTextInlineSchema = ColorTextInlineSchema;
|
|
@@ -21753,11 +23021,13 @@ exports.GridSymmetryJSONSchema = GridSymmetryJSONSchema;
|
|
|
21753
23021
|
exports.GroupItemSchema = GroupItemSchema;
|
|
21754
23022
|
exports.HighlightedTextInlineSchema = HighlightedTextInlineSchema;
|
|
21755
23023
|
exports.HighlightedTextSchema = HighlightedTextSchema;
|
|
23024
|
+
exports.INLINE_INPUT_MAX_WIDTH = INLINE_INPUT_MAX_WIDTH;
|
|
21756
23025
|
exports.ImageInlineSchema = ImageInlineSchema;
|
|
21757
23026
|
exports.ImageItemSchema = ImageItemSchema;
|
|
21758
23027
|
exports.InlineDragOptionSchema = InlineDragOptionSchema;
|
|
21759
23028
|
exports.InlineInputJSONSchema = InlineInputJSONSchema;
|
|
21760
23029
|
exports.InlineInputWidgetSchema = InlineInputWidgetSchema;
|
|
23030
|
+
exports.InlineInputWidthSchema = InlineInputWidthSchema;
|
|
21761
23031
|
exports.InlineNodeSchema = InlineNodeSchema;
|
|
21762
23032
|
exports.JSON_TYPES = JSON_TYPES;
|
|
21763
23033
|
exports.LayoutItemSchema = LayoutItemSchema;
|
|
@@ -21831,9 +23101,11 @@ exports.VopRowItemSchema = VopRowItemSchema;
|
|
|
21831
23101
|
exports.VopRowSchema = VopRowSchema;
|
|
21832
23102
|
exports.VopSeparatorItemSchema = VopSeparatorItemSchema;
|
|
21833
23103
|
exports.VopSeparatorSchema = VopSeparatorSchema;
|
|
23104
|
+
exports.WIDGET_TUTORIALS = WIDGET_TUTORIALS;
|
|
21834
23105
|
exports.WidgetJSONSchema = WidgetJSONSchema;
|
|
21835
23106
|
exports.WidgetRefSchema = WidgetRefSchema;
|
|
21836
23107
|
exports.WidgetRenderer = WidgetRenderer;
|
|
23108
|
+
exports.alignGroupBarsToSeries = alignGroupBarsToSeries;
|
|
21837
23109
|
exports.compile = compile;
|
|
21838
23110
|
exports.compileWithAST = compileWithAST;
|
|
21839
23111
|
exports.computeGridSymmetryCellsToFill = computeGridSymmetryCellsToFill;
|
|
@@ -21842,7 +23114,10 @@ exports.createBoldText = createBoldText;
|
|
|
21842
23114
|
exports.createChartAxis = createChartAxis;
|
|
21843
23115
|
exports.createChartBar = createChartBar;
|
|
21844
23116
|
exports.createChartBlock = createChartBlock;
|
|
23117
|
+
exports.createChartGroup = createChartGroup;
|
|
21845
23118
|
exports.createChartPicto = createChartPicto;
|
|
23119
|
+
exports.createChartSeries = createChartSeries;
|
|
23120
|
+
exports.createChartSeriesBar = createChartSeriesBar;
|
|
21846
23121
|
exports.createColorText = createColorText;
|
|
21847
23122
|
exports.createDisplayClock = createDisplayClock;
|
|
21848
23123
|
exports.createDragOptions = createDragOptions;
|
|
@@ -21886,6 +23161,7 @@ exports.createVopSeparator = createVopSeparator;
|
|
|
21886
23161
|
exports.evaluate = evaluate;
|
|
21887
23162
|
exports.extractVariables = extractVariables;
|
|
21888
23163
|
exports.findValidCombination = findValidCombination;
|
|
23164
|
+
exports.findWidgetTutorial = findWidgetTutorial;
|
|
21889
23165
|
exports.getAnswerableInputs = getAnswerableInputs;
|
|
21890
23166
|
exports.getFractionKey = getFractionKey;
|
|
21891
23167
|
exports.getMissingInputs = getMissingInputs;
|
|
@@ -21895,9 +23171,12 @@ exports.isChartAxis = isChartAxis;
|
|
|
21895
23171
|
exports.isChartBar = isChartBar;
|
|
21896
23172
|
exports.isChartBarWidgetJSON = isChartBarWidgetJSON;
|
|
21897
23173
|
exports.isChartBlock = isChartBlock;
|
|
23174
|
+
exports.isChartGroup = isChartGroup;
|
|
21898
23175
|
exports.isChartItem = isChartItem;
|
|
21899
23176
|
exports.isChartPicto = isChartPicto;
|
|
21900
23177
|
exports.isChartPictoWidgetJSON = isChartPictoWidgetJSON;
|
|
23178
|
+
exports.isChartSeries = isChartSeries;
|
|
23179
|
+
exports.isChartSeriesBar = isChartSeriesBar;
|
|
21901
23180
|
exports.isClock = isClock;
|
|
21902
23181
|
exports.isClockItem = isClockItem;
|
|
21903
23182
|
exports.isColorText = isColorText;
|
|
@@ -21984,7 +23263,8 @@ exports.validateExpression = validateExpression;
|
|
|
21984
23263
|
exports.validateInputFormats = validateInputFormats;
|
|
21985
23264
|
exports.validateMixed = validateMixed;
|
|
21986
23265
|
exports.validateSimple = validateSimple;
|
|
23266
|
+
exports.validateUniqueIds = validateUniqueIds;
|
|
21987
23267
|
exports.validateVisualFractionCapacity = validateVisualFractionCapacity;
|
|
21988
23268
|
exports.validateWidgetJSON = validateWidgetJSON;
|
|
21989
23269
|
exports.validateWidgetJSONSafe = validateWidgetJSONSafe;
|
|
21990
|
-
//# sourceMappingURL=index-
|
|
23270
|
+
//# sourceMappingURL=index-C3qDr71I.js.map
|