@mathscan/math-exercise-engine 1.2.9 → 1.2.10

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.
@@ -100,6 +100,12 @@ const AST_TYPES = {
100
100
  CHART_BAR: "ChartBar",
101
101
  /** A single pictograph item inside a CHART[type="pictograph"] block */
102
102
  CHART_PICTO: "ChartPicto",
103
+ /** A named series (color + legend label) inside a CHART[type="grouped-bar"] block */
104
+ CHART_SERIES: "ChartSeries",
105
+ /** One category cluster inside a CHART[type="grouped-bar"] block */
106
+ CHART_GROUP: "ChartGroup",
107
+ /** A single bar inside a GROUP, referencing a declared SERIES by id */
108
+ CHART_SERIES_BAR: "ChartSeriesBar",
103
109
  // ═══════════════════════════════════════════════════════════════════════════
104
110
  // Tools
105
111
  // ═══════════════════════════════════════════════════════════════════════════
@@ -203,6 +209,10 @@ const JSON_TYPES = {
203
209
  CHART_BAR: "chart-bar",
204
210
  /** A single pictograph item rendered inside a chart */
205
211
  CHART_PICTO: "chart-picto",
212
+ /** A single bar rendered inside a grouped-bar chart's GROUP — distinct from
213
+ * CHART_BAR so the JSON self-identifies which shape it is (has `seriesId`),
214
+ * independent of the widget type used for validation. */
215
+ CHART_SERIES_BAR: "chart-series-bar",
206
216
  // ═══════════════════════════════════════════════════════════════════════════
207
217
  /** Inline input widget (string or numeric) */
208
218
  INLINE_INPUT: "inline-input",
@@ -436,7 +446,7 @@ const MATCHER = createToken({
436
446
  });
437
447
  const GROUP = createToken({
438
448
  name: "GROUP",
439
- pattern: /GROUP/
449
+ pattern: /GROUP(?=\s*[[{])/
440
450
  });
441
451
  const ITEM = createToken({
442
452
  name: "ITEM",
@@ -486,6 +496,18 @@ const PICTO = createToken({
486
496
  name: "PICTO",
487
497
  pattern: /PICTO/
488
498
  });
499
+ const LABEL = createToken({
500
+ name: "LABEL",
501
+ pattern: /LABEL(?=\s*\{)/
502
+ });
503
+ const SERIES = createToken({
504
+ name: "SERIES",
505
+ pattern: /SERIES(?=\s*\[)/
506
+ });
507
+ const SERIES_BAR = createToken({
508
+ name: "SERIES_BAR",
509
+ pattern: /SERIES_BAR(?=\s*\[)/
510
+ });
489
511
  const SEP = createToken({
490
512
  name: "SEP",
491
513
  pattern: /SEP(?![a-zA-Z0-9_])/
@@ -684,6 +706,9 @@ const allTokens = [
684
706
  Y_AXIS,
685
707
  BAR,
686
708
  PICTO,
709
+ LABEL,
710
+ SERIES_BAR,
711
+ SERIES,
687
712
  SEP,
688
713
  CARRY,
689
714
  INPUT,
@@ -1770,22 +1795,15 @@ class MathExerciseParser extends CstParser {
1770
1795
  });
1771
1796
  this.MANY6({
1772
1797
  GATE: () => {
1773
- let k = 1;
1774
- while (this.LA(k).tokenType === Space || this.LA(k).tokenType === Newline) k++;
1775
- const t = this.LA(k).tokenType;
1776
- return t === BAR || t === PICTO;
1798
+ const t = this.LA(1).tokenType;
1799
+ return t === BAR || t === PICTO || t === SERIES || t === GROUP;
1777
1800
  },
1778
1801
  DEF: () => {
1779
1802
  this.OR6([
1780
- {
1781
- GATE: () => {
1782
- let k = 1;
1783
- while (this.LA(k).tokenType === Space || this.LA(k).tokenType === Newline) k++;
1784
- return this.LA(k).tokenType === BAR;
1785
- },
1786
- ALT: () => this.SUBRULE(this.chartBarItem)
1787
- },
1788
- { ALT: () => this.SUBRULE(this.chartPictoItem) }
1803
+ { GATE: () => this.LA(1).tokenType === BAR, ALT: () => this.SUBRULE(this.chartBarItem) },
1804
+ { GATE: () => this.LA(1).tokenType === PICTO, ALT: () => this.SUBRULE(this.chartPictoItem) },
1805
+ { GATE: () => this.LA(1).tokenType === SERIES, ALT: () => this.SUBRULE(this.chartSeriesItem) },
1806
+ { ALT: () => this.SUBRULE(this.chartGroupItem) }
1789
1807
  ]);
1790
1808
  }
1791
1809
  });
@@ -1906,6 +1924,92 @@ class MathExerciseParser extends CstParser {
1906
1924
  this.OR5([{ ALT: () => this.CONSUME5(Space) }, { ALT: () => this.CONSUME5(Newline) }]);
1907
1925
  });
1908
1926
  });
1927
+ this.chartSeriesItem = this.RULE("chartSeriesItem", () => {
1928
+ this.MANY(() => {
1929
+ this.OR([{ ALT: () => this.CONSUME(Space) }, { ALT: () => this.CONSUME(Newline) }]);
1930
+ });
1931
+ this.CONSUME(SERIES);
1932
+ this.CONSUME(LBracket);
1933
+ this.SUBRULE(this.parameterList);
1934
+ this.CONSUME(RBracket);
1935
+ this.MANY2(() => {
1936
+ this.OR2([{ ALT: () => this.CONSUME2(Space) }, { ALT: () => this.CONSUME2(Newline) }]);
1937
+ });
1938
+ this.CONSUME(LBrace);
1939
+ this.MANY3(() => {
1940
+ this.OR3([{ ALT: () => this.CONSUME3(Space) }, { ALT: () => this.CONSUME3(Newline) }]);
1941
+ });
1942
+ this.MANY4(() => {
1943
+ this.OR4([
1944
+ { ALT: () => this.SUBRULE(this.inlineElement) },
1945
+ { ALT: () => this.CONSUME4(Newline) }
1946
+ ]);
1947
+ });
1948
+ this.CONSUME(RBrace);
1949
+ this.MANY5(() => {
1950
+ this.OR5([{ ALT: () => this.CONSUME5(Space) }, { ALT: () => this.CONSUME5(Newline) }]);
1951
+ });
1952
+ });
1953
+ this.chartGroupLabel = this.RULE("chartGroupLabel", () => {
1954
+ this.CONSUME(LABEL);
1955
+ this.MANY(() => {
1956
+ this.OR([{ ALT: () => this.CONSUME(Space) }, { ALT: () => this.CONSUME(Newline) }]);
1957
+ });
1958
+ this.CONSUME(LBrace);
1959
+ this.MANY2(() => {
1960
+ this.OR2([{ ALT: () => this.CONSUME2(Space) }, { ALT: () => this.CONSUME2(Newline) }]);
1961
+ });
1962
+ this.MANY3(() => {
1963
+ this.OR3([
1964
+ { ALT: () => this.SUBRULE(this.inlineElement) },
1965
+ { ALT: () => this.CONSUME3(Newline) }
1966
+ ]);
1967
+ });
1968
+ this.CONSUME(RBrace);
1969
+ this.MANY4(() => {
1970
+ this.OR4([{ ALT: () => this.CONSUME4(Space) }, { ALT: () => this.CONSUME4(Newline) }]);
1971
+ });
1972
+ });
1973
+ this.chartSeriesBarItem = this.RULE("chartSeriesBarItem", () => {
1974
+ this.MANY(() => {
1975
+ this.OR([{ ALT: () => this.CONSUME(Space) }, { ALT: () => this.CONSUME(Newline) }]);
1976
+ });
1977
+ this.CONSUME(SERIES_BAR);
1978
+ this.CONSUME(LBracket);
1979
+ this.SUBRULE(this.parameterList);
1980
+ this.CONSUME(RBracket);
1981
+ this.MANY2(() => {
1982
+ this.OR2([{ ALT: () => this.CONSUME2(Space) }, { ALT: () => this.CONSUME2(Newline) }]);
1983
+ });
1984
+ });
1985
+ this.chartGroupItem = this.RULE("chartGroupItem", () => {
1986
+ this.MANY(() => {
1987
+ this.OR([{ ALT: () => this.CONSUME(Space) }, { ALT: () => this.CONSUME(Newline) }]);
1988
+ });
1989
+ this.CONSUME(GROUP);
1990
+ this.CONSUME(LBracket);
1991
+ this.SUBRULE(this.parameterList);
1992
+ this.CONSUME(RBracket);
1993
+ this.MANY2(() => {
1994
+ this.OR2([{ ALT: () => this.CONSUME2(Space) }, { ALT: () => this.CONSUME2(Newline) }]);
1995
+ });
1996
+ this.CONSUME(LBrace);
1997
+ this.MANY3(() => {
1998
+ this.OR3([{ ALT: () => this.CONSUME3(Space) }, { ALT: () => this.CONSUME3(Newline) }]);
1999
+ });
2000
+ this.MANY4({
2001
+ GATE: () => this.LA(1).tokenType === LABEL,
2002
+ DEF: () => this.SUBRULE(this.chartGroupLabel)
2003
+ });
2004
+ this.MANY5({
2005
+ GATE: () => this.LA(1).tokenType === SERIES_BAR,
2006
+ DEF: () => this.SUBRULE(this.chartSeriesBarItem)
2007
+ });
2008
+ this.CONSUME(RBrace);
2009
+ this.MANY6(() => {
2010
+ this.OR4([{ ALT: () => this.CONSUME4(Space) }, { ALT: () => this.CONSUME4(Newline) }]);
2011
+ });
2012
+ });
1909
2013
  this.performSelfAnalysis();
1910
2014
  }
1911
2015
  /** Distinguish `{[number, number], ...}` and the valid empty form `{}` from string lists. */
@@ -3065,7 +3169,7 @@ function isSetSquareTool(value) {
3065
3169
  function isToolNode(value) {
3066
3170
  return ToolNodeSchema.safeParse(value).success;
3067
3171
  }
3068
- const CHART_TYPES = ["bar", "pictograph"];
3172
+ const CHART_TYPES = ["bar", "pictograph", "grouped-bar"];
3069
3173
  const CHART_AXIS_DEFAULTS = { min: 0, max: 100, step: 10, visible: true };
3070
3174
  const CHART_MAX_AXIS_TICKS = 200;
3071
3175
  const CHART_MAX_PICTO_SLOTS = 60;
@@ -3145,6 +3249,63 @@ function createChartBar(id, opts) {
3145
3249
  function isChartBar(value) {
3146
3250
  return ChartBarSchema.safeParse(value).success;
3147
3251
  }
3252
+ const ChartSeriesSchema = z.object({
3253
+ type: z.literal(AST_TYPES.CHART_SERIES),
3254
+ id: z.string().min(1),
3255
+ color: z.string().min(1),
3256
+ inlines: z.array(DisplayInlineNodeSchema).default([])
3257
+ });
3258
+ function createChartSeries(id, opts) {
3259
+ return ChartSeriesSchema.parse({
3260
+ type: AST_TYPES.CHART_SERIES,
3261
+ id,
3262
+ color: opts.color,
3263
+ inlines: opts.inlines ?? []
3264
+ });
3265
+ }
3266
+ function isChartSeries(value) {
3267
+ return ChartSeriesSchema.safeParse(value).success;
3268
+ }
3269
+ const ChartSeriesBarSchema = z.object({
3270
+ type: z.literal(AST_TYPES.CHART_SERIES_BAR),
3271
+ id: z.string().min(1),
3272
+ seriesId: z.string().min(1),
3273
+ value: z.number().default(0),
3274
+ answer: z.number().optional()
3275
+ });
3276
+ function createChartSeriesBar(id, opts) {
3277
+ return ChartSeriesBarSchema.parse({
3278
+ type: AST_TYPES.CHART_SERIES_BAR,
3279
+ id,
3280
+ seriesId: opts.seriesId,
3281
+ value: opts.value ?? 0,
3282
+ answer: opts.answer
3283
+ });
3284
+ }
3285
+ function isChartSeriesBar(value) {
3286
+ return ChartSeriesBarSchema.safeParse(value).success;
3287
+ }
3288
+ const ChartGroupSchema = z.object({
3289
+ type: z.literal(AST_TYPES.CHART_GROUP),
3290
+ id: z.string().min(1),
3291
+ labelInlines: z.array(InlineNodeSchema).default([]),
3292
+ bars: z.array(ChartSeriesBarSchema).default([])
3293
+ });
3294
+ function createChartGroup(id, opts = {}) {
3295
+ return ChartGroupSchema.parse({
3296
+ type: AST_TYPES.CHART_GROUP,
3297
+ id,
3298
+ labelInlines: opts.labelInlines ?? [],
3299
+ bars: opts.bars ?? []
3300
+ });
3301
+ }
3302
+ function isChartGroup(value) {
3303
+ return ChartGroupSchema.safeParse(value).success;
3304
+ }
3305
+ function alignGroupBarsToSeries(group, series) {
3306
+ const bySeriesId = new Map(group.bars.map((bar) => [bar.seriesId, bar]));
3307
+ return series.map((s) => bySeriesId.get(s.id));
3308
+ }
3148
3309
  const ChartBlockSchema = z.object({
3149
3310
  type: z.literal(AST_TYPES.CHART_BLOCK),
3150
3311
  name: z.string().optional(),
@@ -3156,7 +3317,10 @@ const ChartBlockSchema = z.object({
3156
3317
  xAxis: ChartAxisSchema.optional(),
3157
3318
  yAxis: ChartAxisSchema.optional(),
3158
3319
  bars: z.array(ChartBarSchema).default([]),
3159
- pictos: z.array(ChartPictoSchema).default([])
3320
+ pictos: z.array(ChartPictoSchema).default([]),
3321
+ legend: z.boolean().default(false),
3322
+ series: z.array(ChartSeriesSchema).default([]),
3323
+ groups: z.array(ChartGroupSchema).default([])
3160
3324
  }).superRefine((block, ctx) => {
3161
3325
  validateChartBlock(block, ctx);
3162
3326
  });
@@ -3181,10 +3345,10 @@ function validateChartBlock(block, ctx) {
3181
3345
  const { valueAxis } = resolveChartAxes(block);
3182
3346
  validateValueAxisRange(valueAxis, ctx);
3183
3347
  if (block.chartType === "bar") {
3184
- if (block.pictos.length > 0) {
3348
+ if (block.pictos.length > 0 || block.series.length > 0 || block.groups.length > 0) {
3185
3349
  ctx.addIssue({
3186
3350
  code: z.ZodIssueCode.custom,
3187
- message: 'CHART[type="bar"]: body must contain BAR items only, not PICTO',
3351
+ message: 'CHART[type="bar"]: body must contain BAR items only, not PICTO, SERIES, or GROUP',
3188
3352
  path: ["pictos"]
3189
3353
  });
3190
3354
  return;
@@ -3197,16 +3361,16 @@ function validateChartBlock(block, ctx) {
3197
3361
  });
3198
3362
  return;
3199
3363
  }
3200
- validateUniqueIds(block.bars, ctx);
3364
+ validateUniqueIds(block.bars, ctx, (i) => ["bars", i]);
3201
3365
  validateChartSnap(block.snap, valueAxis.step, ctx);
3202
- validateBarAnswers(block.bars, valueAxis, block.snap, ctx);
3366
+ validateBarAnswers(block.bars, valueAxis, block.snap, ctx, (i) => ["bars", i]);
3203
3367
  return;
3204
3368
  }
3205
3369
  if (block.chartType === "pictograph") {
3206
- if (block.bars.length > 0) {
3370
+ if (block.bars.length > 0 || block.series.length > 0 || block.groups.length > 0) {
3207
3371
  ctx.addIssue({
3208
3372
  code: z.ZodIssueCode.custom,
3209
- message: 'CHART[type="pictograph"]: body must contain PICTO items only, not BAR',
3373
+ message: 'CHART[type="pictograph"]: body must contain PICTO items only, not BAR, SERIES, or GROUP',
3210
3374
  path: ["bars"]
3211
3375
  });
3212
3376
  return;
@@ -3227,7 +3391,7 @@ function validateChartBlock(block, ctx) {
3227
3391
  });
3228
3392
  return;
3229
3393
  }
3230
- validateUniqueIds(block.pictos, ctx);
3394
+ validateUniqueIds(block.pictos, ctx, (i) => ["pictos", i]);
3231
3395
  if (!axisHasNumericParams(valueAxis.axis)) {
3232
3396
  ctx.addIssue({
3233
3397
  code: z.ZodIssueCode.custom,
@@ -3248,6 +3412,63 @@ function validateChartBlock(block, ctx) {
3248
3412
  validatePictoAnswers(block.pictos, valueAxis, ctx);
3249
3413
  return;
3250
3414
  }
3415
+ if (block.chartType === "grouped-bar") {
3416
+ if (block.bars.length > 0 || block.pictos.length > 0) {
3417
+ ctx.addIssue({
3418
+ code: z.ZodIssueCode.custom,
3419
+ message: 'CHART[type="grouped-bar"]: body must contain SERIES and GROUP items only, not BAR or PICTO',
3420
+ path: ["bars"]
3421
+ });
3422
+ return;
3423
+ }
3424
+ if (block.series.length === 0) {
3425
+ ctx.addIssue({
3426
+ code: z.ZodIssueCode.custom,
3427
+ message: 'CHART[type="grouped-bar"]: at least one SERIES is required',
3428
+ path: ["series"]
3429
+ });
3430
+ return;
3431
+ }
3432
+ if (block.groups.length === 0) {
3433
+ ctx.addIssue({
3434
+ code: z.ZodIssueCode.custom,
3435
+ message: 'CHART[type="grouped-bar"]: at least one GROUP is required',
3436
+ path: ["groups"]
3437
+ });
3438
+ return;
3439
+ }
3440
+ validateUniqueIds(block.series, ctx, (i) => ["series", i]);
3441
+ validateUniqueIds(block.groups, ctx, (i) => ["groups", i]);
3442
+ const allBars = block.groups.flatMap((g, gi) => g.bars.map((bar, bi) => ({ ...bar, groupIndex: gi, barIndex: bi })));
3443
+ const pathForFlatBar = (i) => ["groups", allBars[i].groupIndex, "bars", allBars[i].barIndex];
3444
+ validateUniqueIds(allBars, ctx, pathForFlatBar);
3445
+ const seriesIds = new Set(block.series.map((s) => s.id));
3446
+ block.groups.forEach((group, gi) => {
3447
+ const seenInGroup = /* @__PURE__ */ new Set();
3448
+ group.bars.forEach((bar, bi) => {
3449
+ if (!seriesIds.has(bar.seriesId)) {
3450
+ ctx.addIssue({
3451
+ code: z.ZodIssueCode.custom,
3452
+ message: `CHART: GROUP "${group.id}" SERIES_BAR "${bar.id}" references unknown series "${bar.seriesId}" — no SERIES with that id is declared`,
3453
+ path: ["groups", gi, "bars", bi, "seriesId"]
3454
+ });
3455
+ return;
3456
+ }
3457
+ if (seenInGroup.has(bar.seriesId)) {
3458
+ ctx.addIssue({
3459
+ code: z.ZodIssueCode.custom,
3460
+ message: `CHART: GROUP "${group.id}" references series "${bar.seriesId}" twice — each SERIES can appear at most once per GROUP`,
3461
+ path: ["groups", gi, "bars", bi, "seriesId"]
3462
+ });
3463
+ return;
3464
+ }
3465
+ seenInGroup.add(bar.seriesId);
3466
+ });
3467
+ });
3468
+ validateChartSnap(block.snap, valueAxis.step, ctx);
3469
+ validateBarAnswers(allBars, valueAxis, block.snap, ctx, pathForFlatBar);
3470
+ return;
3471
+ }
3251
3472
  ctx.addIssue({
3252
3473
  code: z.ZodIssueCode.custom,
3253
3474
  message: `CHART: unsupported chartType "${block.chartType}" — add a validation branch to validateChartBlock`,
@@ -3268,14 +3489,14 @@ function resolveNumericAxis(axis) {
3268
3489
  function hasAmbiguousValueAxes(block) {
3269
3490
  return axisHasNumericParams(block.xAxis) && axisHasNumericParams(block.yAxis);
3270
3491
  }
3271
- function validateUniqueIds(items, ctx) {
3492
+ function validateUniqueIds(items, ctx, pathFor) {
3272
3493
  const seen = /* @__PURE__ */ new Set();
3273
3494
  items.forEach((item, index) => {
3274
3495
  if (seen.has(item.id)) {
3275
3496
  ctx.addIssue({
3276
3497
  code: z.ZodIssueCode.custom,
3277
3498
  message: `CHART: duplicate id "${item.id}" — ids must be unique within the chart`,
3278
- path: [index, "id"]
3499
+ path: [...pathFor(index), "id"]
3279
3500
  });
3280
3501
  return;
3281
3502
  }
@@ -3317,14 +3538,14 @@ function validateChartSnap(snap, step, ctx) {
3317
3538
  });
3318
3539
  }
3319
3540
  }
3320
- function validateBarAnswers(bars, valueAxis, snap, ctx) {
3541
+ function validateBarAnswers(bars, valueAxis, snap, ctx, pathFor) {
3321
3542
  const gridStep = snap ?? valueAxis.step;
3322
3543
  bars.forEach((bar, index) => {
3323
3544
  if (bar.value < valueAxis.min || bar.value > valueAxis.max) {
3324
3545
  ctx.addIssue({
3325
3546
  code: z.ZodIssueCode.custom,
3326
3547
  message: `CHART: BAR "${bar.id}" value (${bar.value}) is outside axis range [${valueAxis.min}, ${valueAxis.max}] — the bar will not render correctly`,
3327
- path: ["bars", index, "value"]
3548
+ path: [...pathFor(index), "value"]
3328
3549
  });
3329
3550
  }
3330
3551
  if (bar.answer === void 0) return;
@@ -3332,7 +3553,7 @@ function validateBarAnswers(bars, valueAxis, snap, ctx) {
3332
3553
  ctx.addIssue({
3333
3554
  code: z.ZodIssueCode.custom,
3334
3555
  message: `CHART: BAR "${bar.id}" answer (${bar.answer}) is outside axis range [${valueAxis.min}, ${valueAxis.max}] — the student can never reach this value`,
3335
- path: ["bars", index, "answer"]
3556
+ path: [...pathFor(index), "answer"]
3336
3557
  });
3337
3558
  return;
3338
3559
  }
@@ -3341,7 +3562,7 @@ function validateBarAnswers(bars, valueAxis, snap, ctx) {
3341
3562
  ctx.addIssue({
3342
3563
  code: z.ZodIssueCode.custom,
3343
3564
  message: `CHART: BAR "${bar.id}" answer (${bar.answer}) is not on the drag grid (min ${valueAxis.min} + multiples of ${gridStep}) — the student can never land on it. Align the answer to the step/snap grid.`,
3344
- path: ["bars", index, "answer"]
3565
+ path: [...pathFor(index), "answer"]
3345
3566
  });
3346
3567
  }
3347
3568
  });
@@ -3403,7 +3624,10 @@ function createChartBlock(chartType, opts = {}) {
3403
3624
  xAxis: opts.xAxis,
3404
3625
  yAxis: opts.yAxis,
3405
3626
  bars: opts.bars ?? [],
3406
- pictos: opts.pictos ?? []
3627
+ pictos: opts.pictos ?? [],
3628
+ series: opts.series ?? [],
3629
+ groups: opts.groups ?? [],
3630
+ legend: opts.legend ?? false
3407
3631
  });
3408
3632
  }
3409
3633
  function isChartBlock(value) {
@@ -3490,7 +3714,9 @@ const ExerciseDocumentSchema = z.object({
3490
3714
  };
3491
3715
  const visitInlines = (inlines, basePath) => {
3492
3716
  inlines.forEach((inline, i) => {
3493
- if (inline.type === AST_TYPES.INLINE_INPUT || inline.type === AST_TYPES.FRACTION || inline.type === AST_TYPES.IMAGE) {
3717
+ if (inline.type === AST_TYPES.INLINE_INPUT || inline.type === AST_TYPES.FRACTION || inline.type === AST_TYPES.IMAGE || inline.type === AST_TYPES.VISUAL_FRACTION) {
3718
+ record(inline.name, [...basePath, i, "name"]);
3719
+ } else if (inline.type === AST_TYPES.CLOCK && inline.mode === "interactive") {
3494
3720
  record(inline.name, [...basePath, i, "name"]);
3495
3721
  }
3496
3722
  });
@@ -3522,6 +3748,28 @@ const ExerciseDocumentSchema = z.object({
3522
3748
  pm.groups.flat().forEach((item, ii) => {
3523
3749
  visitInlines(item.inlines, ["lines", bi, "items", ii, "inlines"]);
3524
3750
  });
3751
+ } else if (block.type === AST_TYPES.CHART_BLOCK) {
3752
+ const chart = block;
3753
+ const base = ["lines", bi];
3754
+ chart.bars.forEach((bar, i) => {
3755
+ if (bar.answer !== void 0) record(bar.id, [...base, "bars", i, "id"]);
3756
+ visitInlines(bar.inlines, [...base, "bars", i, "inlines"]);
3757
+ });
3758
+ chart.pictos.forEach((picto, i) => {
3759
+ if (picto.answer !== void 0) record(picto.id, [...base, "pictos", i, "id"]);
3760
+ visitInlines(picto.inlines, [...base, "pictos", i, "inlines"]);
3761
+ });
3762
+ chart.groups.forEach((group, gi) => {
3763
+ group.bars.forEach((bar, i) => {
3764
+ if (bar.answer !== void 0) record(bar.id, [...base, "groups", gi, "bars", i, "id"]);
3765
+ });
3766
+ visitInlines(group.labelInlines, [...base, "groups", gi, "labelInlines"]);
3767
+ });
3768
+ chart.series.forEach((series, i) => {
3769
+ visitInlines(series.inlines, [...base, "series", i, "inlines"]);
3770
+ });
3771
+ if (chart.xAxis) visitInlines(chart.xAxis.inlines, [...base, "xAxis", "inlines"]);
3772
+ if (chart.yAxis) visitInlines(chart.yAxis.inlines, [...base, "yAxis", "inlines"]);
3525
3773
  } else if (block.type === AST_TYPES.RADIO_OPTIONS) {
3526
3774
  record(block.name, ["lines", bi, "name"]);
3527
3775
  } else if (block.type === AST_TYPES.GEOMETRY_BLOCK && block.name !== void 0) {
@@ -4093,7 +4341,7 @@ class NaturalDSLVisitor extends BaseCstVisitor {
4093
4341
  chartBlock(ctx) {
4094
4342
  const params = this.visit(ctx.parameterList);
4095
4343
  const chartType = params.type;
4096
- if (!chartType) throw new Error('CHART: type is required ("bar" | "pictograph")');
4344
+ if (!chartType) throw new Error(`CHART: type is required (${CHART_TYPES.map((t) => `"${t}"`).join(" | ")})`);
4097
4345
  if (!CHART_TYPES.includes(chartType)) {
4098
4346
  throw new Error(`CHART: type must be one of ${CHART_TYPES.join(", ")} (got "${chartType}")`);
4099
4347
  }
@@ -4112,7 +4360,26 @@ class NaturalDSLVisitor extends BaseCstVisitor {
4112
4360
  const yAxis = ctx.chartYAxis ? this.visit(ctx.chartYAxis) : void 0;
4113
4361
  const bars = ctx.chartBarItem ? ctx.chartBarItem.map((b) => this.visit(b)) : [];
4114
4362
  const pictos = ctx.chartPictoItem ? ctx.chartPictoItem.map((p) => this.visit(p)) : [];
4115
- return createChartBlock(chartType, { bars, pictos, name, title, grid, scale, snap, xAxis, yAxis });
4363
+ const series = ctx.chartSeriesItem ? ctx.chartSeriesItem.map((s) => this.visit(s)) : [];
4364
+ const groups = ctx.chartGroupItem ? ctx.chartGroupItem.map((g) => this.visit(g)) : [];
4365
+ if (params.legend !== void 0 && chartType !== "grouped-bar") {
4366
+ throw new Error(`CHART: "legend" is only supported for type="grouped-bar" (got "${chartType}")`);
4367
+ }
4368
+ const legend = params.legend === true || params.legend === "true";
4369
+ return createChartBlock(chartType, {
4370
+ bars,
4371
+ pictos,
4372
+ series,
4373
+ groups,
4374
+ legend,
4375
+ name,
4376
+ title,
4377
+ grid,
4378
+ scale,
4379
+ snap,
4380
+ xAxis,
4381
+ yAxis
4382
+ });
4116
4383
  }
4117
4384
  /** X_AXIS and Y_AXIS carry identical params — only the produced AST type differs.
4118
4385
  * Chevrotain needs two separate grammar rules, but one visitor body serves both. */
@@ -4151,6 +4418,52 @@ class NaturalDSLVisitor extends BaseCstVisitor {
4151
4418
  inlines
4152
4419
  });
4153
4420
  }
4421
+ chartSeriesItem(ctx) {
4422
+ const params = this.visit(ctx.parameterList);
4423
+ const inlines = ctx.inlineElement ? ctx.inlineElement.map((e) => this.visit(e)) : [];
4424
+ if (inlines.some((n) => !DisplayInlineNodeSchema.safeParse(n).success)) {
4425
+ throw new Error(
4426
+ `CHART: SERIES "${params.id}" label must be display-only — inputs, drag options, clocks and interactive fractions are not allowed in a legend label`
4427
+ );
4428
+ }
4429
+ if (!params.color) {
4430
+ throw new Error(`CHART: SERIES "${params.id}" requires "color"`);
4431
+ }
4432
+ return createChartSeries(params.id, {
4433
+ color: params.color,
4434
+ inlines
4435
+ });
4436
+ }
4437
+ chartGroupLabel(ctx) {
4438
+ return ctx.inlineElement ? ctx.inlineElement.map((e) => this.visit(e)) : [];
4439
+ }
4440
+ chartGroupItem(ctx) {
4441
+ const params = this.visit(ctx.parameterList);
4442
+ if (ctx.chartGroupLabel && ctx.chartGroupLabel.length > 1) {
4443
+ throw new Error(`CHART: GROUP "${params.id}" has more than one LABEL — only one is allowed`);
4444
+ }
4445
+ const labelInlines = ctx.chartGroupLabel ? this.visit(ctx.chartGroupLabel) : [];
4446
+ const bars = ctx.chartSeriesBarItem ? ctx.chartSeriesBarItem.map((b) => this.visit(b)) : [];
4447
+ return createChartGroup(params.id, { labelInlines, bars });
4448
+ }
4449
+ // `params.series` is the raw DSL attribute name (SERIES_BAR[series="..."])
4450
+ // — unrelated to the AST field's own `seriesId` name.
4451
+ chartSeriesBarItem(ctx) {
4452
+ const params = this.visit(ctx.parameterList);
4453
+ if (!params.series) {
4454
+ throw new Error(`CHART: SERIES_BAR "${params.id}" requires "series"`);
4455
+ }
4456
+ if (params.color !== void 0) {
4457
+ throw new Error(
4458
+ `CHART: SERIES_BAR "${params.id}" cannot set "color" — it comes from its SERIES`
4459
+ );
4460
+ }
4461
+ return createChartSeriesBar(params.id, {
4462
+ seriesId: params.series,
4463
+ value: params.value !== void 0 ? Number(params.value) : void 0,
4464
+ answer: params.answer !== void 0 ? Number(params.answer) : void 0
4465
+ });
4466
+ }
4154
4467
  // ═══════════════════════════════════════════════════════════════════════════
4155
4468
  // Tools
4156
4469
  // ═══════════════════════════════════════════════════════════════════════════
@@ -4605,23 +4918,23 @@ const ChartAxisJSONSchema = z.object({
4605
4918
  });
4606
4919
  }
4607
4920
  });
4608
- const ChartBarJSONSchema = z.discriminatedUnion("interactive", [
4609
- z.object({
4610
- id: z.string(),
4611
- type: z.literal(JSON_TYPES.CHART_BAR),
4612
- interactive: z.literal(false),
4613
- value: z.number(),
4614
- color: z.string().optional(),
4615
- label: z.array(LayoutItemSchema).optional()
4616
- }),
4617
- z.object({
4618
- id: z.string(),
4619
- type: z.literal(JSON_TYPES.CHART_BAR),
4921
+ const ChartBarJSONBaseSchema = z.object({
4922
+ id: z.string(),
4923
+ type: z.literal(JSON_TYPES.CHART_BAR),
4924
+ value: z.number(),
4925
+ answer: z.number().optional(),
4926
+ color: z.string().optional(),
4927
+ label: z.array(LayoutItemSchema).optional()
4928
+ });
4929
+ const ChartBarJSONSchema = z.union([
4930
+ ChartBarJSONBaseSchema.extend({
4620
4931
  interactive: z.literal(true),
4621
- value: z.number(),
4622
- widgetId: z.string(),
4623
- color: z.string().optional(),
4624
- label: z.array(LayoutItemSchema).optional()
4932
+ /** The widgets/answers key, equal to `id` when compiled. */
4933
+ widgetId: z.string()
4934
+ }),
4935
+ ChartBarJSONBaseSchema.extend({
4936
+ interactive: z.literal(false).optional(),
4937
+ widgetId: z.undefined().optional()
4625
4938
  })
4626
4939
  ]);
4627
4940
  const ChartPictoJSONSchema = z.object({
@@ -4635,21 +4948,55 @@ const ChartPictoJSONSchema = z.object({
4635
4948
  ),
4636
4949
  label: z.array(LayoutItemSchema).optional()
4637
4950
  });
4638
- const ChartItemSchema = z.object({
4951
+ const ChartSeriesJSONSchema = z.object({
4952
+ id: z.string(),
4953
+ color: z.string(),
4954
+ label: z.array(LayoutItemSchema).optional()
4955
+ });
4956
+ const ChartSeriesBarJSONSchema = z.object({
4957
+ id: z.string(),
4958
+ type: z.literal(JSON_TYPES.CHART_SERIES_BAR),
4959
+ value: z.number(),
4960
+ answer: z.number().optional(),
4961
+ color: z.string(),
4962
+ seriesId: z.string()
4963
+ });
4964
+ const ChartGroupJSONSchema = z.object({
4965
+ id: z.string(),
4966
+ label: z.array(LayoutItemSchema).optional(),
4967
+ bars: z.array(ChartSeriesBarJSONSchema)
4968
+ });
4969
+ const ChartItemBaseSchema = z.object({
4639
4970
  id: z.string(),
4640
4971
  type: z.literal(JSON_TYPES.CHART),
4641
4972
  name: z.string().optional(),
4642
4973
  title: z.string().optional(),
4643
- chartType: z.enum(CHART_TYPES),
4644
4974
  orientation: z.enum(["vertical", "horizontal"]).default("vertical"),
4645
4975
  grid: z.boolean().default(false),
4646
4976
  scale: z.number().positive().default(1),
4647
4977
  snap: z.number().positive().optional(),
4648
4978
  xAxis: ChartAxisJSONSchema.optional(),
4649
- yAxis: ChartAxisJSONSchema.optional(),
4650
- bars: z.array(ChartBarJSONSchema).default([]),
4651
- pictos: z.array(ChartPictoJSONSchema).default([])
4652
- }).superRefine((item, ctx) => {
4979
+ yAxis: ChartAxisJSONSchema.optional()
4980
+ });
4981
+ const ChartItemBarSchema = ChartItemBaseSchema.extend({
4982
+ chartType: z.literal("bar"),
4983
+ bars: z.array(ChartBarJSONSchema)
4984
+ });
4985
+ const ChartItemPictographSchema = ChartItemBaseSchema.extend({
4986
+ chartType: z.literal("pictograph"),
4987
+ pictos: z.array(ChartPictoJSONSchema)
4988
+ });
4989
+ const ChartItemGroupedBarSchema = ChartItemBaseSchema.extend({
4990
+ chartType: z.literal("grouped-bar"),
4991
+ series: z.array(ChartSeriesJSONSchema),
4992
+ groups: z.array(ChartGroupJSONSchema),
4993
+ legend: z.boolean().default(false)
4994
+ });
4995
+ const ChartItemSchema = z.discriminatedUnion("chartType", [
4996
+ ChartItemBarSchema,
4997
+ ChartItemPictographSchema,
4998
+ ChartItemGroupedBarSchema
4999
+ ]).superRefine((item, ctx) => {
4653
5000
  if (item.chartType === "bar" && item.bars.length === 0) {
4654
5001
  ctx.addIssue({
4655
5002
  code: z.ZodIssueCode.custom,
@@ -4657,18 +5004,63 @@ const ChartItemSchema = z.object({
4657
5004
  path: ["bars"]
4658
5005
  });
4659
5006
  }
4660
- if (item.chartType === "pictograph" && item.pictos.length === 0) {
4661
- ctx.addIssue({
4662
- code: z.ZodIssueCode.custom,
4663
- message: 'CHART[type="pictograph"]: at least one PICTO is required',
4664
- path: ["pictos"]
4665
- });
5007
+ if (item.chartType === "pictograph") {
5008
+ if (item.pictos.length === 0) {
5009
+ ctx.addIssue({
5010
+ code: z.ZodIssueCode.custom,
5011
+ message: 'CHART[type="pictograph"]: at least one PICTO is required',
5012
+ path: ["pictos"]
5013
+ });
5014
+ }
5015
+ if (item.snap !== void 0) {
5016
+ ctx.addIssue({
5017
+ code: z.ZodIssueCode.custom,
5018
+ message: 'CHART[type="pictograph"]: snap is not supported for pictograph charts',
5019
+ path: ["snap"]
5020
+ });
5021
+ }
4666
5022
  }
4667
- if (item.chartType === "pictograph" && item.snap !== void 0) {
4668
- ctx.addIssue({
4669
- code: z.ZodIssueCode.custom,
4670
- message: 'CHART[type="pictograph"]: snap is not supported for pictograph charts',
4671
- path: ["snap"]
5023
+ if (item.chartType === "grouped-bar") {
5024
+ if (item.series.length === 0) {
5025
+ ctx.addIssue({
5026
+ code: z.ZodIssueCode.custom,
5027
+ message: 'CHART[type="grouped-bar"]: at least one SERIES is required',
5028
+ path: ["series"]
5029
+ });
5030
+ }
5031
+ if (item.groups.length === 0) {
5032
+ ctx.addIssue({
5033
+ code: z.ZodIssueCode.custom,
5034
+ message: 'CHART[type="grouped-bar"]: at least one GROUP is required',
5035
+ path: ["groups"]
5036
+ });
5037
+ }
5038
+ validateUniqueIds(item.series, ctx, (i) => ["series", i]);
5039
+ validateUniqueIds(item.groups, ctx, (i) => ["groups", i]);
5040
+ const allBars = item.groups.flatMap((g, gi) => g.bars.map((bar, bi) => ({ id: bar.id, gi, bi })));
5041
+ validateUniqueIds(allBars, ctx, (i) => ["groups", allBars[i].gi, "bars", allBars[i].bi]);
5042
+ const seriesIds = new Set(item.series.map((s) => s.id));
5043
+ item.groups.forEach((group, gi) => {
5044
+ const seenInGroup = /* @__PURE__ */ new Set();
5045
+ group.bars.forEach((bar, bi) => {
5046
+ if (!seriesIds.has(bar.seriesId)) {
5047
+ ctx.addIssue({
5048
+ code: z.ZodIssueCode.custom,
5049
+ message: `CHART[type="grouped-bar"]: GROUP "${group.id}" bar "${bar.id}" references unknown seriesId "${bar.seriesId}" — no SERIES with that id is declared`,
5050
+ path: ["groups", gi, "bars", bi, "seriesId"]
5051
+ });
5052
+ return;
5053
+ }
5054
+ if (seenInGroup.has(bar.seriesId)) {
5055
+ ctx.addIssue({
5056
+ code: z.ZodIssueCode.custom,
5057
+ message: `CHART[type="grouped-bar"]: GROUP "${group.id}" has two bars for seriesId "${bar.seriesId}" — each series can appear at most once per group`,
5058
+ path: ["groups", gi, "bars", bi, "seriesId"]
5059
+ });
5060
+ return;
5061
+ }
5062
+ seenInGroup.add(bar.seriesId);
5063
+ });
4672
5064
  });
4673
5065
  }
4674
5066
  });
@@ -10767,6 +11159,8 @@ function collectAllInlines(blocks) {
10767
11159
  for (const picto of chart.pictos) result.push(...picto.inlines);
10768
11160
  if (chart.xAxis) result.push(...chart.xAxis.inlines);
10769
11161
  if (chart.yAxis) result.push(...chart.yAxis.inlines);
11162
+ for (const series of chart.series) result.push(...series.inlines);
11163
+ for (const group of chart.groups) result.push(...group.labelInlines);
10770
11164
  }
10771
11165
  }
10772
11166
  return result;
@@ -10801,27 +11195,19 @@ function transformChart(block, idGenerator, inlineDragIds) {
10801
11195
  const chartAnswers = {};
10802
11196
  const bars = block.bars.map((bar) => {
10803
11197
  const label = toLabel(bar.inlines);
10804
- if (bar.answer !== void 0) {
10805
- chartWidgets[bar.id] = { type: JSON_TYPES.CHART_BAR, value: bar.value };
10806
- chartAnswers[bar.id] = bar.answer;
10807
- return {
10808
- id: bar.id,
10809
- type: JSON_TYPES.CHART_BAR,
10810
- interactive: true,
10811
- value: bar.value,
10812
- widgetId: bar.id,
10813
- ...bar.color !== void 0 && { color: bar.color },
10814
- ...label !== void 0 && { label }
10815
- };
10816
- }
10817
- return {
11198
+ const common = {
10818
11199
  id: bar.id,
10819
11200
  type: JSON_TYPES.CHART_BAR,
10820
- interactive: false,
10821
11201
  value: bar.value,
10822
11202
  ...bar.color !== void 0 && { color: bar.color },
10823
11203
  ...label !== void 0 && { label }
10824
11204
  };
11205
+ if (bar.answer !== void 0) {
11206
+ chartWidgets[bar.id] = { type: JSON_TYPES.CHART_BAR, value: bar.value };
11207
+ chartAnswers[bar.id] = bar.answer;
11208
+ return { ...common, interactive: true, widgetId: bar.id, answer: bar.answer };
11209
+ }
11210
+ return { ...common, interactive: false };
10825
11211
  });
10826
11212
  const pictos = block.pictos.map((picto) => {
10827
11213
  const label = toLabel(picto.inlines);
@@ -10838,21 +11224,62 @@ function transformChart(block, idGenerator, inlineDragIds) {
10838
11224
  ...label !== void 0 && { label }
10839
11225
  };
10840
11226
  });
10841
- const item = {
11227
+ const series = block.series.map((s) => {
11228
+ const label = toLabel(s.inlines);
11229
+ return {
11230
+ id: s.id,
11231
+ color: s.color,
11232
+ ...label !== void 0 && { label }
11233
+ };
11234
+ });
11235
+ const groups = block.groups.map((group) => {
11236
+ const slots = alignGroupBarsToSeries(group, block.series);
11237
+ const groupBars = slots.map((bar, i) => {
11238
+ if (bar === void 0) return void 0;
11239
+ const series2 = block.series[i];
11240
+ if (bar.answer !== void 0) {
11241
+ chartWidgets[bar.id] = { type: JSON_TYPES.CHART_BAR, value: bar.value };
11242
+ chartAnswers[bar.id] = bar.answer;
11243
+ }
11244
+ return {
11245
+ id: bar.id,
11246
+ type: JSON_TYPES.CHART_SERIES_BAR,
11247
+ value: bar.value,
11248
+ ...bar.answer !== void 0 && { answer: bar.answer },
11249
+ color: series2.color,
11250
+ seriesId: series2.id
11251
+ };
11252
+ }).filter((bar) => bar !== void 0);
11253
+ const label = toLabel(group.labelInlines);
11254
+ return {
11255
+ id: group.id,
11256
+ ...label !== void 0 && { label },
11257
+ bars: groupBars
11258
+ };
11259
+ });
11260
+ const base = {
10842
11261
  id: idGenerator.generate(JSON_TYPES.CHART),
10843
11262
  type: JSON_TYPES.CHART,
10844
11263
  ...block.name !== void 0 && { name: block.name },
10845
11264
  ...block.title !== void 0 && { title: block.title },
10846
- chartType: block.chartType,
10847
11265
  orientation,
10848
11266
  grid: block.grid,
10849
11267
  scale: block.scale,
10850
11268
  ...block.snap !== void 0 && { snap: block.snap },
10851
11269
  ...xAxis !== void 0 && { xAxis },
10852
- ...yAxis !== void 0 && { yAxis },
10853
- bars,
10854
- pictos
11270
+ ...yAxis !== void 0 && { yAxis }
10855
11271
  };
11272
+ let item;
11273
+ if (block.chartType === "bar") {
11274
+ item = { ...base, chartType: "bar", bars };
11275
+ } else if (block.chartType === "pictograph") {
11276
+ item = { ...base, chartType: "pictograph", pictos };
11277
+ } else if (block.chartType === "grouped-bar") {
11278
+ item = { ...base, chartType: "grouped-bar", series, groups, legend: block.legend };
11279
+ } else {
11280
+ const unhandled = block.chartType;
11281
+ throw new Error(`CHART: unsupported chart type "${String(unhandled)}"`);
11282
+ }
10856
11283
  return { item, chartWidgets, chartAnswers };
10857
11284
  }
10858
11285
  function transformNumberLine(numberLine, idGenerator, inlineDragIds) {
@@ -17517,12 +17944,17 @@ const GridSymmetry = ({
17517
17944
  }
17518
17945
  ) });
17519
17946
  };
17947
+ function interactiveBarKey(bar) {
17948
+ if (bar.answer === void 0 && bar.interactive !== true) return void 0;
17949
+ return bar.widgetId ?? bar.id;
17950
+ }
17520
17951
  function getChartClassName(item) {
17521
17952
  const isHorizontal = item.orientation === "horizontal";
17522
17953
  return [
17523
17954
  "chart",
17524
17955
  isHorizontal ? "chart--horizontal" : "",
17525
17956
  item.chartType === "pictograph" ? "chart--pictograph" : "",
17957
+ item.chartType === "grouped-bar" ? "chart--grouped-bar" : "",
17526
17958
  item.xAxis?.visible === false ? "chart--no-xaxis" : "",
17527
17959
  item.yAxis?.visible === false ? "chart--no-yaxis" : ""
17528
17960
  ].filter(Boolean).join(" ");
@@ -17533,7 +17965,8 @@ function resolveItemValue(id, defaultValue2, values) {
17533
17965
  return isNaN(parsed) ? defaultValue2 : parsed;
17534
17966
  }
17535
17967
  function resolveValue(bar, values) {
17536
- return bar.interactive ? resolveItemValue(bar.widgetId, bar.value, values) : bar.value;
17968
+ const widgetKey = interactiveBarKey(bar);
17969
+ return widgetKey !== void 0 ? resolveItemValue(widgetKey, bar.value, values) : bar.value;
17537
17970
  }
17538
17971
  function clamp(v, min, max) {
17539
17972
  return Math.min(max, Math.max(min, v));
@@ -17592,6 +18025,8 @@ const CHART_METRICS = {
17592
18025
  gapWidth: 1,
17593
18026
  axisWidth: 2.35,
17594
18027
  barWidth: 3.35,
18028
+ // Fixed narrow width so several series fit in one cluster; not derived from count.
18029
+ groupedBarWidth: 1.8,
17595
18030
  fontSize: 0.9,
17596
18031
  labelFontSize: 0.98,
17597
18032
  handleSize: 1.15,
@@ -17644,6 +18079,41 @@ function computePictographChartStyle(scale, columnCount, totalSlots, orientation
17644
18079
  "--chart-height": slotSpan
17645
18080
  };
17646
18081
  }
18082
+ function computeGroupedChartStyle(scale, groupCount, seriesCount, orientation = "vertical", isInteractive = false) {
18083
+ const visualScale = clamp(scale, CHART_METRICS.scaleMin, CHART_METRICS.scaleMax);
18084
+ const barWidth = (isInteractive ? CHART_METRICS.barWidth : CHART_METRICS.groupedBarWidth) * visualScale;
18085
+ const barGap = CHART_METRICS.gapWidth * visualScale * 0.5;
18086
+ const groupGap = CHART_METRICS.gapWidth * visualScale * 2;
18087
+ const seriesPerGroup = Math.max(1, seriesCount);
18088
+ const groupWidth = seriesPerGroup * barWidth + Math.max(0, seriesPerGroup - 1) * barGap;
18089
+ const trackLength = groupCount * groupWidth + Math.max(0, groupCount - 1) * groupGap + groupGap * 2;
18090
+ const cssVars = {
18091
+ "--chart-axis-width": `${CHART_METRICS.axisWidth * visualScale}rem`,
18092
+ "--chart-bar-width": `${barWidth}rem`,
18093
+ // A bar slot inside a group is just the bar; reuse the plain bar-chart var.
18094
+ "--chart-bar-slot-width": `${barWidth}rem`,
18095
+ "--chart-bar-gap": `${barGap}rem`,
18096
+ "--chart-group-gap": `${groupGap}rem`,
18097
+ "--chart-group-width": `${groupWidth}rem`,
18098
+ "--chart-font-size": `${CHART_METRICS.fontSize * visualScale}rem`,
18099
+ "--chart-label-font-size": `${CHART_METRICS.labelFontSize * visualScale}rem`,
18100
+ "--chart-handle-size": `${CHART_METRICS.handleSize * visualScale}rem`
18101
+ };
18102
+ if (orientation === "horizontal") {
18103
+ const valueWidth = CHART_METRICS.chartHeight * visualScale;
18104
+ return {
18105
+ ...cssVars,
18106
+ "--chart-height": `${trackLength}rem`,
18107
+ "--chart-h-value-width": `${valueWidth}rem`,
18108
+ "--chart-content-width": `${valueWidth}rem`
18109
+ };
18110
+ }
18111
+ return {
18112
+ ...cssVars,
18113
+ "--chart-height": `${CHART_METRICS.chartHeight * visualScale}rem`,
18114
+ "--chart-content-width": `${trackLength}rem`
18115
+ };
18116
+ }
17647
18117
  function computeHorizontalChartStyle(scale, barCount) {
17648
18118
  const { trackLength, visualScale, cssVars } = computeBaseChartVars(scale, barCount);
17649
18119
  const valueWidth = CHART_METRICS.chartHeight * visualScale;
@@ -17695,6 +18165,7 @@ const DraggableBar = ({
17695
18165
  const axisMax = axis.max ?? CHART_AXIS_DEFAULTS.max;
17696
18166
  const axisStep = axis.step ?? CHART_AXIS_DEFAULTS.step;
17697
18167
  const snapStep = snap ?? axisStep;
18168
+ const widgetKey = bar.widgetId ?? bar.id;
17698
18169
  const fillFromPointer = useCallback(
17699
18170
  (clientY, clientX) => {
17700
18171
  const track = trackRef.current;
@@ -17712,10 +18183,10 @@ const DraggableBar = ({
17712
18183
  if (snapped !== lastSnap.current) {
17713
18184
  lastSnap.current = snapped;
17714
18185
  playSound("bar-step");
17715
- onChange(bar.widgetId, String(snapped));
18186
+ onChange(widgetKey, String(snapped));
17716
18187
  }
17717
18188
  },
17718
- [axisMin, axisMax, snapStep, bar.widgetId, onChange, orientation, playSound]
18189
+ [axisMin, axisMax, snapStep, widgetKey, onChange, orientation, playSound]
17719
18190
  );
17720
18191
  const onPointerDown = useCallback(
17721
18192
  (e) => {
@@ -17748,8 +18219,8 @@ const DraggableBar = ({
17748
18219
  if (!dragging.current) return;
17749
18220
  dragging.current = false;
17750
18221
  setIsDragging(false);
17751
- onChange(bar.widgetId, String(preDragValue.current));
17752
- }, [bar.widgetId, onChange]);
18222
+ onChange(widgetKey, String(preDragValue.current));
18223
+ }, [widgetKey, onChange]);
17753
18224
  const onKeyDown = useCallback(
17754
18225
  (e) => {
17755
18226
  if (exerciseLocked) return;
@@ -17758,22 +18229,22 @@ const DraggableBar = ({
17758
18229
  if (isInc) {
17759
18230
  e.preventDefault();
17760
18231
  playSound("bar-step");
17761
- onChange(bar.widgetId, String(nextStepValue(currentValue, axis, 1, snap)));
18232
+ onChange(widgetKey, String(nextStepValue(currentValue, axis, 1, snap)));
17762
18233
  } else if (isDec) {
17763
18234
  e.preventDefault();
17764
18235
  playSound("bar-step");
17765
- onChange(bar.widgetId, String(nextStepValue(currentValue, axis, -1, snap)));
18236
+ onChange(widgetKey, String(nextStepValue(currentValue, axis, -1, snap)));
17766
18237
  } else if (e.key === "Home") {
17767
18238
  e.preventDefault();
17768
18239
  playSound("bar-step");
17769
- onChange(bar.widgetId, String(axisMin));
18240
+ onChange(widgetKey, String(axisMin));
17770
18241
  } else if (e.key === "End") {
17771
18242
  e.preventDefault();
17772
18243
  playSound("bar-step");
17773
- onChange(bar.widgetId, String(axisMax));
18244
+ onChange(widgetKey, String(axisMax));
17774
18245
  }
17775
18246
  },
17776
- [axisMin, axisMax, bar.widgetId, currentValue, axis, snap, onChange, playSound, exerciseLocked]
18247
+ [axisMin, axisMax, widgetKey, currentValue, axis, snap, onChange, playSound, exerciseLocked]
17777
18248
  );
17778
18249
  const fill = toFill(currentValue, axis);
17779
18250
  const isAtMin = currentValue <= axisMin;
@@ -17849,7 +18320,7 @@ const StaticBar = ({
17849
18320
  }
17850
18321
  ) });
17851
18322
  };
17852
- const EMPTY_SET$2 = /* @__PURE__ */ new Set();
18323
+ const EMPTY_SET$4 = /* @__PURE__ */ new Set();
17853
18324
  const XAxisLabel = ({
17854
18325
  bar,
17855
18326
  widgets,
@@ -17864,14 +18335,14 @@ const XAxisLabel = ({
17864
18335
  item: subItem,
17865
18336
  widgets,
17866
18337
  values,
17867
- droppedInputs: EMPTY_SET$2,
18338
+ droppedInputs: EMPTY_SET$4,
17868
18339
  validationDetails,
17869
18340
  onChange
17870
18341
  },
17871
18342
  subItem.id
17872
18343
  )) }) });
17873
18344
  };
17874
- const EMPTY_SET$1 = /* @__PURE__ */ new Set();
18345
+ const EMPTY_SET$3 = /* @__PURE__ */ new Set();
17875
18346
  const BarChart = ({
17876
18347
  item,
17877
18348
  widgets,
@@ -17879,6 +18350,7 @@ const BarChart = ({
17879
18350
  validationDetails,
17880
18351
  onChange
17881
18352
  }) => {
18353
+ if (item.chartType !== "bar") return null;
17882
18354
  const { bars, grid } = item;
17883
18355
  const isHorizontal = item.orientation === "horizontal";
17884
18356
  const valueAxis = isHorizontal ? item.xAxis : item.yAxis;
@@ -17892,7 +18364,7 @@ const BarChart = ({
17892
18364
  item: subItem,
17893
18365
  widgets,
17894
18366
  values,
17895
- droppedInputs: EMPTY_SET$1,
18367
+ droppedInputs: EMPTY_SET$3,
17896
18368
  validationDetails,
17897
18369
  onChange
17898
18370
  },
@@ -17902,9 +18374,10 @@ const BarChart = ({
17902
18374
  const fillAxis = valueAxis ?? CHART_AXIS_DEFAULTS;
17903
18375
  const renderBar = (bar, orient = "vertical") => {
17904
18376
  const currentValue = resolveValue(bar, values);
17905
- const vStatus = bar.interactive ? validationDetails?.[bar.widgetId] : void 0;
18377
+ const widgetKey = interactiveBarKey(bar);
18378
+ const vStatus = widgetKey !== void 0 ? validationDetails?.[widgetKey] : void 0;
17906
18379
  const validationStatus = vStatus?.isCorrect === true ? "correct" : vStatus?.isCorrect === false ? "incorrect" : null;
17907
- return /* @__PURE__ */ jsx("div", { className: "chart-bar-wrapper", children: bar.interactive ? /* @__PURE__ */ jsx(
18380
+ return /* @__PURE__ */ jsx("div", { className: "chart-bar-wrapper", children: widgetKey !== void 0 ? /* @__PURE__ */ jsx(
17908
18381
  DraggableBar,
17909
18382
  {
17910
18383
  bar,
@@ -17937,7 +18410,7 @@ const BarChart = ({
17937
18410
  item: subItem,
17938
18411
  widgets,
17939
18412
  values,
17940
- droppedInputs: EMPTY_SET$1,
18413
+ droppedInputs: EMPTY_SET$3,
17941
18414
  validationDetails,
17942
18415
  onChange
17943
18416
  },
@@ -18142,7 +18615,7 @@ const PictographGrid = ({
18142
18615
  ))
18143
18616
  ] });
18144
18617
  };
18145
- const EMPTY_SET = /* @__PURE__ */ new Set();
18618
+ const EMPTY_SET$2 = /* @__PURE__ */ new Set();
18146
18619
  const PictographChart = ({
18147
18620
  item,
18148
18621
  widgets,
@@ -18150,7 +18623,8 @@ const PictographChart = ({
18150
18623
  validationDetails,
18151
18624
  onChange
18152
18625
  }) => {
18153
- const { pictos = [], grid } = item;
18626
+ if (item.chartType !== "pictograph") return null;
18627
+ const { pictos, grid } = item;
18154
18628
  const isHorizontal = item.orientation === "horizontal";
18155
18629
  const valueAxis = isHorizontal ? item.xAxis : item.yAxis;
18156
18630
  const categoryAxis = isHorizontal ? item.yAxis : item.xAxis;
@@ -18163,7 +18637,7 @@ const PictographChart = ({
18163
18637
  item: subItem,
18164
18638
  widgets,
18165
18639
  values,
18166
- droppedInputs: EMPTY_SET,
18640
+ droppedInputs: EMPTY_SET$2,
18167
18641
  validationDetails,
18168
18642
  onChange
18169
18643
  },
@@ -18201,7 +18675,7 @@ const PictographChart = ({
18201
18675
  item: subItem,
18202
18676
  widgets,
18203
18677
  values,
18204
- droppedInputs: EMPTY_SET,
18678
+ droppedInputs: EMPTY_SET$2,
18205
18679
  validationDetails,
18206
18680
  onChange
18207
18681
  },
@@ -18222,7 +18696,7 @@ const PictographChart = ({
18222
18696
  item: subItem,
18223
18697
  widgets,
18224
18698
  values,
18225
- droppedInputs: EMPTY_SET,
18699
+ droppedInputs: EMPTY_SET$2,
18226
18700
  validationDetails,
18227
18701
  onChange
18228
18702
  },
@@ -18241,9 +18715,183 @@ const PictographChart = ({
18241
18715
  xAxisTitle
18242
18716
  ] }) }) });
18243
18717
  };
18718
+ const GroupSlot = ({
18719
+ group,
18720
+ series,
18721
+ axis,
18722
+ snap,
18723
+ orientation = "vertical",
18724
+ values,
18725
+ validationDetails,
18726
+ onChange
18727
+ }) => {
18728
+ return /* @__PURE__ */ jsx("div", { className: "chart-group", children: series.map((s) => {
18729
+ const bar = group.bars.find((b) => b.seriesId === s.id);
18730
+ if (!bar) {
18731
+ return /* @__PURE__ */ jsx("div", { className: "chart-group-bar-gap", "aria-hidden": "true" }, s.id);
18732
+ }
18733
+ const currentValue = resolveValue(bar, values);
18734
+ const vStatus = bar.answer !== void 0 ? validationDetails?.[bar.id] : void 0;
18735
+ const validationStatus = vStatus?.isCorrect === true ? "correct" : vStatus?.isCorrect === false ? "incorrect" : null;
18736
+ return /* @__PURE__ */ jsx("div", { className: "chart-bar-wrapper", children: bar.answer !== void 0 ? /* @__PURE__ */ jsx(
18737
+ DraggableBar,
18738
+ {
18739
+ bar: { ...bar, answer: bar.answer },
18740
+ currentValue,
18741
+ axis,
18742
+ snap,
18743
+ validationStatus,
18744
+ orientation,
18745
+ onChange
18746
+ }
18747
+ ) : /* @__PURE__ */ jsx(
18748
+ StaticBar,
18749
+ {
18750
+ bar,
18751
+ currentValue,
18752
+ axis,
18753
+ validationStatus,
18754
+ orientation
18755
+ }
18756
+ ) }, bar.id);
18757
+ }) });
18758
+ };
18759
+ const EMPTY_SET$1 = /* @__PURE__ */ new Set();
18760
+ const ChartLegend = ({
18761
+ series,
18762
+ widgets,
18763
+ values,
18764
+ validationDetails,
18765
+ onChange
18766
+ }) => {
18767
+ return /* @__PURE__ */ jsx("div", { className: "chart-legend", children: series.map((s) => /* @__PURE__ */ jsxs("div", { className: "chart-legend-item", children: [
18768
+ /* @__PURE__ */ jsx("span", { className: "chart-legend-swatch", "aria-hidden": "true", style: { backgroundColor: s.color } }),
18769
+ /* @__PURE__ */ jsx("span", { className: "chart-legend-label", children: s.label?.map((subItem) => /* @__PURE__ */ jsx(
18770
+ LayoutRenderer,
18771
+ {
18772
+ item: subItem,
18773
+ widgets,
18774
+ values,
18775
+ droppedInputs: EMPTY_SET$1,
18776
+ validationDetails,
18777
+ onChange
18778
+ },
18779
+ subItem.id
18780
+ )) })
18781
+ ] }, s.id)) });
18782
+ };
18783
+ const EMPTY_SET = /* @__PURE__ */ new Set();
18784
+ const GroupedBarChart = ({
18785
+ item,
18786
+ widgets,
18787
+ values,
18788
+ validationDetails,
18789
+ onChange
18790
+ }) => {
18791
+ if (item.chartType !== "grouped-bar") return null;
18792
+ const { groups, series, grid } = item;
18793
+ const isHorizontal = item.orientation === "horizontal";
18794
+ const isInteractive = groups.some((g) => g.bars.some((bar) => bar.answer !== void 0));
18795
+ const valueAxis = isHorizontal ? item.xAxis : item.yAxis;
18796
+ const categoryAxis = isHorizontal ? item.yAxis : item.xAxis;
18797
+ const chartClass = getChartClassName(item);
18798
+ const fillAxis = valueAxis ?? CHART_AXIS_DEFAULTS;
18799
+ const renderAxisTitle = (title) => {
18800
+ if (!title?.length) return null;
18801
+ return /* @__PURE__ */ jsx(Fragment, { children: title.map((subItem) => /* @__PURE__ */ jsx(
18802
+ LayoutRenderer,
18803
+ {
18804
+ item: subItem,
18805
+ widgets,
18806
+ values,
18807
+ droppedInputs: EMPTY_SET,
18808
+ validationDetails,
18809
+ onChange
18810
+ },
18811
+ subItem.id
18812
+ )) });
18813
+ };
18814
+ const renderGroupSlots = (orient) => /* @__PURE__ */ jsx("div", { className: "chart-groups", children: groups.map((group) => /* @__PURE__ */ jsx(
18815
+ GroupSlot,
18816
+ {
18817
+ group,
18818
+ series,
18819
+ axis: fillAxis,
18820
+ snap: item.snap,
18821
+ orientation: orient,
18822
+ values,
18823
+ validationDetails,
18824
+ onChange
18825
+ },
18826
+ group.id
18827
+ )) });
18828
+ const legend = item.legend ? /* @__PURE__ */ jsx(
18829
+ ChartLegend,
18830
+ {
18831
+ series,
18832
+ widgets,
18833
+ values,
18834
+ validationDetails,
18835
+ onChange
18836
+ }
18837
+ ) : null;
18838
+ if (isHorizontal) {
18839
+ const chartStyle2 = computeGroupedChartStyle(item.scale, groups.length, series.length, "horizontal", isInteractive);
18840
+ return /* @__PURE__ */ jsx("div", { className: "chart-container", children: /* @__PURE__ */ jsx("div", { className: "chart-scroll", children: /* @__PURE__ */ jsxs("div", { className: chartClass, dir: "ltr", style: chartStyle2, children: [
18841
+ item.title && /* @__PURE__ */ jsx("div", { className: "chart-title", children: item.title }),
18842
+ categoryAxis?.visible && categoryAxis.title?.length ? /* @__PURE__ */ jsx("div", { className: "chart-axis-title chart-axis-title--y-h", children: renderAxisTitle(categoryAxis.title) }) : null,
18843
+ /* @__PURE__ */ jsx("div", { className: "chart-axis-y--categories", children: groups.map((group) => /* @__PURE__ */ jsx("div", { className: "chart-bar-y-label", children: group.label?.map((subItem) => /* @__PURE__ */ jsx(
18844
+ LayoutRenderer,
18845
+ {
18846
+ item: subItem,
18847
+ widgets,
18848
+ values,
18849
+ droppedInputs: EMPTY_SET,
18850
+ validationDetails,
18851
+ onChange
18852
+ },
18853
+ subItem.id
18854
+ )) }, group.id)) }),
18855
+ /* @__PURE__ */ jsxs("div", { className: "chart-plot", children: [
18856
+ grid && /* @__PURE__ */ jsx(Grid, { axis: fillAxis, orientation: "horizontal" }),
18857
+ renderGroupSlots("horizontal")
18858
+ ] }),
18859
+ valueAxis && valueAxis.visible && /* @__PURE__ */ jsx(XAxis, { axis: valueAxis, title: renderAxisTitle(valueAxis.title) }),
18860
+ legend
18861
+ ] }) }) });
18862
+ }
18863
+ const chartStyle = computeGroupedChartStyle(item.scale, groups.length, series.length, "vertical", isInteractive);
18864
+ const hasAnyGroupLabel = groups.some((g) => g.label && g.label.length > 0);
18865
+ const groupLabelRow = hasAnyGroupLabel ? /* @__PURE__ */ jsx("div", { className: "chart-axis-x", children: groups.map((group) => /* @__PURE__ */ jsx("div", { className: "chart-axis-tick-x", children: /* @__PURE__ */ jsx("div", { className: "chart-axis-tick-x-inner", children: group.label?.map((subItem) => /* @__PURE__ */ jsx(
18866
+ LayoutRenderer,
18867
+ {
18868
+ item: subItem,
18869
+ widgets,
18870
+ values,
18871
+ droppedInputs: EMPTY_SET,
18872
+ validationDetails,
18873
+ onChange
18874
+ },
18875
+ subItem.id
18876
+ )) }) }, group.id)) }) : null;
18877
+ const xAxisTitle = categoryAxis?.visible && categoryAxis.title?.length ? /* @__PURE__ */ jsx("div", { className: "chart-axis-title chart-axis-title--x-category", children: renderAxisTitle(categoryAxis.title) }) : null;
18878
+ return /* @__PURE__ */ jsx("div", { className: "chart-container", children: /* @__PURE__ */ jsx("div", { className: "chart-scroll", children: /* @__PURE__ */ jsxs("div", { className: chartClass, dir: "ltr", style: chartStyle, children: [
18879
+ item.title && /* @__PURE__ */ jsx("div", { className: "chart-title", children: item.title }),
18880
+ valueAxis?.visible && valueAxis.title?.length ? /* @__PURE__ */ jsx("div", { className: "chart-axis-title chart-axis-title--y", children: renderAxisTitle(valueAxis.title) }) : null,
18881
+ valueAxis && valueAxis.visible && /* @__PURE__ */ jsx(YAxis, { axis: valueAxis }),
18882
+ /* @__PURE__ */ jsxs("div", { className: "chart-plot", children: [
18883
+ grid && /* @__PURE__ */ jsx(Grid, { axis: fillAxis }),
18884
+ renderGroupSlots("vertical")
18885
+ ] }),
18886
+ groupLabelRow,
18887
+ xAxisTitle,
18888
+ legend
18889
+ ] }) }) });
18890
+ };
18244
18891
  const CHART_TYPE_COMPONENTS = {
18245
18892
  bar: BarChart,
18246
- pictograph: PictographChart
18893
+ pictograph: PictographChart,
18894
+ "grouped-bar": GroupedBarChart
18247
18895
  // line: LineChart,
18248
18896
  // pie: PieChart,
18249
18897
  };
@@ -18926,7 +19574,7 @@ function isGeometryWidget(widget) {
18926
19574
  return widget.type === JSON_TYPES.GEOMETRY;
18927
19575
  }
18928
19576
  const GeometryWidget = React.lazy(async () => ({
18929
- default: (await import("./GeometryWidget-BRV6LLcW.mjs")).GeometryWidget
19577
+ default: (await import("./GeometryWidget-gtAP_-_S.mjs")).GeometryWidget
18930
19578
  }));
18931
19579
  const WidgetRenderer = ({
18932
19580
  widget,
@@ -21728,7 +22376,7 @@ export {
21728
22376
  isTableRowItem as Z,
21729
22377
  isTableCellItem as _,
21730
22378
  compileWithAST as a,
21731
- EngineError as a$,
22379
+ validateExerciseJSONSafe as a$,
21732
22380
  isVerticalOperationItem as a0,
21733
22381
  isClockItem as a1,
21734
22382
  isRulerWidget as a2,
@@ -21747,25 +22395,25 @@ export {
21747
22395
  ChartAxisJSONSchema as aF,
21748
22396
  ChartBarJSONSchema as aG,
21749
22397
  ChartPictoJSONSchema as aH,
21750
- WidgetJSONSchema as aI,
21751
- InlineInputJSONSchema as aJ,
21752
- DragOptionsJSONSchema as aK,
21753
- RadioOptionsJSONSchema as aL,
21754
- NumericKeypadJSONSchema as aM,
21755
- FractionJSONSchema as aN,
21756
- MatcherWidgetJSONSchema as aO,
21757
- PairMatcherWidgetJSONSchema as aP,
21758
- GridSymmetryJSONSchema as aQ,
21759
- ChartBarWidgetJSONSchema as aR,
21760
- ChartPictoWidgetJSONSchema as aS,
21761
- RulerWidgetSchema as aT,
21762
- ProtractorWidgetSchema as aU,
21763
- SetSquareWidgetSchema as aV,
21764
- GeometryJSONSchema as aW,
21765
- validateExerciseJSON as aX,
21766
- validateExerciseJSONSafe as aY,
21767
- validateWidgetJSON as aZ,
21768
- validateWidgetJSONSafe as a_,
22398
+ ChartSeriesJSONSchema as aI,
22399
+ ChartSeriesBarJSONSchema as aJ,
22400
+ ChartGroupJSONSchema as aK,
22401
+ WidgetJSONSchema as aL,
22402
+ InlineInputJSONSchema as aM,
22403
+ DragOptionsJSONSchema as aN,
22404
+ RadioOptionsJSONSchema as aO,
22405
+ NumericKeypadJSONSchema as aP,
22406
+ FractionJSONSchema as aQ,
22407
+ MatcherWidgetJSONSchema as aR,
22408
+ PairMatcherWidgetJSONSchema as aS,
22409
+ GridSymmetryJSONSchema as aT,
22410
+ ChartBarWidgetJSONSchema as aU,
22411
+ ChartPictoWidgetJSONSchema as aV,
22412
+ RulerWidgetSchema as aW,
22413
+ ProtractorWidgetSchema as aX,
22414
+ SetSquareWidgetSchema as aY,
22415
+ GeometryJSONSchema as aZ,
22416
+ validateExerciseJSON as a_,
21769
22417
  QuestionTextSchema as aa,
21770
22418
  TextItemSchema as ab,
21771
22419
  WidgetRefSchema as ac,
@@ -21793,178 +22441,192 @@ export {
21793
22441
  VopSeparatorItemSchema as ay,
21794
22442
  VopCellItemSchema as az,
21795
22443
  tokenizeSafe as b,
21796
- MatcherItemSchema as b$,
21797
- LexerError as b0,
21798
- ParseError as b1,
21799
- TransformError as b2,
21800
- ValidationError as b3,
21801
- ExerciseRenderer as b4,
21802
- LayoutRenderer as b5,
21803
- WidgetRenderer as b6,
21804
- renderExercise as b7,
21805
- SoundProvider as b8,
21806
- useSound as b9,
21807
- createHighlightedText as bA,
21808
- createColorText as bB,
21809
- createBoldText as bC,
21810
- createStringInput as bD,
21811
- createNumericInput as bE,
21812
- createImage as bF,
21813
- createFraction as bG,
21814
- createVisualFraction as bH,
21815
- createInlineDragOption as bI,
21816
- createDisplayClock as bJ,
21817
- createInteractiveClock as bK,
21818
- isFreeText as bL,
21819
- isSpace as bM,
21820
- isInlineInput as bN,
21821
- isFraction as bO,
21822
- isImage as bP,
21823
- isClock as bQ,
21824
- isInlineNode as bR,
21825
- isVisualFractionInline as bS,
21826
- isInlineDragOption as bT,
21827
- TextBlockSchema as bU,
21828
- LineBlockSchema as bV,
21829
- createTextBlock as bW,
21830
- createLineBlock as bX,
21831
- isTextBlock as bY,
21832
- isLineBlock as bZ,
21833
- DisplayInlineNodeSchema as b_,
21834
- ExerciseLockProvider as ba,
21835
- isPairMatcherWidget as bb,
21836
- isMatcherWidget as bc,
21837
- VISUAL_FRACTION_SHAPES as bd,
21838
- VISUAL_FRACTION_SHAPE as be,
21839
- VISUAL_FRACTION_DEFAULT_SIZE as bf,
21840
- VISUAL_FRACTION_MAX_DIVISIONS as bg,
21841
- FreeTextInlineSchema as bh,
21842
- SpaceInlineSchema as bi,
21843
- HighlightedTextInlineSchema as bj,
21844
- ColorTextInlineSchema as bk,
21845
- BoldTextInlineSchema as bl,
21846
- InlineInputWidgetSchema as bm,
21847
- FractionInlineSchema as bn,
21848
- ImageInlineSchema as bo,
21849
- VisualFractionInlineSchema as bp,
21850
- validateVisualFractionCapacity as bq,
21851
- InlineDragOptionSchema as br,
21852
- CLOCK_TYPES as bs,
21853
- CLOCK_MODES as bt,
21854
- CLOCK_FORMATS as bu,
21855
- CLOCK_TIME_REGEX as bv,
21856
- ClockInlineSchema as bw,
21857
- InlineNodeSchema as bx,
21858
- createFreeText as by,
21859
- createSpace as bz,
22444
+ isTextBlock as b$,
22445
+ validateWidgetJSON as b0,
22446
+ validateWidgetJSONSafe as b1,
22447
+ EngineError as b2,
22448
+ LexerError as b3,
22449
+ ParseError as b4,
22450
+ TransformError as b5,
22451
+ ValidationError as b6,
22452
+ ExerciseRenderer as b7,
22453
+ LayoutRenderer as b8,
22454
+ WidgetRenderer as b9,
22455
+ InlineNodeSchema as bA,
22456
+ createFreeText as bB,
22457
+ createSpace as bC,
22458
+ createHighlightedText as bD,
22459
+ createColorText as bE,
22460
+ createBoldText as bF,
22461
+ createStringInput as bG,
22462
+ createNumericInput as bH,
22463
+ createImage as bI,
22464
+ createFraction as bJ,
22465
+ createVisualFraction as bK,
22466
+ createInlineDragOption as bL,
22467
+ createDisplayClock as bM,
22468
+ createInteractiveClock as bN,
22469
+ isFreeText as bO,
22470
+ isSpace as bP,
22471
+ isInlineInput as bQ,
22472
+ isFraction as bR,
22473
+ isImage as bS,
22474
+ isClock as bT,
22475
+ isInlineNode as bU,
22476
+ isVisualFractionInline as bV,
22477
+ isInlineDragOption as bW,
22478
+ TextBlockSchema as bX,
22479
+ LineBlockSchema as bY,
22480
+ createTextBlock as bZ,
22481
+ createLineBlock as b_,
22482
+ renderExercise as ba,
22483
+ SoundProvider as bb,
22484
+ useSound as bc,
22485
+ ExerciseLockProvider as bd,
22486
+ isPairMatcherWidget as be,
22487
+ isMatcherWidget as bf,
22488
+ VISUAL_FRACTION_SHAPES as bg,
22489
+ VISUAL_FRACTION_SHAPE as bh,
22490
+ VISUAL_FRACTION_DEFAULT_SIZE as bi,
22491
+ VISUAL_FRACTION_MAX_DIVISIONS as bj,
22492
+ FreeTextInlineSchema as bk,
22493
+ SpaceInlineSchema as bl,
22494
+ HighlightedTextInlineSchema as bm,
22495
+ ColorTextInlineSchema as bn,
22496
+ BoldTextInlineSchema as bo,
22497
+ InlineInputWidgetSchema as bp,
22498
+ FractionInlineSchema as bq,
22499
+ ImageInlineSchema as br,
22500
+ VisualFractionInlineSchema as bs,
22501
+ validateVisualFractionCapacity as bt,
22502
+ InlineDragOptionSchema as bu,
22503
+ CLOCK_TYPES as bv,
22504
+ CLOCK_MODES as bw,
22505
+ CLOCK_FORMATS as bx,
22506
+ CLOCK_TIME_REGEX as by,
22507
+ ClockInlineSchema as bz,
21860
22508
  compile as c,
21861
- createVopCarryRow as c$,
21862
- createMatcherItem as c0,
21863
- isMatcherItem as c1,
21864
- MatcherRowSchema as c2,
21865
- createMatcherRow as c3,
21866
- isMatcherRow as c4,
21867
- MatcherBlockSchema as c5,
21868
- createMatcherBlock as c6,
21869
- isMatcherBlock as c7,
21870
- PairMatcherBlockSchema as c8,
21871
- createPairMatcherBlock as c9,
21872
- isExpressionValidation as cA,
21873
- isMixedValidation as cB,
21874
- isValidationNode as cC,
21875
- ContentBlockSchema as cD,
21876
- ExerciseDocumentSchema as cE,
21877
- createExerciseDocument as cF,
21878
- isExerciseDocument as cG,
21879
- VOP_OPERATORS as cH,
21880
- normalizeVopOperator as cI,
21881
- VopDigitCellSchema as cJ,
21882
- VopLabelCellSchema as cK,
21883
- VopInputCellSchema as cL,
21884
- VopEmptyCellSchema as cM,
21885
- VopColorCellSchema as cN,
21886
- VopBoldCellSchema as cO,
21887
- VopHighlightCellSchema as cP,
21888
- VopCellSchema as cQ,
21889
- createVopDigitCell as cR,
21890
- createVopInputCell as cS,
21891
- createVopLabelCell as cT,
21892
- createVopEmptyCell as cU,
21893
- createVopColorCell as cV,
21894
- createVopBoldCell as cW,
21895
- createVopHighlightCell as cX,
21896
- VopRowSchema as cY,
21897
- createVopRow as cZ,
21898
- VopCarryRowSchema as c_,
21899
- isPairMatcherBlock as ca,
21900
- DragOptionsToolSchema as cb,
21901
- NumericKeypadToolSchema as cc,
21902
- RulerToolSchema as cd,
21903
- ProtractorToolSchema as ce,
21904
- SetSquareToolSchema as cf,
21905
- ToolNodeSchema as cg,
21906
- createDragOptions as ch,
21907
- createNumericKeypad as ci,
21908
- createRulerTool as cj,
21909
- createProtractorTool as ck,
21910
- createSetSquareTool as cl,
21911
- isDragOptions as cm,
21912
- isNumericKeypad as cn,
21913
- isRulerTool as co,
21914
- isProtractorTool as cp,
21915
- isSetSquareTool as cq,
21916
- isToolNode as cr,
21917
- SimpleValidationSchema as cs,
21918
- ExpressionValidationSchema as ct,
21919
- MixedValidationSchema as cu,
21920
- ValidationNodeSchema as cv,
21921
- createSimpleValidation as cw,
21922
- createExpressionValidation as cx,
21923
- createMixedValidation as cy,
21924
- isSimpleValidation as cz,
22509
+ VopRowSchema as c$,
22510
+ isLineBlock as c0,
22511
+ DisplayInlineNodeSchema as c1,
22512
+ MatcherItemSchema as c2,
22513
+ createMatcherItem as c3,
22514
+ isMatcherItem as c4,
22515
+ MatcherRowSchema as c5,
22516
+ createMatcherRow as c6,
22517
+ isMatcherRow as c7,
22518
+ MatcherBlockSchema as c8,
22519
+ createMatcherBlock as c9,
22520
+ createExpressionValidation as cA,
22521
+ createMixedValidation as cB,
22522
+ isSimpleValidation as cC,
22523
+ isExpressionValidation as cD,
22524
+ isMixedValidation as cE,
22525
+ isValidationNode as cF,
22526
+ ContentBlockSchema as cG,
22527
+ ExerciseDocumentSchema as cH,
22528
+ createExerciseDocument as cI,
22529
+ isExerciseDocument as cJ,
22530
+ VOP_OPERATORS as cK,
22531
+ normalizeVopOperator as cL,
22532
+ VopDigitCellSchema as cM,
22533
+ VopLabelCellSchema as cN,
22534
+ VopInputCellSchema as cO,
22535
+ VopEmptyCellSchema as cP,
22536
+ VopColorCellSchema as cQ,
22537
+ VopBoldCellSchema as cR,
22538
+ VopHighlightCellSchema as cS,
22539
+ VopCellSchema as cT,
22540
+ createVopDigitCell as cU,
22541
+ createVopInputCell as cV,
22542
+ createVopLabelCell as cW,
22543
+ createVopEmptyCell as cX,
22544
+ createVopColorCell as cY,
22545
+ createVopBoldCell as cZ,
22546
+ createVopHighlightCell as c_,
22547
+ isMatcherBlock as ca,
22548
+ PairMatcherBlockSchema as cb,
22549
+ createPairMatcherBlock as cc,
22550
+ isPairMatcherBlock as cd,
22551
+ DragOptionsToolSchema as ce,
22552
+ NumericKeypadToolSchema as cf,
22553
+ RulerToolSchema as cg,
22554
+ ProtractorToolSchema as ch,
22555
+ SetSquareToolSchema as ci,
22556
+ ToolNodeSchema as cj,
22557
+ createDragOptions as ck,
22558
+ createNumericKeypad as cl,
22559
+ createRulerTool as cm,
22560
+ createProtractorTool as cn,
22561
+ createSetSquareTool as co,
22562
+ isDragOptions as cp,
22563
+ isNumericKeypad as cq,
22564
+ isRulerTool as cr,
22565
+ isProtractorTool as cs,
22566
+ isSetSquareTool as ct,
22567
+ isToolNode as cu,
22568
+ SimpleValidationSchema as cv,
22569
+ ExpressionValidationSchema as cw,
22570
+ MixedValidationSchema as cx,
22571
+ ValidationNodeSchema as cy,
22572
+ createSimpleValidation as cz,
21925
22573
  parse as d,
21926
- VopSeparatorSchema as d0,
21927
- createVopSeparator as d1,
21928
- VerticalOperationBlockSchema as d2,
21929
- createVerticalOperationBlock as d3,
21930
- isVopCell as d4,
21931
- isVopRow as d5,
21932
- isVopCarryRow as d6,
21933
- isVopSeparator as d7,
21934
- isVerticalOperationBlock as d8,
21935
- GRID_SYMMETRY_AXES as d9,
21936
- ChartBlockSchema as dA,
21937
- resolveChartAxes as dB,
21938
- createChartBlock as dC,
21939
- isChartBlock as dD,
21940
- GeometryBlockSchema as dE,
21941
- createGeometryBlock as dF,
21942
- isGeometryBlock as dG,
21943
- GRID_SYMMETRY_INTERACTIVE_AREAS as da,
21944
- GRID_SYMMETRY_MAX_ROWS as db,
21945
- GRID_SYMMETRY_MAX_COLS as dc,
21946
- GRID_SYMMETRY_MIN_CELL_SIZE as dd,
21947
- GRID_SYMMETRY_MAX_CELL_SIZE as de,
21948
- isCellInInteractiveArea as df,
21949
- reflectGridSymmetryCell as dg,
21950
- computeGridSymmetryExpectedCells as dh,
21951
- computeGridSymmetryCellsToFill as di,
21952
- GridSymmetryBlockSchema as dj,
21953
- createGridSymmetryBlock as dk,
21954
- isGridSymmetryBlock as dl,
21955
- CHART_TYPES as dm,
21956
- CHART_AXIS_DEFAULTS as dn,
21957
- CHART_MAX_AXIS_TICKS as dp,
21958
- CHART_MAX_PICTO_SLOTS as dq,
21959
- ChartAxisSchema as dr,
21960
- createChartAxis as ds,
21961
- isChartAxis as dt,
21962
- ChartPictoSchema as du,
21963
- createChartPicto as dv,
21964
- isChartPicto as dw,
21965
- ChartBarSchema as dx,
21966
- createChartBar as dy,
21967
- isChartBar as dz,
22574
+ createVopRow as d0,
22575
+ VopCarryRowSchema as d1,
22576
+ createVopCarryRow as d2,
22577
+ VopSeparatorSchema as d3,
22578
+ createVopSeparator as d4,
22579
+ VerticalOperationBlockSchema as d5,
22580
+ createVerticalOperationBlock as d6,
22581
+ isVopCell as d7,
22582
+ isVopRow as d8,
22583
+ isVopCarryRow as d9,
22584
+ ChartBarSchema as dA,
22585
+ createChartBar as dB,
22586
+ isChartBar as dC,
22587
+ ChartSeriesSchema as dD,
22588
+ createChartSeries as dE,
22589
+ isChartSeries as dF,
22590
+ ChartSeriesBarSchema as dG,
22591
+ createChartSeriesBar as dH,
22592
+ isChartSeriesBar as dI,
22593
+ ChartGroupSchema as dJ,
22594
+ createChartGroup as dK,
22595
+ isChartGroup as dL,
22596
+ alignGroupBarsToSeries as dM,
22597
+ ChartBlockSchema as dN,
22598
+ resolveChartAxes as dO,
22599
+ validateUniqueIds as dP,
22600
+ createChartBlock as dQ,
22601
+ isChartBlock as dR,
22602
+ GeometryBlockSchema as dS,
22603
+ createGeometryBlock as dT,
22604
+ isGeometryBlock as dU,
22605
+ isVopSeparator as da,
22606
+ isVerticalOperationBlock as db,
22607
+ GRID_SYMMETRY_AXES as dc,
22608
+ GRID_SYMMETRY_INTERACTIVE_AREAS as dd,
22609
+ GRID_SYMMETRY_MAX_ROWS as de,
22610
+ GRID_SYMMETRY_MAX_COLS as df,
22611
+ GRID_SYMMETRY_MIN_CELL_SIZE as dg,
22612
+ GRID_SYMMETRY_MAX_CELL_SIZE as dh,
22613
+ isCellInInteractiveArea as di,
22614
+ reflectGridSymmetryCell as dj,
22615
+ computeGridSymmetryExpectedCells as dk,
22616
+ computeGridSymmetryCellsToFill as dl,
22617
+ GridSymmetryBlockSchema as dm,
22618
+ createGridSymmetryBlock as dn,
22619
+ isGridSymmetryBlock as dp,
22620
+ CHART_TYPES as dq,
22621
+ CHART_AXIS_DEFAULTS as dr,
22622
+ CHART_MAX_AXIS_TICKS as ds,
22623
+ CHART_MAX_PICTO_SLOTS as dt,
22624
+ ChartAxisSchema as du,
22625
+ createChartAxis as dv,
22626
+ isChartAxis as dw,
22627
+ ChartPictoSchema as dx,
22628
+ createChartPicto as dy,
22629
+ isChartPicto as dz,
21968
22630
  parseSafe as e,
21969
22631
  toAST as f,
21970
22632
  getFractionKey as g,
@@ -21988,4 +22650,4 @@ export {
21988
22650
  extractVariables as y,
21989
22651
  validateBooleanExpression as z
21990
22652
  };
21991
- //# sourceMappingURL=index-XAInSHXa.mjs.map
22653
+ //# sourceMappingURL=index-DBteHZkS.mjs.map