@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.
@@ -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",
@@ -437,7 +447,7 @@ const MATCHER = chevrotain.createToken({
437
447
  });
438
448
  const GROUP = chevrotain.createToken({
439
449
  name: "GROUP",
440
- pattern: /GROUP/
450
+ pattern: /GROUP(?=\s*[[{])/
441
451
  });
442
452
  const ITEM = chevrotain.createToken({
443
453
  name: "ITEM",
@@ -487,6 +497,18 @@ const PICTO = chevrotain.createToken({
487
497
  name: "PICTO",
488
498
  pattern: /PICTO/
489
499
  });
500
+ const LABEL = chevrotain.createToken({
501
+ name: "LABEL",
502
+ pattern: /LABEL(?=\s*\{)/
503
+ });
504
+ const SERIES = chevrotain.createToken({
505
+ name: "SERIES",
506
+ pattern: /SERIES(?=\s*\[)/
507
+ });
508
+ const SERIES_BAR = chevrotain.createToken({
509
+ name: "SERIES_BAR",
510
+ pattern: /SERIES_BAR(?=\s*\[)/
511
+ });
490
512
  const SEP = chevrotain.createToken({
491
513
  name: "SEP",
492
514
  pattern: /SEP(?![a-zA-Z0-9_])/
@@ -685,6 +707,9 @@ const allTokens = [
685
707
  Y_AXIS,
686
708
  BAR,
687
709
  PICTO,
710
+ LABEL,
711
+ SERIES_BAR,
712
+ SERIES,
688
713
  SEP,
689
714
  CARRY,
690
715
  INPUT,
@@ -1771,22 +1796,15 @@ class MathExerciseParser extends chevrotain.CstParser {
1771
1796
  });
1772
1797
  this.MANY6({
1773
1798
  GATE: () => {
1774
- let k = 1;
1775
- while (this.LA(k).tokenType === Space || this.LA(k).tokenType === Newline) k++;
1776
- const t = this.LA(k).tokenType;
1777
- return t === BAR || t === PICTO;
1799
+ const t = this.LA(1).tokenType;
1800
+ return t === BAR || t === PICTO || t === SERIES || t === GROUP;
1778
1801
  },
1779
1802
  DEF: () => {
1780
1803
  this.OR6([
1781
- {
1782
- GATE: () => {
1783
- let k = 1;
1784
- while (this.LA(k).tokenType === Space || this.LA(k).tokenType === Newline) k++;
1785
- return this.LA(k).tokenType === BAR;
1786
- },
1787
- ALT: () => this.SUBRULE(this.chartBarItem)
1788
- },
1789
- { ALT: () => this.SUBRULE(this.chartPictoItem) }
1804
+ { GATE: () => this.LA(1).tokenType === BAR, ALT: () => this.SUBRULE(this.chartBarItem) },
1805
+ { GATE: () => this.LA(1).tokenType === PICTO, ALT: () => this.SUBRULE(this.chartPictoItem) },
1806
+ { GATE: () => this.LA(1).tokenType === SERIES, ALT: () => this.SUBRULE(this.chartSeriesItem) },
1807
+ { ALT: () => this.SUBRULE(this.chartGroupItem) }
1790
1808
  ]);
1791
1809
  }
1792
1810
  });
@@ -1907,6 +1925,92 @@ class MathExerciseParser extends chevrotain.CstParser {
1907
1925
  this.OR5([{ ALT: () => this.CONSUME5(Space) }, { ALT: () => this.CONSUME5(Newline) }]);
1908
1926
  });
1909
1927
  });
1928
+ this.chartSeriesItem = this.RULE("chartSeriesItem", () => {
1929
+ this.MANY(() => {
1930
+ this.OR([{ ALT: () => this.CONSUME(Space) }, { ALT: () => this.CONSUME(Newline) }]);
1931
+ });
1932
+ this.CONSUME(SERIES);
1933
+ this.CONSUME(LBracket);
1934
+ this.SUBRULE(this.parameterList);
1935
+ this.CONSUME(RBracket);
1936
+ this.MANY2(() => {
1937
+ this.OR2([{ ALT: () => this.CONSUME2(Space) }, { ALT: () => this.CONSUME2(Newline) }]);
1938
+ });
1939
+ this.CONSUME(LBrace);
1940
+ this.MANY3(() => {
1941
+ this.OR3([{ ALT: () => this.CONSUME3(Space) }, { ALT: () => this.CONSUME3(Newline) }]);
1942
+ });
1943
+ this.MANY4(() => {
1944
+ this.OR4([
1945
+ { ALT: () => this.SUBRULE(this.inlineElement) },
1946
+ { ALT: () => this.CONSUME4(Newline) }
1947
+ ]);
1948
+ });
1949
+ this.CONSUME(RBrace);
1950
+ this.MANY5(() => {
1951
+ this.OR5([{ ALT: () => this.CONSUME5(Space) }, { ALT: () => this.CONSUME5(Newline) }]);
1952
+ });
1953
+ });
1954
+ this.chartGroupLabel = this.RULE("chartGroupLabel", () => {
1955
+ this.CONSUME(LABEL);
1956
+ this.MANY(() => {
1957
+ this.OR([{ ALT: () => this.CONSUME(Space) }, { ALT: () => this.CONSUME(Newline) }]);
1958
+ });
1959
+ this.CONSUME(LBrace);
1960
+ this.MANY2(() => {
1961
+ this.OR2([{ ALT: () => this.CONSUME2(Space) }, { ALT: () => this.CONSUME2(Newline) }]);
1962
+ });
1963
+ this.MANY3(() => {
1964
+ this.OR3([
1965
+ { ALT: () => this.SUBRULE(this.inlineElement) },
1966
+ { ALT: () => this.CONSUME3(Newline) }
1967
+ ]);
1968
+ });
1969
+ this.CONSUME(RBrace);
1970
+ this.MANY4(() => {
1971
+ this.OR4([{ ALT: () => this.CONSUME4(Space) }, { ALT: () => this.CONSUME4(Newline) }]);
1972
+ });
1973
+ });
1974
+ this.chartSeriesBarItem = this.RULE("chartSeriesBarItem", () => {
1975
+ this.MANY(() => {
1976
+ this.OR([{ ALT: () => this.CONSUME(Space) }, { ALT: () => this.CONSUME(Newline) }]);
1977
+ });
1978
+ this.CONSUME(SERIES_BAR);
1979
+ this.CONSUME(LBracket);
1980
+ this.SUBRULE(this.parameterList);
1981
+ this.CONSUME(RBracket);
1982
+ this.MANY2(() => {
1983
+ this.OR2([{ ALT: () => this.CONSUME2(Space) }, { ALT: () => this.CONSUME2(Newline) }]);
1984
+ });
1985
+ });
1986
+ this.chartGroupItem = this.RULE("chartGroupItem", () => {
1987
+ this.MANY(() => {
1988
+ this.OR([{ ALT: () => this.CONSUME(Space) }, { ALT: () => this.CONSUME(Newline) }]);
1989
+ });
1990
+ this.CONSUME(GROUP);
1991
+ this.CONSUME(LBracket);
1992
+ this.SUBRULE(this.parameterList);
1993
+ this.CONSUME(RBracket);
1994
+ this.MANY2(() => {
1995
+ this.OR2([{ ALT: () => this.CONSUME2(Space) }, { ALT: () => this.CONSUME2(Newline) }]);
1996
+ });
1997
+ this.CONSUME(LBrace);
1998
+ this.MANY3(() => {
1999
+ this.OR3([{ ALT: () => this.CONSUME3(Space) }, { ALT: () => this.CONSUME3(Newline) }]);
2000
+ });
2001
+ this.MANY4({
2002
+ GATE: () => this.LA(1).tokenType === LABEL,
2003
+ DEF: () => this.SUBRULE(this.chartGroupLabel)
2004
+ });
2005
+ this.MANY5({
2006
+ GATE: () => this.LA(1).tokenType === SERIES_BAR,
2007
+ DEF: () => this.SUBRULE(this.chartSeriesBarItem)
2008
+ });
2009
+ this.CONSUME(RBrace);
2010
+ this.MANY6(() => {
2011
+ this.OR4([{ ALT: () => this.CONSUME4(Space) }, { ALT: () => this.CONSUME4(Newline) }]);
2012
+ });
2013
+ });
1910
2014
  this.performSelfAnalysis();
1911
2015
  }
1912
2016
  /** Distinguish `{[number, number], ...}` and the valid empty form `{}` from string lists. */
@@ -3066,7 +3170,7 @@ function isSetSquareTool(value) {
3066
3170
  function isToolNode(value) {
3067
3171
  return ToolNodeSchema.safeParse(value).success;
3068
3172
  }
3069
- const CHART_TYPES = ["bar", "pictograph"];
3173
+ const CHART_TYPES = ["bar", "pictograph", "grouped-bar"];
3070
3174
  const CHART_AXIS_DEFAULTS = { min: 0, max: 100, step: 10, visible: true };
3071
3175
  const CHART_MAX_AXIS_TICKS = 200;
3072
3176
  const CHART_MAX_PICTO_SLOTS = 60;
@@ -3146,6 +3250,63 @@ function createChartBar(id, opts) {
3146
3250
  function isChartBar(value) {
3147
3251
  return ChartBarSchema.safeParse(value).success;
3148
3252
  }
3253
+ const ChartSeriesSchema = zod.z.object({
3254
+ type: zod.z.literal(AST_TYPES.CHART_SERIES),
3255
+ id: zod.z.string().min(1),
3256
+ color: zod.z.string().min(1),
3257
+ inlines: zod.z.array(DisplayInlineNodeSchema).default([])
3258
+ });
3259
+ function createChartSeries(id, opts) {
3260
+ return ChartSeriesSchema.parse({
3261
+ type: AST_TYPES.CHART_SERIES,
3262
+ id,
3263
+ color: opts.color,
3264
+ inlines: opts.inlines ?? []
3265
+ });
3266
+ }
3267
+ function isChartSeries(value) {
3268
+ return ChartSeriesSchema.safeParse(value).success;
3269
+ }
3270
+ const ChartSeriesBarSchema = zod.z.object({
3271
+ type: zod.z.literal(AST_TYPES.CHART_SERIES_BAR),
3272
+ id: zod.z.string().min(1),
3273
+ seriesId: zod.z.string().min(1),
3274
+ value: zod.z.number().default(0),
3275
+ answer: zod.z.number().optional()
3276
+ });
3277
+ function createChartSeriesBar(id, opts) {
3278
+ return ChartSeriesBarSchema.parse({
3279
+ type: AST_TYPES.CHART_SERIES_BAR,
3280
+ id,
3281
+ seriesId: opts.seriesId,
3282
+ value: opts.value ?? 0,
3283
+ answer: opts.answer
3284
+ });
3285
+ }
3286
+ function isChartSeriesBar(value) {
3287
+ return ChartSeriesBarSchema.safeParse(value).success;
3288
+ }
3289
+ const ChartGroupSchema = zod.z.object({
3290
+ type: zod.z.literal(AST_TYPES.CHART_GROUP),
3291
+ id: zod.z.string().min(1),
3292
+ labelInlines: zod.z.array(InlineNodeSchema).default([]),
3293
+ bars: zod.z.array(ChartSeriesBarSchema).default([])
3294
+ });
3295
+ function createChartGroup(id, opts = {}) {
3296
+ return ChartGroupSchema.parse({
3297
+ type: AST_TYPES.CHART_GROUP,
3298
+ id,
3299
+ labelInlines: opts.labelInlines ?? [],
3300
+ bars: opts.bars ?? []
3301
+ });
3302
+ }
3303
+ function isChartGroup(value) {
3304
+ return ChartGroupSchema.safeParse(value).success;
3305
+ }
3306
+ function alignGroupBarsToSeries(group, series) {
3307
+ const bySeriesId = new Map(group.bars.map((bar) => [bar.seriesId, bar]));
3308
+ return series.map((s) => bySeriesId.get(s.id));
3309
+ }
3149
3310
  const ChartBlockSchema = zod.z.object({
3150
3311
  type: zod.z.literal(AST_TYPES.CHART_BLOCK),
3151
3312
  name: zod.z.string().optional(),
@@ -3157,7 +3318,10 @@ const ChartBlockSchema = zod.z.object({
3157
3318
  xAxis: ChartAxisSchema.optional(),
3158
3319
  yAxis: ChartAxisSchema.optional(),
3159
3320
  bars: zod.z.array(ChartBarSchema).default([]),
3160
- pictos: zod.z.array(ChartPictoSchema).default([])
3321
+ pictos: zod.z.array(ChartPictoSchema).default([]),
3322
+ legend: zod.z.boolean().default(false),
3323
+ series: zod.z.array(ChartSeriesSchema).default([]),
3324
+ groups: zod.z.array(ChartGroupSchema).default([])
3161
3325
  }).superRefine((block, ctx) => {
3162
3326
  validateChartBlock(block, ctx);
3163
3327
  });
@@ -3182,10 +3346,10 @@ function validateChartBlock(block, ctx) {
3182
3346
  const { valueAxis } = resolveChartAxes(block);
3183
3347
  validateValueAxisRange(valueAxis, ctx);
3184
3348
  if (block.chartType === "bar") {
3185
- if (block.pictos.length > 0) {
3349
+ if (block.pictos.length > 0 || block.series.length > 0 || block.groups.length > 0) {
3186
3350
  ctx.addIssue({
3187
3351
  code: zod.z.ZodIssueCode.custom,
3188
- message: 'CHART[type="bar"]: body must contain BAR items only, not PICTO',
3352
+ message: 'CHART[type="bar"]: body must contain BAR items only, not PICTO, SERIES, or GROUP',
3189
3353
  path: ["pictos"]
3190
3354
  });
3191
3355
  return;
@@ -3198,16 +3362,16 @@ function validateChartBlock(block, ctx) {
3198
3362
  });
3199
3363
  return;
3200
3364
  }
3201
- validateUniqueIds(block.bars, ctx);
3365
+ validateUniqueIds(block.bars, ctx, (i) => ["bars", i]);
3202
3366
  validateChartSnap(block.snap, valueAxis.step, ctx);
3203
- validateBarAnswers(block.bars, valueAxis, block.snap, ctx);
3367
+ validateBarAnswers(block.bars, valueAxis, block.snap, ctx, (i) => ["bars", i]);
3204
3368
  return;
3205
3369
  }
3206
3370
  if (block.chartType === "pictograph") {
3207
- if (block.bars.length > 0) {
3371
+ if (block.bars.length > 0 || block.series.length > 0 || block.groups.length > 0) {
3208
3372
  ctx.addIssue({
3209
3373
  code: zod.z.ZodIssueCode.custom,
3210
- message: 'CHART[type="pictograph"]: body must contain PICTO items only, not BAR',
3374
+ message: 'CHART[type="pictograph"]: body must contain PICTO items only, not BAR, SERIES, or GROUP',
3211
3375
  path: ["bars"]
3212
3376
  });
3213
3377
  return;
@@ -3228,7 +3392,7 @@ function validateChartBlock(block, ctx) {
3228
3392
  });
3229
3393
  return;
3230
3394
  }
3231
- validateUniqueIds(block.pictos, ctx);
3395
+ validateUniqueIds(block.pictos, ctx, (i) => ["pictos", i]);
3232
3396
  if (!axisHasNumericParams(valueAxis.axis)) {
3233
3397
  ctx.addIssue({
3234
3398
  code: zod.z.ZodIssueCode.custom,
@@ -3249,6 +3413,63 @@ function validateChartBlock(block, ctx) {
3249
3413
  validatePictoAnswers(block.pictos, valueAxis, ctx);
3250
3414
  return;
3251
3415
  }
3416
+ if (block.chartType === "grouped-bar") {
3417
+ if (block.bars.length > 0 || block.pictos.length > 0) {
3418
+ ctx.addIssue({
3419
+ code: zod.z.ZodIssueCode.custom,
3420
+ message: 'CHART[type="grouped-bar"]: body must contain SERIES and GROUP items only, not BAR or PICTO',
3421
+ path: ["bars"]
3422
+ });
3423
+ return;
3424
+ }
3425
+ if (block.series.length === 0) {
3426
+ ctx.addIssue({
3427
+ code: zod.z.ZodIssueCode.custom,
3428
+ message: 'CHART[type="grouped-bar"]: at least one SERIES is required',
3429
+ path: ["series"]
3430
+ });
3431
+ return;
3432
+ }
3433
+ if (block.groups.length === 0) {
3434
+ ctx.addIssue({
3435
+ code: zod.z.ZodIssueCode.custom,
3436
+ message: 'CHART[type="grouped-bar"]: at least one GROUP is required',
3437
+ path: ["groups"]
3438
+ });
3439
+ return;
3440
+ }
3441
+ validateUniqueIds(block.series, ctx, (i) => ["series", i]);
3442
+ validateUniqueIds(block.groups, ctx, (i) => ["groups", i]);
3443
+ const allBars = block.groups.flatMap((g, gi) => g.bars.map((bar, bi) => ({ ...bar, groupIndex: gi, barIndex: bi })));
3444
+ const pathForFlatBar = (i) => ["groups", allBars[i].groupIndex, "bars", allBars[i].barIndex];
3445
+ validateUniqueIds(allBars, ctx, pathForFlatBar);
3446
+ const seriesIds = new Set(block.series.map((s) => s.id));
3447
+ block.groups.forEach((group, gi) => {
3448
+ const seenInGroup = /* @__PURE__ */ new Set();
3449
+ group.bars.forEach((bar, bi) => {
3450
+ if (!seriesIds.has(bar.seriesId)) {
3451
+ ctx.addIssue({
3452
+ code: zod.z.ZodIssueCode.custom,
3453
+ message: `CHART: GROUP "${group.id}" SERIES_BAR "${bar.id}" references unknown series "${bar.seriesId}" — no SERIES with that id is declared`,
3454
+ path: ["groups", gi, "bars", bi, "seriesId"]
3455
+ });
3456
+ return;
3457
+ }
3458
+ if (seenInGroup.has(bar.seriesId)) {
3459
+ ctx.addIssue({
3460
+ code: zod.z.ZodIssueCode.custom,
3461
+ message: `CHART: GROUP "${group.id}" references series "${bar.seriesId}" twice — each SERIES can appear at most once per GROUP`,
3462
+ path: ["groups", gi, "bars", bi, "seriesId"]
3463
+ });
3464
+ return;
3465
+ }
3466
+ seenInGroup.add(bar.seriesId);
3467
+ });
3468
+ });
3469
+ validateChartSnap(block.snap, valueAxis.step, ctx);
3470
+ validateBarAnswers(allBars, valueAxis, block.snap, ctx, pathForFlatBar);
3471
+ return;
3472
+ }
3252
3473
  ctx.addIssue({
3253
3474
  code: zod.z.ZodIssueCode.custom,
3254
3475
  message: `CHART: unsupported chartType "${block.chartType}" — add a validation branch to validateChartBlock`,
@@ -3269,14 +3490,14 @@ function resolveNumericAxis(axis) {
3269
3490
  function hasAmbiguousValueAxes(block) {
3270
3491
  return axisHasNumericParams(block.xAxis) && axisHasNumericParams(block.yAxis);
3271
3492
  }
3272
- function validateUniqueIds(items, ctx) {
3493
+ function validateUniqueIds(items, ctx, pathFor) {
3273
3494
  const seen = /* @__PURE__ */ new Set();
3274
3495
  items.forEach((item, index) => {
3275
3496
  if (seen.has(item.id)) {
3276
3497
  ctx.addIssue({
3277
3498
  code: zod.z.ZodIssueCode.custom,
3278
3499
  message: `CHART: duplicate id "${item.id}" — ids must be unique within the chart`,
3279
- path: [index, "id"]
3500
+ path: [...pathFor(index), "id"]
3280
3501
  });
3281
3502
  return;
3282
3503
  }
@@ -3318,14 +3539,14 @@ function validateChartSnap(snap, step, ctx) {
3318
3539
  });
3319
3540
  }
3320
3541
  }
3321
- function validateBarAnswers(bars, valueAxis, snap, ctx) {
3542
+ function validateBarAnswers(bars, valueAxis, snap, ctx, pathFor) {
3322
3543
  const gridStep = snap ?? valueAxis.step;
3323
3544
  bars.forEach((bar, index) => {
3324
3545
  if (bar.value < valueAxis.min || bar.value > valueAxis.max) {
3325
3546
  ctx.addIssue({
3326
3547
  code: zod.z.ZodIssueCode.custom,
3327
3548
  message: `CHART: BAR "${bar.id}" value (${bar.value}) is outside axis range [${valueAxis.min}, ${valueAxis.max}] — the bar will not render correctly`,
3328
- path: ["bars", index, "value"]
3549
+ path: [...pathFor(index), "value"]
3329
3550
  });
3330
3551
  }
3331
3552
  if (bar.answer === void 0) return;
@@ -3333,7 +3554,7 @@ function validateBarAnswers(bars, valueAxis, snap, ctx) {
3333
3554
  ctx.addIssue({
3334
3555
  code: zod.z.ZodIssueCode.custom,
3335
3556
  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: ["bars", index, "answer"]
3557
+ path: [...pathFor(index), "answer"]
3337
3558
  });
3338
3559
  return;
3339
3560
  }
@@ -3342,7 +3563,7 @@ function validateBarAnswers(bars, valueAxis, snap, ctx) {
3342
3563
  ctx.addIssue({
3343
3564
  code: zod.z.ZodIssueCode.custom,
3344
3565
  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: ["bars", index, "answer"]
3566
+ path: [...pathFor(index), "answer"]
3346
3567
  });
3347
3568
  }
3348
3569
  });
@@ -3404,7 +3625,10 @@ function createChartBlock(chartType, opts = {}) {
3404
3625
  xAxis: opts.xAxis,
3405
3626
  yAxis: opts.yAxis,
3406
3627
  bars: opts.bars ?? [],
3407
- pictos: opts.pictos ?? []
3628
+ pictos: opts.pictos ?? [],
3629
+ series: opts.series ?? [],
3630
+ groups: opts.groups ?? [],
3631
+ legend: opts.legend ?? false
3408
3632
  });
3409
3633
  }
3410
3634
  function isChartBlock(value) {
@@ -3491,7 +3715,9 @@ const ExerciseDocumentSchema = zod.z.object({
3491
3715
  };
3492
3716
  const visitInlines = (inlines, basePath) => {
3493
3717
  inlines.forEach((inline, i) => {
3494
- if (inline.type === AST_TYPES.INLINE_INPUT || inline.type === AST_TYPES.FRACTION || inline.type === AST_TYPES.IMAGE) {
3718
+ if (inline.type === AST_TYPES.INLINE_INPUT || inline.type === AST_TYPES.FRACTION || inline.type === AST_TYPES.IMAGE || inline.type === AST_TYPES.VISUAL_FRACTION) {
3719
+ record(inline.name, [...basePath, i, "name"]);
3720
+ } else if (inline.type === AST_TYPES.CLOCK && inline.mode === "interactive") {
3495
3721
  record(inline.name, [...basePath, i, "name"]);
3496
3722
  }
3497
3723
  });
@@ -3523,6 +3749,28 @@ const ExerciseDocumentSchema = zod.z.object({
3523
3749
  pm.groups.flat().forEach((item, ii) => {
3524
3750
  visitInlines(item.inlines, ["lines", bi, "items", ii, "inlines"]);
3525
3751
  });
3752
+ } else if (block.type === AST_TYPES.CHART_BLOCK) {
3753
+ const chart = block;
3754
+ const base = ["lines", bi];
3755
+ chart.bars.forEach((bar, i) => {
3756
+ if (bar.answer !== void 0) record(bar.id, [...base, "bars", i, "id"]);
3757
+ visitInlines(bar.inlines, [...base, "bars", i, "inlines"]);
3758
+ });
3759
+ chart.pictos.forEach((picto, i) => {
3760
+ if (picto.answer !== void 0) record(picto.id, [...base, "pictos", i, "id"]);
3761
+ visitInlines(picto.inlines, [...base, "pictos", i, "inlines"]);
3762
+ });
3763
+ chart.groups.forEach((group, gi) => {
3764
+ group.bars.forEach((bar, i) => {
3765
+ if (bar.answer !== void 0) record(bar.id, [...base, "groups", gi, "bars", i, "id"]);
3766
+ });
3767
+ visitInlines(group.labelInlines, [...base, "groups", gi, "labelInlines"]);
3768
+ });
3769
+ chart.series.forEach((series, i) => {
3770
+ visitInlines(series.inlines, [...base, "series", i, "inlines"]);
3771
+ });
3772
+ if (chart.xAxis) visitInlines(chart.xAxis.inlines, [...base, "xAxis", "inlines"]);
3773
+ if (chart.yAxis) visitInlines(chart.yAxis.inlines, [...base, "yAxis", "inlines"]);
3526
3774
  } else if (block.type === AST_TYPES.RADIO_OPTIONS) {
3527
3775
  record(block.name, ["lines", bi, "name"]);
3528
3776
  } else if (block.type === AST_TYPES.GEOMETRY_BLOCK && block.name !== void 0) {
@@ -4094,7 +4342,7 @@ class NaturalDSLVisitor extends BaseCstVisitor {
4094
4342
  chartBlock(ctx) {
4095
4343
  const params = this.visit(ctx.parameterList);
4096
4344
  const chartType = params.type;
4097
- if (!chartType) throw new Error('CHART: type is required ("bar" | "pictograph")');
4345
+ if (!chartType) throw new Error(`CHART: type is required (${CHART_TYPES.map((t) => `"${t}"`).join(" | ")})`);
4098
4346
  if (!CHART_TYPES.includes(chartType)) {
4099
4347
  throw new Error(`CHART: type must be one of ${CHART_TYPES.join(", ")} (got "${chartType}")`);
4100
4348
  }
@@ -4113,7 +4361,26 @@ class NaturalDSLVisitor extends BaseCstVisitor {
4113
4361
  const yAxis = ctx.chartYAxis ? this.visit(ctx.chartYAxis) : void 0;
4114
4362
  const bars = ctx.chartBarItem ? ctx.chartBarItem.map((b) => this.visit(b)) : [];
4115
4363
  const pictos = ctx.chartPictoItem ? ctx.chartPictoItem.map((p) => this.visit(p)) : [];
4116
- return createChartBlock(chartType, { bars, pictos, name, title, grid, scale, snap, xAxis, yAxis });
4364
+ const series = ctx.chartSeriesItem ? ctx.chartSeriesItem.map((s) => this.visit(s)) : [];
4365
+ const groups = ctx.chartGroupItem ? ctx.chartGroupItem.map((g) => this.visit(g)) : [];
4366
+ if (params.legend !== void 0 && chartType !== "grouped-bar") {
4367
+ throw new Error(`CHART: "legend" is only supported for type="grouped-bar" (got "${chartType}")`);
4368
+ }
4369
+ const legend = params.legend === true || params.legend === "true";
4370
+ return createChartBlock(chartType, {
4371
+ bars,
4372
+ pictos,
4373
+ series,
4374
+ groups,
4375
+ legend,
4376
+ name,
4377
+ title,
4378
+ grid,
4379
+ scale,
4380
+ snap,
4381
+ xAxis,
4382
+ yAxis
4383
+ });
4117
4384
  }
4118
4385
  /** X_AXIS and Y_AXIS carry identical params — only the produced AST type differs.
4119
4386
  * Chevrotain needs two separate grammar rules, but one visitor body serves both. */
@@ -4152,6 +4419,52 @@ class NaturalDSLVisitor extends BaseCstVisitor {
4152
4419
  inlines
4153
4420
  });
4154
4421
  }
4422
+ chartSeriesItem(ctx) {
4423
+ const params = this.visit(ctx.parameterList);
4424
+ const inlines = ctx.inlineElement ? ctx.inlineElement.map((e) => this.visit(e)) : [];
4425
+ if (inlines.some((n) => !DisplayInlineNodeSchema.safeParse(n).success)) {
4426
+ throw new Error(
4427
+ `CHART: SERIES "${params.id}" label must be display-only — inputs, drag options, clocks and interactive fractions are not allowed in a legend label`
4428
+ );
4429
+ }
4430
+ if (!params.color) {
4431
+ throw new Error(`CHART: SERIES "${params.id}" requires "color"`);
4432
+ }
4433
+ return createChartSeries(params.id, {
4434
+ color: params.color,
4435
+ inlines
4436
+ });
4437
+ }
4438
+ chartGroupLabel(ctx) {
4439
+ return ctx.inlineElement ? ctx.inlineElement.map((e) => this.visit(e)) : [];
4440
+ }
4441
+ chartGroupItem(ctx) {
4442
+ const params = this.visit(ctx.parameterList);
4443
+ if (ctx.chartGroupLabel && ctx.chartGroupLabel.length > 1) {
4444
+ throw new Error(`CHART: GROUP "${params.id}" has more than one LABEL — only one is allowed`);
4445
+ }
4446
+ const labelInlines = ctx.chartGroupLabel ? this.visit(ctx.chartGroupLabel) : [];
4447
+ const bars = ctx.chartSeriesBarItem ? ctx.chartSeriesBarItem.map((b) => this.visit(b)) : [];
4448
+ return createChartGroup(params.id, { labelInlines, bars });
4449
+ }
4450
+ // `params.series` is the raw DSL attribute name (SERIES_BAR[series="..."])
4451
+ // — unrelated to the AST field's own `seriesId` name.
4452
+ chartSeriesBarItem(ctx) {
4453
+ const params = this.visit(ctx.parameterList);
4454
+ if (!params.series) {
4455
+ throw new Error(`CHART: SERIES_BAR "${params.id}" requires "series"`);
4456
+ }
4457
+ if (params.color !== void 0) {
4458
+ throw new Error(
4459
+ `CHART: SERIES_BAR "${params.id}" cannot set "color" — it comes from its SERIES`
4460
+ );
4461
+ }
4462
+ return createChartSeriesBar(params.id, {
4463
+ seriesId: params.series,
4464
+ value: params.value !== void 0 ? Number(params.value) : void 0,
4465
+ answer: params.answer !== void 0 ? Number(params.answer) : void 0
4466
+ });
4467
+ }
4155
4468
  // ═══════════════════════════════════════════════════════════════════════════
4156
4469
  // Tools
4157
4470
  // ═══════════════════════════════════════════════════════════════════════════
@@ -4606,23 +4919,23 @@ const ChartAxisJSONSchema = zod.z.object({
4606
4919
  });
4607
4920
  }
4608
4921
  });
4609
- const ChartBarJSONSchema = zod.z.discriminatedUnion("interactive", [
4610
- zod.z.object({
4611
- id: zod.z.string(),
4612
- type: zod.z.literal(JSON_TYPES.CHART_BAR),
4613
- interactive: zod.z.literal(false),
4614
- value: zod.z.number(),
4615
- color: zod.z.string().optional(),
4616
- label: zod.z.array(LayoutItemSchema).optional()
4617
- }),
4618
- zod.z.object({
4619
- id: zod.z.string(),
4620
- type: zod.z.literal(JSON_TYPES.CHART_BAR),
4922
+ const ChartBarJSONBaseSchema = zod.z.object({
4923
+ id: zod.z.string(),
4924
+ type: zod.z.literal(JSON_TYPES.CHART_BAR),
4925
+ value: zod.z.number(),
4926
+ answer: zod.z.number().optional(),
4927
+ color: zod.z.string().optional(),
4928
+ label: zod.z.array(LayoutItemSchema).optional()
4929
+ });
4930
+ const ChartBarJSONSchema = zod.z.union([
4931
+ ChartBarJSONBaseSchema.extend({
4621
4932
  interactive: zod.z.literal(true),
4622
- value: zod.z.number(),
4623
- widgetId: zod.z.string(),
4624
- color: zod.z.string().optional(),
4625
- label: zod.z.array(LayoutItemSchema).optional()
4933
+ /** The widgets/answers key, equal to `id` when compiled. */
4934
+ widgetId: zod.z.string()
4935
+ }),
4936
+ ChartBarJSONBaseSchema.extend({
4937
+ interactive: zod.z.literal(false).optional(),
4938
+ widgetId: zod.z.undefined().optional()
4626
4939
  })
4627
4940
  ]);
4628
4941
  const ChartPictoJSONSchema = zod.z.object({
@@ -4636,21 +4949,55 @@ const ChartPictoJSONSchema = zod.z.object({
4636
4949
  ),
4637
4950
  label: zod.z.array(LayoutItemSchema).optional()
4638
4951
  });
4639
- const ChartItemSchema = zod.z.object({
4952
+ const ChartSeriesJSONSchema = zod.z.object({
4953
+ id: zod.z.string(),
4954
+ color: zod.z.string(),
4955
+ label: zod.z.array(LayoutItemSchema).optional()
4956
+ });
4957
+ const ChartSeriesBarJSONSchema = zod.z.object({
4958
+ id: zod.z.string(),
4959
+ type: zod.z.literal(JSON_TYPES.CHART_SERIES_BAR),
4960
+ value: zod.z.number(),
4961
+ answer: zod.z.number().optional(),
4962
+ color: zod.z.string(),
4963
+ seriesId: zod.z.string()
4964
+ });
4965
+ const ChartGroupJSONSchema = zod.z.object({
4966
+ id: zod.z.string(),
4967
+ label: zod.z.array(LayoutItemSchema).optional(),
4968
+ bars: zod.z.array(ChartSeriesBarJSONSchema)
4969
+ });
4970
+ const ChartItemBaseSchema = zod.z.object({
4640
4971
  id: zod.z.string(),
4641
4972
  type: zod.z.literal(JSON_TYPES.CHART),
4642
4973
  name: zod.z.string().optional(),
4643
4974
  title: zod.z.string().optional(),
4644
- chartType: zod.z.enum(CHART_TYPES),
4645
4975
  orientation: zod.z.enum(["vertical", "horizontal"]).default("vertical"),
4646
4976
  grid: zod.z.boolean().default(false),
4647
4977
  scale: zod.z.number().positive().default(1),
4648
4978
  snap: zod.z.number().positive().optional(),
4649
4979
  xAxis: ChartAxisJSONSchema.optional(),
4650
- yAxis: ChartAxisJSONSchema.optional(),
4651
- bars: zod.z.array(ChartBarJSONSchema).default([]),
4652
- pictos: zod.z.array(ChartPictoJSONSchema).default([])
4653
- }).superRefine((item, ctx) => {
4980
+ yAxis: ChartAxisJSONSchema.optional()
4981
+ });
4982
+ const ChartItemBarSchema = ChartItemBaseSchema.extend({
4983
+ chartType: zod.z.literal("bar"),
4984
+ bars: zod.z.array(ChartBarJSONSchema)
4985
+ });
4986
+ const ChartItemPictographSchema = ChartItemBaseSchema.extend({
4987
+ chartType: zod.z.literal("pictograph"),
4988
+ pictos: zod.z.array(ChartPictoJSONSchema)
4989
+ });
4990
+ const ChartItemGroupedBarSchema = ChartItemBaseSchema.extend({
4991
+ chartType: zod.z.literal("grouped-bar"),
4992
+ series: zod.z.array(ChartSeriesJSONSchema),
4993
+ groups: zod.z.array(ChartGroupJSONSchema),
4994
+ legend: zod.z.boolean().default(false)
4995
+ });
4996
+ const ChartItemSchema = zod.z.discriminatedUnion("chartType", [
4997
+ ChartItemBarSchema,
4998
+ ChartItemPictographSchema,
4999
+ ChartItemGroupedBarSchema
5000
+ ]).superRefine((item, ctx) => {
4654
5001
  if (item.chartType === "bar" && item.bars.length === 0) {
4655
5002
  ctx.addIssue({
4656
5003
  code: zod.z.ZodIssueCode.custom,
@@ -4658,18 +5005,63 @@ const ChartItemSchema = zod.z.object({
4658
5005
  path: ["bars"]
4659
5006
  });
4660
5007
  }
4661
- if (item.chartType === "pictograph" && item.pictos.length === 0) {
4662
- ctx.addIssue({
4663
- code: zod.z.ZodIssueCode.custom,
4664
- message: 'CHART[type="pictograph"]: at least one PICTO is required',
4665
- path: ["pictos"]
4666
- });
5008
+ if (item.chartType === "pictograph") {
5009
+ if (item.pictos.length === 0) {
5010
+ ctx.addIssue({
5011
+ code: zod.z.ZodIssueCode.custom,
5012
+ message: 'CHART[type="pictograph"]: at least one PICTO is required',
5013
+ path: ["pictos"]
5014
+ });
5015
+ }
5016
+ if (item.snap !== void 0) {
5017
+ ctx.addIssue({
5018
+ code: zod.z.ZodIssueCode.custom,
5019
+ message: 'CHART[type="pictograph"]: snap is not supported for pictograph charts',
5020
+ path: ["snap"]
5021
+ });
5022
+ }
4667
5023
  }
4668
- if (item.chartType === "pictograph" && item.snap !== void 0) {
4669
- ctx.addIssue({
4670
- code: zod.z.ZodIssueCode.custom,
4671
- message: 'CHART[type="pictograph"]: snap is not supported for pictograph charts',
4672
- path: ["snap"]
5024
+ if (item.chartType === "grouped-bar") {
5025
+ if (item.series.length === 0) {
5026
+ ctx.addIssue({
5027
+ code: zod.z.ZodIssueCode.custom,
5028
+ message: 'CHART[type="grouped-bar"]: at least one SERIES is required',
5029
+ path: ["series"]
5030
+ });
5031
+ }
5032
+ if (item.groups.length === 0) {
5033
+ ctx.addIssue({
5034
+ code: zod.z.ZodIssueCode.custom,
5035
+ message: 'CHART[type="grouped-bar"]: at least one GROUP is required',
5036
+ path: ["groups"]
5037
+ });
5038
+ }
5039
+ validateUniqueIds(item.series, ctx, (i) => ["series", i]);
5040
+ validateUniqueIds(item.groups, ctx, (i) => ["groups", i]);
5041
+ const allBars = item.groups.flatMap((g, gi) => g.bars.map((bar, bi) => ({ id: bar.id, gi, bi })));
5042
+ validateUniqueIds(allBars, ctx, (i) => ["groups", allBars[i].gi, "bars", allBars[i].bi]);
5043
+ const seriesIds = new Set(item.series.map((s) => s.id));
5044
+ item.groups.forEach((group, gi) => {
5045
+ const seenInGroup = /* @__PURE__ */ new Set();
5046
+ group.bars.forEach((bar, bi) => {
5047
+ if (!seriesIds.has(bar.seriesId)) {
5048
+ ctx.addIssue({
5049
+ code: zod.z.ZodIssueCode.custom,
5050
+ message: `CHART[type="grouped-bar"]: GROUP "${group.id}" bar "${bar.id}" references unknown seriesId "${bar.seriesId}" — no SERIES with that id is declared`,
5051
+ path: ["groups", gi, "bars", bi, "seriesId"]
5052
+ });
5053
+ return;
5054
+ }
5055
+ if (seenInGroup.has(bar.seriesId)) {
5056
+ ctx.addIssue({
5057
+ code: zod.z.ZodIssueCode.custom,
5058
+ message: `CHART[type="grouped-bar"]: GROUP "${group.id}" has two bars for seriesId "${bar.seriesId}" — each series can appear at most once per group`,
5059
+ path: ["groups", gi, "bars", bi, "seriesId"]
5060
+ });
5061
+ return;
5062
+ }
5063
+ seenInGroup.add(bar.seriesId);
5064
+ });
4673
5065
  });
4674
5066
  }
4675
5067
  });
@@ -10768,6 +11160,8 @@ function collectAllInlines(blocks) {
10768
11160
  for (const picto of chart.pictos) result.push(...picto.inlines);
10769
11161
  if (chart.xAxis) result.push(...chart.xAxis.inlines);
10770
11162
  if (chart.yAxis) result.push(...chart.yAxis.inlines);
11163
+ for (const series of chart.series) result.push(...series.inlines);
11164
+ for (const group of chart.groups) result.push(...group.labelInlines);
10771
11165
  }
10772
11166
  }
10773
11167
  return result;
@@ -10802,27 +11196,19 @@ function transformChart(block, idGenerator, inlineDragIds) {
10802
11196
  const chartAnswers = {};
10803
11197
  const bars = block.bars.map((bar) => {
10804
11198
  const label = toLabel(bar.inlines);
10805
- if (bar.answer !== void 0) {
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 {
11199
+ const common = {
10819
11200
  id: bar.id,
10820
11201
  type: JSON_TYPES.CHART_BAR,
10821
- interactive: false,
10822
11202
  value: bar.value,
10823
11203
  ...bar.color !== void 0 && { color: bar.color },
10824
11204
  ...label !== void 0 && { label }
10825
11205
  };
11206
+ if (bar.answer !== void 0) {
11207
+ chartWidgets[bar.id] = { type: JSON_TYPES.CHART_BAR, value: bar.value };
11208
+ chartAnswers[bar.id] = bar.answer;
11209
+ return { ...common, interactive: true, widgetId: bar.id, answer: bar.answer };
11210
+ }
11211
+ return { ...common, interactive: false };
10826
11212
  });
10827
11213
  const pictos = block.pictos.map((picto) => {
10828
11214
  const label = toLabel(picto.inlines);
@@ -10839,21 +11225,62 @@ function transformChart(block, idGenerator, inlineDragIds) {
10839
11225
  ...label !== void 0 && { label }
10840
11226
  };
10841
11227
  });
10842
- const item = {
11228
+ const series = block.series.map((s) => {
11229
+ const label = toLabel(s.inlines);
11230
+ return {
11231
+ id: s.id,
11232
+ color: s.color,
11233
+ ...label !== void 0 && { label }
11234
+ };
11235
+ });
11236
+ const groups = block.groups.map((group) => {
11237
+ const slots = alignGroupBarsToSeries(group, block.series);
11238
+ const groupBars = slots.map((bar, i) => {
11239
+ if (bar === void 0) return void 0;
11240
+ const series2 = block.series[i];
11241
+ if (bar.answer !== void 0) {
11242
+ chartWidgets[bar.id] = { type: JSON_TYPES.CHART_BAR, value: bar.value };
11243
+ chartAnswers[bar.id] = bar.answer;
11244
+ }
11245
+ return {
11246
+ id: bar.id,
11247
+ type: JSON_TYPES.CHART_SERIES_BAR,
11248
+ value: bar.value,
11249
+ ...bar.answer !== void 0 && { answer: bar.answer },
11250
+ color: series2.color,
11251
+ seriesId: series2.id
11252
+ };
11253
+ }).filter((bar) => bar !== void 0);
11254
+ const label = toLabel(group.labelInlines);
11255
+ return {
11256
+ id: group.id,
11257
+ ...label !== void 0 && { label },
11258
+ bars: groupBars
11259
+ };
11260
+ });
11261
+ const base = {
10843
11262
  id: idGenerator.generate(JSON_TYPES.CHART),
10844
11263
  type: JSON_TYPES.CHART,
10845
11264
  ...block.name !== void 0 && { name: block.name },
10846
11265
  ...block.title !== void 0 && { title: block.title },
10847
- chartType: block.chartType,
10848
11266
  orientation,
10849
11267
  grid: block.grid,
10850
11268
  scale: block.scale,
10851
11269
  ...block.snap !== void 0 && { snap: block.snap },
10852
11270
  ...xAxis !== void 0 && { xAxis },
10853
- ...yAxis !== void 0 && { yAxis },
10854
- bars,
10855
- pictos
11271
+ ...yAxis !== void 0 && { yAxis }
10856
11272
  };
11273
+ let item;
11274
+ if (block.chartType === "bar") {
11275
+ item = { ...base, chartType: "bar", bars };
11276
+ } else if (block.chartType === "pictograph") {
11277
+ item = { ...base, chartType: "pictograph", pictos };
11278
+ } else if (block.chartType === "grouped-bar") {
11279
+ item = { ...base, chartType: "grouped-bar", series, groups, legend: block.legend };
11280
+ } else {
11281
+ const unhandled = block.chartType;
11282
+ throw new Error(`CHART: unsupported chart type "${String(unhandled)}"`);
11283
+ }
10857
11284
  return { item, chartWidgets, chartAnswers };
10858
11285
  }
10859
11286
  function transformNumberLine(numberLine, idGenerator, inlineDragIds) {
@@ -17518,12 +17945,17 @@ const GridSymmetry = ({
17518
17945
  }
17519
17946
  ) });
17520
17947
  };
17948
+ function interactiveBarKey(bar) {
17949
+ if (bar.answer === void 0 && bar.interactive !== true) return void 0;
17950
+ return bar.widgetId ?? bar.id;
17951
+ }
17521
17952
  function getChartClassName(item) {
17522
17953
  const isHorizontal = item.orientation === "horizontal";
17523
17954
  return [
17524
17955
  "chart",
17525
17956
  isHorizontal ? "chart--horizontal" : "",
17526
17957
  item.chartType === "pictograph" ? "chart--pictograph" : "",
17958
+ item.chartType === "grouped-bar" ? "chart--grouped-bar" : "",
17527
17959
  item.xAxis?.visible === false ? "chart--no-xaxis" : "",
17528
17960
  item.yAxis?.visible === false ? "chart--no-yaxis" : ""
17529
17961
  ].filter(Boolean).join(" ");
@@ -17534,7 +17966,8 @@ function resolveItemValue(id, defaultValue2, values) {
17534
17966
  return isNaN(parsed) ? defaultValue2 : parsed;
17535
17967
  }
17536
17968
  function resolveValue(bar, values) {
17537
- return bar.interactive ? resolveItemValue(bar.widgetId, bar.value, values) : bar.value;
17969
+ const widgetKey = interactiveBarKey(bar);
17970
+ return widgetKey !== void 0 ? resolveItemValue(widgetKey, bar.value, values) : bar.value;
17538
17971
  }
17539
17972
  function clamp(v, min, max) {
17540
17973
  return Math.min(max, Math.max(min, v));
@@ -17593,6 +18026,8 @@ const CHART_METRICS = {
17593
18026
  gapWidth: 1,
17594
18027
  axisWidth: 2.35,
17595
18028
  barWidth: 3.35,
18029
+ // Fixed narrow width so several series fit in one cluster; not derived from count.
18030
+ groupedBarWidth: 1.8,
17596
18031
  fontSize: 0.9,
17597
18032
  labelFontSize: 0.98,
17598
18033
  handleSize: 1.15,
@@ -17645,6 +18080,41 @@ function computePictographChartStyle(scale, columnCount, totalSlots, orientation
17645
18080
  "--chart-height": slotSpan
17646
18081
  };
17647
18082
  }
18083
+ function computeGroupedChartStyle(scale, groupCount, seriesCount, orientation = "vertical", isInteractive = false) {
18084
+ const visualScale = clamp(scale, CHART_METRICS.scaleMin, CHART_METRICS.scaleMax);
18085
+ const barWidth = (isInteractive ? CHART_METRICS.barWidth : CHART_METRICS.groupedBarWidth) * visualScale;
18086
+ const barGap = CHART_METRICS.gapWidth * visualScale * 0.5;
18087
+ const groupGap = CHART_METRICS.gapWidth * visualScale * 2;
18088
+ const seriesPerGroup = Math.max(1, seriesCount);
18089
+ const groupWidth = seriesPerGroup * barWidth + Math.max(0, seriesPerGroup - 1) * barGap;
18090
+ const trackLength = groupCount * groupWidth + Math.max(0, groupCount - 1) * groupGap + groupGap * 2;
18091
+ const cssVars = {
18092
+ "--chart-axis-width": `${CHART_METRICS.axisWidth * visualScale}rem`,
18093
+ "--chart-bar-width": `${barWidth}rem`,
18094
+ // A bar slot inside a group is just the bar; reuse the plain bar-chart var.
18095
+ "--chart-bar-slot-width": `${barWidth}rem`,
18096
+ "--chart-bar-gap": `${barGap}rem`,
18097
+ "--chart-group-gap": `${groupGap}rem`,
18098
+ "--chart-group-width": `${groupWidth}rem`,
18099
+ "--chart-font-size": `${CHART_METRICS.fontSize * visualScale}rem`,
18100
+ "--chart-label-font-size": `${CHART_METRICS.labelFontSize * visualScale}rem`,
18101
+ "--chart-handle-size": `${CHART_METRICS.handleSize * visualScale}rem`
18102
+ };
18103
+ if (orientation === "horizontal") {
18104
+ const valueWidth = CHART_METRICS.chartHeight * visualScale;
18105
+ return {
18106
+ ...cssVars,
18107
+ "--chart-height": `${trackLength}rem`,
18108
+ "--chart-h-value-width": `${valueWidth}rem`,
18109
+ "--chart-content-width": `${valueWidth}rem`
18110
+ };
18111
+ }
18112
+ return {
18113
+ ...cssVars,
18114
+ "--chart-height": `${CHART_METRICS.chartHeight * visualScale}rem`,
18115
+ "--chart-content-width": `${trackLength}rem`
18116
+ };
18117
+ }
17648
18118
  function computeHorizontalChartStyle(scale, barCount) {
17649
18119
  const { trackLength, visualScale, cssVars } = computeBaseChartVars(scale, barCount);
17650
18120
  const valueWidth = CHART_METRICS.chartHeight * visualScale;
@@ -17696,6 +18166,7 @@ const DraggableBar = ({
17696
18166
  const axisMax = axis.max ?? CHART_AXIS_DEFAULTS.max;
17697
18167
  const axisStep = axis.step ?? CHART_AXIS_DEFAULTS.step;
17698
18168
  const snapStep = snap ?? axisStep;
18169
+ const widgetKey = bar.widgetId ?? bar.id;
17699
18170
  const fillFromPointer = React.useCallback(
17700
18171
  (clientY, clientX) => {
17701
18172
  const track = trackRef.current;
@@ -17713,10 +18184,10 @@ const DraggableBar = ({
17713
18184
  if (snapped !== lastSnap.current) {
17714
18185
  lastSnap.current = snapped;
17715
18186
  playSound("bar-step");
17716
- onChange(bar.widgetId, String(snapped));
18187
+ onChange(widgetKey, String(snapped));
17717
18188
  }
17718
18189
  },
17719
- [axisMin, axisMax, snapStep, bar.widgetId, onChange, orientation, playSound]
18190
+ [axisMin, axisMax, snapStep, widgetKey, onChange, orientation, playSound]
17720
18191
  );
17721
18192
  const onPointerDown = React.useCallback(
17722
18193
  (e) => {
@@ -17749,8 +18220,8 @@ const DraggableBar = ({
17749
18220
  if (!dragging.current) return;
17750
18221
  dragging.current = false;
17751
18222
  setIsDragging(false);
17752
- onChange(bar.widgetId, String(preDragValue.current));
17753
- }, [bar.widgetId, onChange]);
18223
+ onChange(widgetKey, String(preDragValue.current));
18224
+ }, [widgetKey, onChange]);
17754
18225
  const onKeyDown = React.useCallback(
17755
18226
  (e) => {
17756
18227
  if (exerciseLocked) return;
@@ -17759,22 +18230,22 @@ const DraggableBar = ({
17759
18230
  if (isInc) {
17760
18231
  e.preventDefault();
17761
18232
  playSound("bar-step");
17762
- onChange(bar.widgetId, String(nextStepValue(currentValue, axis, 1, snap)));
18233
+ onChange(widgetKey, String(nextStepValue(currentValue, axis, 1, snap)));
17763
18234
  } else if (isDec) {
17764
18235
  e.preventDefault();
17765
18236
  playSound("bar-step");
17766
- onChange(bar.widgetId, String(nextStepValue(currentValue, axis, -1, snap)));
18237
+ onChange(widgetKey, String(nextStepValue(currentValue, axis, -1, snap)));
17767
18238
  } else if (e.key === "Home") {
17768
18239
  e.preventDefault();
17769
18240
  playSound("bar-step");
17770
- onChange(bar.widgetId, String(axisMin));
18241
+ onChange(widgetKey, String(axisMin));
17771
18242
  } else if (e.key === "End") {
17772
18243
  e.preventDefault();
17773
18244
  playSound("bar-step");
17774
- onChange(bar.widgetId, String(axisMax));
18245
+ onChange(widgetKey, String(axisMax));
17775
18246
  }
17776
18247
  },
17777
- [axisMin, axisMax, bar.widgetId, currentValue, axis, snap, onChange, playSound, exerciseLocked]
18248
+ [axisMin, axisMax, widgetKey, currentValue, axis, snap, onChange, playSound, exerciseLocked]
17778
18249
  );
17779
18250
  const fill = toFill(currentValue, axis);
17780
18251
  const isAtMin = currentValue <= axisMin;
@@ -17850,7 +18321,7 @@ const StaticBar = ({
17850
18321
  }
17851
18322
  ) });
17852
18323
  };
17853
- const EMPTY_SET$2 = /* @__PURE__ */ new Set();
18324
+ const EMPTY_SET$4 = /* @__PURE__ */ new Set();
17854
18325
  const XAxisLabel = ({
17855
18326
  bar,
17856
18327
  widgets,
@@ -17865,14 +18336,14 @@ const XAxisLabel = ({
17865
18336
  item: subItem,
17866
18337
  widgets,
17867
18338
  values,
17868
- droppedInputs: EMPTY_SET$2,
18339
+ droppedInputs: EMPTY_SET$4,
17869
18340
  validationDetails,
17870
18341
  onChange
17871
18342
  },
17872
18343
  subItem.id
17873
18344
  )) }) });
17874
18345
  };
17875
- const EMPTY_SET$1 = /* @__PURE__ */ new Set();
18346
+ const EMPTY_SET$3 = /* @__PURE__ */ new Set();
17876
18347
  const BarChart = ({
17877
18348
  item,
17878
18349
  widgets,
@@ -17880,6 +18351,7 @@ const BarChart = ({
17880
18351
  validationDetails,
17881
18352
  onChange
17882
18353
  }) => {
18354
+ if (item.chartType !== "bar") return null;
17883
18355
  const { bars, grid } = item;
17884
18356
  const isHorizontal = item.orientation === "horizontal";
17885
18357
  const valueAxis = isHorizontal ? item.xAxis : item.yAxis;
@@ -17893,7 +18365,7 @@ const BarChart = ({
17893
18365
  item: subItem,
17894
18366
  widgets,
17895
18367
  values,
17896
- droppedInputs: EMPTY_SET$1,
18368
+ droppedInputs: EMPTY_SET$3,
17897
18369
  validationDetails,
17898
18370
  onChange
17899
18371
  },
@@ -17903,9 +18375,10 @@ const BarChart = ({
17903
18375
  const fillAxis = valueAxis ?? CHART_AXIS_DEFAULTS;
17904
18376
  const renderBar = (bar, orient = "vertical") => {
17905
18377
  const currentValue = resolveValue(bar, values);
17906
- const vStatus = bar.interactive ? validationDetails?.[bar.widgetId] : void 0;
18378
+ const widgetKey = interactiveBarKey(bar);
18379
+ const vStatus = widgetKey !== void 0 ? validationDetails?.[widgetKey] : void 0;
17907
18380
  const validationStatus = vStatus?.isCorrect === true ? "correct" : vStatus?.isCorrect === false ? "incorrect" : null;
17908
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "chart-bar-wrapper", children: bar.interactive ? /* @__PURE__ */ jsxRuntime.jsx(
18381
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "chart-bar-wrapper", children: widgetKey !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(
17909
18382
  DraggableBar,
17910
18383
  {
17911
18384
  bar,
@@ -17938,7 +18411,7 @@ const BarChart = ({
17938
18411
  item: subItem,
17939
18412
  widgets,
17940
18413
  values,
17941
- droppedInputs: EMPTY_SET$1,
18414
+ droppedInputs: EMPTY_SET$3,
17942
18415
  validationDetails,
17943
18416
  onChange
17944
18417
  },
@@ -18143,7 +18616,7 @@ const PictographGrid = ({
18143
18616
  ))
18144
18617
  ] });
18145
18618
  };
18146
- const EMPTY_SET = /* @__PURE__ */ new Set();
18619
+ const EMPTY_SET$2 = /* @__PURE__ */ new Set();
18147
18620
  const PictographChart = ({
18148
18621
  item,
18149
18622
  widgets,
@@ -18151,7 +18624,8 @@ const PictographChart = ({
18151
18624
  validationDetails,
18152
18625
  onChange
18153
18626
  }) => {
18154
- const { pictos = [], grid } = item;
18627
+ if (item.chartType !== "pictograph") return null;
18628
+ const { pictos, grid } = item;
18155
18629
  const isHorizontal = item.orientation === "horizontal";
18156
18630
  const valueAxis = isHorizontal ? item.xAxis : item.yAxis;
18157
18631
  const categoryAxis = isHorizontal ? item.yAxis : item.xAxis;
@@ -18164,7 +18638,7 @@ const PictographChart = ({
18164
18638
  item: subItem,
18165
18639
  widgets,
18166
18640
  values,
18167
- droppedInputs: EMPTY_SET,
18641
+ droppedInputs: EMPTY_SET$2,
18168
18642
  validationDetails,
18169
18643
  onChange
18170
18644
  },
@@ -18202,7 +18676,7 @@ const PictographChart = ({
18202
18676
  item: subItem,
18203
18677
  widgets,
18204
18678
  values,
18205
- droppedInputs: EMPTY_SET,
18679
+ droppedInputs: EMPTY_SET$2,
18206
18680
  validationDetails,
18207
18681
  onChange
18208
18682
  },
@@ -18223,7 +18697,7 @@ const PictographChart = ({
18223
18697
  item: subItem,
18224
18698
  widgets,
18225
18699
  values,
18226
- droppedInputs: EMPTY_SET,
18700
+ droppedInputs: EMPTY_SET$2,
18227
18701
  validationDetails,
18228
18702
  onChange
18229
18703
  },
@@ -18242,9 +18716,183 @@ const PictographChart = ({
18242
18716
  xAxisTitle
18243
18717
  ] }) }) });
18244
18718
  };
18719
+ const GroupSlot = ({
18720
+ group,
18721
+ series,
18722
+ axis,
18723
+ snap,
18724
+ orientation = "vertical",
18725
+ values,
18726
+ validationDetails,
18727
+ onChange
18728
+ }) => {
18729
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "chart-group", children: series.map((s) => {
18730
+ const bar = group.bars.find((b) => b.seriesId === s.id);
18731
+ if (!bar) {
18732
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "chart-group-bar-gap", "aria-hidden": "true" }, s.id);
18733
+ }
18734
+ const currentValue = resolveValue(bar, values);
18735
+ const vStatus = bar.answer !== void 0 ? validationDetails?.[bar.id] : void 0;
18736
+ const validationStatus = vStatus?.isCorrect === true ? "correct" : vStatus?.isCorrect === false ? "incorrect" : null;
18737
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "chart-bar-wrapper", children: bar.answer !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(
18738
+ DraggableBar,
18739
+ {
18740
+ bar: { ...bar, answer: bar.answer },
18741
+ currentValue,
18742
+ axis,
18743
+ snap,
18744
+ validationStatus,
18745
+ orientation,
18746
+ onChange
18747
+ }
18748
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
18749
+ StaticBar,
18750
+ {
18751
+ bar,
18752
+ currentValue,
18753
+ axis,
18754
+ validationStatus,
18755
+ orientation
18756
+ }
18757
+ ) }, bar.id);
18758
+ }) });
18759
+ };
18760
+ const EMPTY_SET$1 = /* @__PURE__ */ new Set();
18761
+ const ChartLegend = ({
18762
+ series,
18763
+ widgets,
18764
+ values,
18765
+ validationDetails,
18766
+ onChange
18767
+ }) => {
18768
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "chart-legend", children: series.map((s) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "chart-legend-item", children: [
18769
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "chart-legend-swatch", "aria-hidden": "true", style: { backgroundColor: s.color } }),
18770
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "chart-legend-label", children: s.label?.map((subItem) => /* @__PURE__ */ jsxRuntime.jsx(
18771
+ LayoutRenderer,
18772
+ {
18773
+ item: subItem,
18774
+ widgets,
18775
+ values,
18776
+ droppedInputs: EMPTY_SET$1,
18777
+ validationDetails,
18778
+ onChange
18779
+ },
18780
+ subItem.id
18781
+ )) })
18782
+ ] }, s.id)) });
18783
+ };
18784
+ const EMPTY_SET = /* @__PURE__ */ new Set();
18785
+ const GroupedBarChart = ({
18786
+ item,
18787
+ widgets,
18788
+ values,
18789
+ validationDetails,
18790
+ onChange
18791
+ }) => {
18792
+ if (item.chartType !== "grouped-bar") return null;
18793
+ const { groups, series, grid } = item;
18794
+ const isHorizontal = item.orientation === "horizontal";
18795
+ const isInteractive = groups.some((g) => g.bars.some((bar) => bar.answer !== void 0));
18796
+ const valueAxis = isHorizontal ? item.xAxis : item.yAxis;
18797
+ const categoryAxis = isHorizontal ? item.yAxis : item.xAxis;
18798
+ const chartClass = getChartClassName(item);
18799
+ const fillAxis = valueAxis ?? CHART_AXIS_DEFAULTS;
18800
+ const renderAxisTitle = (title) => {
18801
+ if (!title?.length) return null;
18802
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: title.map((subItem) => /* @__PURE__ */ jsxRuntime.jsx(
18803
+ LayoutRenderer,
18804
+ {
18805
+ item: subItem,
18806
+ widgets,
18807
+ values,
18808
+ droppedInputs: EMPTY_SET,
18809
+ validationDetails,
18810
+ onChange
18811
+ },
18812
+ subItem.id
18813
+ )) });
18814
+ };
18815
+ const renderGroupSlots = (orient) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "chart-groups", children: groups.map((group) => /* @__PURE__ */ jsxRuntime.jsx(
18816
+ GroupSlot,
18817
+ {
18818
+ group,
18819
+ series,
18820
+ axis: fillAxis,
18821
+ snap: item.snap,
18822
+ orientation: orient,
18823
+ values,
18824
+ validationDetails,
18825
+ onChange
18826
+ },
18827
+ group.id
18828
+ )) });
18829
+ const legend = item.legend ? /* @__PURE__ */ jsxRuntime.jsx(
18830
+ ChartLegend,
18831
+ {
18832
+ series,
18833
+ widgets,
18834
+ values,
18835
+ validationDetails,
18836
+ onChange
18837
+ }
18838
+ ) : null;
18839
+ if (isHorizontal) {
18840
+ const chartStyle2 = computeGroupedChartStyle(item.scale, groups.length, series.length, "horizontal", isInteractive);
18841
+ 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: [
18842
+ item.title && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "chart-title", children: item.title }),
18843
+ categoryAxis?.visible && categoryAxis.title?.length ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "chart-axis-title chart-axis-title--y-h", children: renderAxisTitle(categoryAxis.title) }) : null,
18844
+ /* @__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(
18845
+ LayoutRenderer,
18846
+ {
18847
+ item: subItem,
18848
+ widgets,
18849
+ values,
18850
+ droppedInputs: EMPTY_SET,
18851
+ validationDetails,
18852
+ onChange
18853
+ },
18854
+ subItem.id
18855
+ )) }, group.id)) }),
18856
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "chart-plot", children: [
18857
+ grid && /* @__PURE__ */ jsxRuntime.jsx(Grid, { axis: fillAxis, orientation: "horizontal" }),
18858
+ renderGroupSlots("horizontal")
18859
+ ] }),
18860
+ valueAxis && valueAxis.visible && /* @__PURE__ */ jsxRuntime.jsx(XAxis, { axis: valueAxis, title: renderAxisTitle(valueAxis.title) }),
18861
+ legend
18862
+ ] }) }) });
18863
+ }
18864
+ const chartStyle = computeGroupedChartStyle(item.scale, groups.length, series.length, "vertical", isInteractive);
18865
+ const hasAnyGroupLabel = groups.some((g) => g.label && g.label.length > 0);
18866
+ 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(
18867
+ LayoutRenderer,
18868
+ {
18869
+ item: subItem,
18870
+ widgets,
18871
+ values,
18872
+ droppedInputs: EMPTY_SET,
18873
+ validationDetails,
18874
+ onChange
18875
+ },
18876
+ subItem.id
18877
+ )) }) }, group.id)) }) : null;
18878
+ 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;
18879
+ 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: [
18880
+ item.title && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "chart-title", children: item.title }),
18881
+ valueAxis?.visible && valueAxis.title?.length ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "chart-axis-title chart-axis-title--y", children: renderAxisTitle(valueAxis.title) }) : null,
18882
+ valueAxis && valueAxis.visible && /* @__PURE__ */ jsxRuntime.jsx(YAxis, { axis: valueAxis }),
18883
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "chart-plot", children: [
18884
+ grid && /* @__PURE__ */ jsxRuntime.jsx(Grid, { axis: fillAxis }),
18885
+ renderGroupSlots("vertical")
18886
+ ] }),
18887
+ groupLabelRow,
18888
+ xAxisTitle,
18889
+ legend
18890
+ ] }) }) });
18891
+ };
18245
18892
  const CHART_TYPE_COMPONENTS = {
18246
18893
  bar: BarChart,
18247
- pictograph: PictographChart
18894
+ pictograph: PictographChart,
18895
+ "grouped-bar": GroupedBarChart
18248
18896
  // line: LineChart,
18249
18897
  // pie: PieChart,
18250
18898
  };
@@ -18927,7 +19575,7 @@ function isGeometryWidget(widget) {
18927
19575
  return widget.type === JSON_TYPES.GEOMETRY;
18928
19576
  }
18929
19577
  const GeometryWidget = React.lazy(async () => ({
18930
- default: (await Promise.resolve().then(() => require("./GeometryWidget-D2QyGzW-.js"))).GeometryWidget
19578
+ default: (await Promise.resolve().then(() => require("./GeometryWidget-DgPfqyQv.js"))).GeometryWidget
18931
19579
  }));
18932
19580
  const WidgetRenderer = ({
18933
19581
  widget,
@@ -21716,10 +22364,16 @@ exports.ChartBarJSONSchema = ChartBarJSONSchema;
21716
22364
  exports.ChartBarSchema = ChartBarSchema;
21717
22365
  exports.ChartBarWidgetJSONSchema = ChartBarWidgetJSONSchema;
21718
22366
  exports.ChartBlockSchema = ChartBlockSchema;
22367
+ exports.ChartGroupJSONSchema = ChartGroupJSONSchema;
22368
+ exports.ChartGroupSchema = ChartGroupSchema;
21719
22369
  exports.ChartItemSchema = ChartItemSchema;
21720
22370
  exports.ChartPictoJSONSchema = ChartPictoJSONSchema;
21721
22371
  exports.ChartPictoSchema = ChartPictoSchema;
21722
22372
  exports.ChartPictoWidgetJSONSchema = ChartPictoWidgetJSONSchema;
22373
+ exports.ChartSeriesBarJSONSchema = ChartSeriesBarJSONSchema;
22374
+ exports.ChartSeriesBarSchema = ChartSeriesBarSchema;
22375
+ exports.ChartSeriesJSONSchema = ChartSeriesJSONSchema;
22376
+ exports.ChartSeriesSchema = ChartSeriesSchema;
21723
22377
  exports.ClockInlineSchema = ClockInlineSchema;
21724
22378
  exports.ClockItemSchema = ClockItemSchema;
21725
22379
  exports.ColorTextInlineSchema = ColorTextInlineSchema;
@@ -21834,6 +22488,7 @@ exports.VopSeparatorSchema = VopSeparatorSchema;
21834
22488
  exports.WidgetJSONSchema = WidgetJSONSchema;
21835
22489
  exports.WidgetRefSchema = WidgetRefSchema;
21836
22490
  exports.WidgetRenderer = WidgetRenderer;
22491
+ exports.alignGroupBarsToSeries = alignGroupBarsToSeries;
21837
22492
  exports.compile = compile;
21838
22493
  exports.compileWithAST = compileWithAST;
21839
22494
  exports.computeGridSymmetryCellsToFill = computeGridSymmetryCellsToFill;
@@ -21842,7 +22497,10 @@ exports.createBoldText = createBoldText;
21842
22497
  exports.createChartAxis = createChartAxis;
21843
22498
  exports.createChartBar = createChartBar;
21844
22499
  exports.createChartBlock = createChartBlock;
22500
+ exports.createChartGroup = createChartGroup;
21845
22501
  exports.createChartPicto = createChartPicto;
22502
+ exports.createChartSeries = createChartSeries;
22503
+ exports.createChartSeriesBar = createChartSeriesBar;
21846
22504
  exports.createColorText = createColorText;
21847
22505
  exports.createDisplayClock = createDisplayClock;
21848
22506
  exports.createDragOptions = createDragOptions;
@@ -21895,9 +22553,12 @@ exports.isChartAxis = isChartAxis;
21895
22553
  exports.isChartBar = isChartBar;
21896
22554
  exports.isChartBarWidgetJSON = isChartBarWidgetJSON;
21897
22555
  exports.isChartBlock = isChartBlock;
22556
+ exports.isChartGroup = isChartGroup;
21898
22557
  exports.isChartItem = isChartItem;
21899
22558
  exports.isChartPicto = isChartPicto;
21900
22559
  exports.isChartPictoWidgetJSON = isChartPictoWidgetJSON;
22560
+ exports.isChartSeries = isChartSeries;
22561
+ exports.isChartSeriesBar = isChartSeriesBar;
21901
22562
  exports.isClock = isClock;
21902
22563
  exports.isClockItem = isClockItem;
21903
22564
  exports.isColorText = isColorText;
@@ -21984,7 +22645,8 @@ exports.validateExpression = validateExpression;
21984
22645
  exports.validateInputFormats = validateInputFormats;
21985
22646
  exports.validateMixed = validateMixed;
21986
22647
  exports.validateSimple = validateSimple;
22648
+ exports.validateUniqueIds = validateUniqueIds;
21987
22649
  exports.validateVisualFractionCapacity = validateVisualFractionCapacity;
21988
22650
  exports.validateWidgetJSON = validateWidgetJSON;
21989
22651
  exports.validateWidgetJSONSafe = validateWidgetJSONSafe;
21990
- //# sourceMappingURL=index-D-QbiBn5.js.map
22652
+ //# sourceMappingURL=index-tEgJE6m2.js.map