@mathscan/math-exercise-engine 1.1.10 → 1.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -3755,635 +3755,6 @@ function isProtractorWidget(widget) {
3755
3755
  function isSetSquareWidget(widget) {
3756
3756
  return widget.type === JSON_TYPES.SET_SQUARE;
3757
3757
  }
3758
- class LayoutIdGenerator {
3759
- constructor() {
3760
- this.counter = 0;
3761
- }
3762
- reset() {
3763
- this.counter = 0;
3764
- }
3765
- generate(type) {
3766
- return `${type}_${++this.counter}`;
3767
- }
3768
- }
3769
- function toJSON(ast) {
3770
- const idGenerator = new LayoutIdGenerator();
3771
- const widgets = {};
3772
- const answers = {};
3773
- const allInlines = collectAllInlines(ast.lines);
3774
- const inlineDragIds = /* @__PURE__ */ new Map();
3775
- {
3776
- let counter = 0;
3777
- for (const inline of allInlines) {
3778
- if (inline.type === AST_TYPES.INLINE_DRAG_OPTION) {
3779
- inlineDragIds.set(inline, `${INLINE_DRAG_OPT_PREFIX}${counter++}`);
3780
- }
3781
- }
3782
- }
3783
- const layout = [];
3784
- if (ast.question) {
3785
- layout.push({
3786
- id: idGenerator.generate(JSON_TYPES.QUESTION_TEXT),
3787
- type: JSON_TYPES.QUESTION_TEXT,
3788
- value: ast.question.value
3789
- });
3790
- }
3791
- for (const block of ast.lines) {
3792
- if (block.type === AST_TYPES.TABLE_BLOCK) {
3793
- layout.push(transformTable(block, idGenerator, inlineDragIds));
3794
- } else if (block.type === AST_TYPES.NUMBER_LINE_BLOCK) {
3795
- const nlItem = transformNumberLine(block, idGenerator, inlineDragIds);
3796
- layout.push(nlItem);
3797
- if (nlItem.counter?.answer !== void 0) {
3798
- answers[nlItem.counter.id] = nlItem.counter.answer;
3799
- }
3800
- for (const cursor of nlItem.cursors ?? []) {
3801
- answers[cursor.id] = cursor.answer;
3802
- }
3803
- } else if (block.type === AST_TYPES.MATCHER_BLOCK) {
3804
- const matcherBlock = block;
3805
- layout.push({
3806
- id: idGenerator.generate(JSON_TYPES.WIDGET_REF),
3807
- type: JSON_TYPES.WIDGET_REF,
3808
- widgetId: matcherBlock.name
3809
- });
3810
- layout.push({
3811
- id: idGenerator.generate(JSON_TYPES.LINEBREAK),
3812
- type: JSON_TYPES.LINEBREAK
3813
- });
3814
- } else if (block.type === AST_TYPES.PAIR_MATCHER_BLOCK) {
3815
- const pmBlock = block;
3816
- layout.push({
3817
- id: idGenerator.generate(JSON_TYPES.WIDGET_REF),
3818
- type: JSON_TYPES.WIDGET_REF,
3819
- widgetId: pmBlock.name
3820
- });
3821
- layout.push({
3822
- id: idGenerator.generate(JSON_TYPES.LINEBREAK),
3823
- type: JSON_TYPES.LINEBREAK
3824
- });
3825
- } else if (block.type === AST_TYPES.VERTICAL_OPERATION_BLOCK) {
3826
- layout.push(transformVerticalOperation(block, idGenerator));
3827
- } else if (block.type === AST_TYPES.RADIO_OPTIONS) {
3828
- const radioBlock = block;
3829
- layout.push({
3830
- id: idGenerator.generate(JSON_TYPES.WIDGET_REF),
3831
- type: JSON_TYPES.WIDGET_REF,
3832
- widgetId: radioBlock.name
3833
- });
3834
- layout.push({
3835
- id: idGenerator.generate(JSON_TYPES.LINEBREAK),
3836
- type: JSON_TYPES.LINEBREAK
3837
- });
3838
- } else {
3839
- const lineItems = transformLine(block, idGenerator, inlineDragIds);
3840
- layout.push(...lineItems);
3841
- }
3842
- }
3843
- for (const inline of allInlines) {
3844
- if (inline.type === AST_TYPES.INLINE_INPUT) {
3845
- if (inline.inputType === INPUT_TYPES.STRING) {
3846
- widgets[inline.name] = {
3847
- type: JSON_TYPES.INLINE_INPUT,
3848
- inputType: INPUT_TYPES.STRING,
3849
- length: inline.length,
3850
- placeholder: inline.placeholder
3851
- };
3852
- } else {
3853
- widgets[inline.name] = {
3854
- type: JSON_TYPES.INLINE_INPUT,
3855
- inputType: INPUT_TYPES.NUMERIC,
3856
- digits: inline.digits,
3857
- placeholder: inline.placeholder
3858
- };
3859
- }
3860
- if (inline.answer !== void 0) {
3861
- answers[inline.name] = inline.answer;
3862
- }
3863
- } else if (inline.type === AST_TYPES.CLOCK && inline.mode === "interactive") {
3864
- answers[inline.name] = inline.answer;
3865
- }
3866
- if (inline.type === AST_TYPES.FRACTION) {
3867
- widgets[inline.name] = {
3868
- type: JSON_TYPES.FRACTION,
3869
- numerator: inline.numerator ?? null,
3870
- denominator: inline.denominator ?? null,
3871
- simplified: inline.simplified ?? true,
3872
- placeholder: inline.placeholder
3873
- };
3874
- if (inline.answer !== void 0) {
3875
- answers[inline.name] = inline.answer;
3876
- }
3877
- }
3878
- if (inline.type === AST_TYPES.INLINE_DRAG_OPTION) {
3879
- const widgetId = inlineDragIds.get(inline);
3880
- widgets[widgetId] = {
3881
- type: JSON_TYPES.INLINE_DRAG_OPTION,
3882
- value: inline.value
3883
- };
3884
- }
3885
- if (inline.type === AST_TYPES.VISUAL_FRACTION) {
3886
- const answerArr = inline.answer ?? [];
3887
- widgets[inline.name] = {
3888
- type: JSON_TYPES.VISUAL_FRACTION,
3889
- shape: inline.shape,
3890
- divisions: inline.divisions,
3891
- filled: inline.filled ?? [],
3892
- answer: answerArr,
3893
- ...inline.orientation !== void 0 && { orientation: inline.orientation },
3894
- ...inline.label !== void 0 && { label: inline.label },
3895
- ...inline.size !== void 0 && { size: inline.size }
3896
- };
3897
- if (answerArr.length > 0) {
3898
- answers[inline.name] = answerArr;
3899
- }
3900
- }
3901
- }
3902
- for (const block of ast.lines) {
3903
- if (block.type !== AST_TYPES.VERTICAL_OPERATION_BLOCK) continue;
3904
- const vop = block;
3905
- for (const item of vop.items) {
3906
- if (item.type !== AST_TYPES.VOP_ROW && item.type !== AST_TYPES.VOP_CARRY_ROW) continue;
3907
- for (const cell of item.cells) {
3908
- if (cell.kind !== "input") continue;
3909
- if (widgets[cell.name]) {
3910
- throw new Error(`VERTICAL_OPERATION: duplicate INPUT name "${cell.name}"`);
3911
- }
3912
- widgets[cell.name] = {
3913
- type: JSON_TYPES.INLINE_INPUT,
3914
- inputType: INPUT_TYPES.NUMERIC,
3915
- digits: cell.digits
3916
- };
3917
- if (cell.answer !== void 0) {
3918
- answers[cell.name] = cell.answer;
3919
- }
3920
- }
3921
- }
3922
- }
3923
- for (const block of ast.lines) {
3924
- if (block.type === AST_TYPES.RADIO_OPTIONS) {
3925
- const radioBlock = block;
3926
- widgets[radioBlock.name] = {
3927
- type: JSON_TYPES.RADIO_OPTIONS,
3928
- options: radioBlock.options
3929
- };
3930
- answers[radioBlock.name] = radioBlock.answer;
3931
- } else if (block.type === AST_TYPES.MATCHER_BLOCK) {
3932
- const matcherBlock = block;
3933
- widgets[matcherBlock.name] = buildMatcherWidget(matcherBlock, idGenerator, inlineDragIds);
3934
- answers[matcherBlock.name] = matcherBlock.answers;
3935
- } else if (block.type === AST_TYPES.PAIR_MATCHER_BLOCK) {
3936
- const pmBlock = block;
3937
- widgets[pmBlock.name] = buildPairMatcherWidget(pmBlock, idGenerator, inlineDragIds);
3938
- answers[pmBlock.name] = pmBlock.answers;
3939
- }
3940
- }
3941
- if (ast.tools) {
3942
- for (const tool of ast.tools) {
3943
- if (tool.type === AST_TYPES.DRAG_OPTIONS) {
3944
- widgets["_drag_options"] = {
3945
- type: JSON_TYPES.DRAG_OPTIONS,
3946
- options: tool.options
3947
- };
3948
- } else if (tool.type === AST_TYPES.NUMERIC_KEYPAD) {
3949
- widgets["_numeric_keypad"] = {
3950
- type: JSON_TYPES.NUMERIC_KEYPAD,
3951
- includeOperators: tool.includeOperators,
3952
- includeDecimal: tool.includeDecimal,
3953
- style: tool.style
3954
- };
3955
- } else if (tool.type === AST_TYPES.GEOMETRY_RULER) {
3956
- widgets[RULER_WIDGET_ID] = {
3957
- type: JSON_TYPES.RULER,
3958
- size: tool.size
3959
- };
3960
- } else if (tool.type === AST_TYPES.GEOMETRY_PROTRACTOR) {
3961
- widgets[PROTRACTOR_WIDGET_ID] = {
3962
- type: JSON_TYPES.PROTRACTOR,
3963
- size: tool.size
3964
- };
3965
- } else if (tool.type === AST_TYPES.GEOMETRY_SET_SQUARE) {
3966
- widgets[SET_SQUARE_WIDGET_ID] = {
3967
- type: JSON_TYPES.SET_SQUARE,
3968
- size: tool.size
3969
- };
3970
- }
3971
- }
3972
- }
3973
- return {
3974
- version: "2.0",
3975
- // Natural DSL version
3976
- layout,
3977
- widgets,
3978
- ...ast.validation ? { validation: transformValidation(ast.validation, answers, widgets) } : {}
3979
- };
3980
- }
3981
- function collectAllInlines(blocks) {
3982
- const result = [];
3983
- for (const block of blocks) {
3984
- if (block.type === AST_TYPES.LINE_BLOCK) {
3985
- result.push(...block.inlines);
3986
- } else if (block.type === AST_TYPES.TABLE_BLOCK) {
3987
- for (const row of block.rows) {
3988
- for (const cell of row.cells) {
3989
- result.push(...cell.inlines);
3990
- }
3991
- }
3992
- } else if (block.type === AST_TYPES.NUMBER_LINE_BLOCK) {
3993
- const numberLine = block;
3994
- for (const position of numberLine.positions) {
3995
- result.push(...position.inlines);
3996
- }
3997
- for (const arrow of numberLine.arrows ?? []) {
3998
- result.push(...arrow.inlines);
3999
- }
4000
- } else if (block.type === AST_TYPES.MATCHER_BLOCK) {
4001
- for (const row of block.rows) {
4002
- for (const item of row.items) {
4003
- result.push(...item.inlines);
4004
- }
4005
- }
4006
- } else if (block.type === AST_TYPES.PAIR_MATCHER_BLOCK) {
4007
- const pm = block;
4008
- for (const item of [...pm.topItems, ...pm.bottomItems]) {
4009
- result.push(...item.inlines);
4010
- }
4011
- }
4012
- }
4013
- return result;
4014
- }
4015
- function transformNumberLine(numberLine, idGenerator, inlineDragIds) {
4016
- const positions = numberLine.positions.map((pos) => {
4017
- const content = pos.inlines.map((inline) => transformInline(inline, idGenerator, inlineDragIds));
4018
- return {
4019
- id: idGenerator.generate(JSON_TYPES.NUMBER_LINE_POSITION),
4020
- type: JSON_TYPES.NUMBER_LINE_POSITION,
4021
- value: pos.value,
4022
- content
4023
- };
4024
- });
4025
- let counterItem;
4026
- if (numberLine.counter !== void 0) {
4027
- const c = numberLine.counter;
4028
- counterItem = {
4029
- id: idGenerator.generate(JSON_TYPES.NUMBER_LINE_COUNTER),
4030
- type: JSON_TYPES.NUMBER_LINE_COUNTER,
4031
- min: c.min,
4032
- max: c.max,
4033
- ...c.default !== void 0 && { default: c.default },
4034
- ...c.name !== void 0 && { name: c.name },
4035
- ...c.answer !== void 0 && { answer: c.answer }
4036
- };
4037
- }
4038
- let highlights;
4039
- if (numberLine.highlights !== void 0 && numberLine.highlights.length > 0) {
4040
- highlights = numberLine.highlights.map((h) => ({
4041
- id: idGenerator.generate(JSON_TYPES.NUMBER_LINE_HIGHLIGHT),
4042
- type: JSON_TYPES.NUMBER_LINE_HIGHLIGHT,
4043
- from: h.from,
4044
- to: h.to,
4045
- ...h.color !== void 0 && { color: h.color },
4046
- ...h.label !== void 0 && { label: h.label }
4047
- }));
4048
- }
4049
- let cursors;
4050
- if (numberLine.cursors !== void 0 && numberLine.cursors.length > 0) {
4051
- cursors = numberLine.cursors.map((c) => ({
4052
- id: idGenerator.generate(JSON_TYPES.NUMBER_LINE_CURSOR),
4053
- type: JSON_TYPES.NUMBER_LINE_CURSOR,
4054
- value: c.value,
4055
- answer: c.answer,
4056
- fillRange: c.fillRange,
4057
- ...c.color !== void 0 && { color: c.color },
4058
- ...c.label !== void 0 && { label: c.label }
4059
- }));
4060
- }
4061
- let arrows;
4062
- if (numberLine.arrows !== void 0 && numberLine.arrows.length > 0) {
4063
- arrows = numberLine.arrows.map((a) => ({
4064
- id: idGenerator.generate(JSON_TYPES.NUMBER_LINE_ARROW),
4065
- type: JSON_TYPES.NUMBER_LINE_ARROW,
4066
- from: a.from,
4067
- to: a.to,
4068
- ...a.color !== void 0 && { color: a.color },
4069
- content: a.inlines.map((inline) => transformInline(inline, idGenerator, inlineDragIds))
4070
- }));
4071
- }
4072
- const item = {
4073
- id: idGenerator.generate(JSON_TYPES.NUMBER_LINE),
4074
- type: JSON_TYPES.NUMBER_LINE,
4075
- min: numberLine.min,
4076
- max: numberLine.max,
4077
- step: numberLine.step,
4078
- ...numberLine.minorTicks !== void 0 && { minorTicks: numberLine.minorTicks },
4079
- ...numberLine.style !== void 0 && { style: numberLine.style },
4080
- ...numberLine.labelVisibility !== void 0 && { labelVisibility: numberLine.labelVisibility },
4081
- ...counterItem !== void 0 && { counter: counterItem },
4082
- ...highlights !== void 0 && { highlights },
4083
- ...cursors !== void 0 && { cursors },
4084
- ...arrows !== void 0 && { arrows },
4085
- ...numberLine.dragMode !== void 0 && { dragMode: numberLine.dragMode },
4086
- positions
4087
- };
4088
- return item;
4089
- }
4090
- function mapMatcherItemList(items, idGenerator, inlineDragIds) {
4091
- return items.map((item) => ({
4092
- id: idGenerator.generate(JSON_TYPES.MATCHER_ITEM),
4093
- type: JSON_TYPES.MATCHER_ITEM,
4094
- itemId: item.itemId,
4095
- content: item.inlines.map((inline) => transformInline(inline, idGenerator, inlineDragIds))
4096
- }));
4097
- }
4098
- function buildMatcherWidget(matcher, idGenerator, inlineDragIds) {
4099
- const rows = matcher.rows.map((row) => ({
4100
- id: idGenerator.generate(JSON_TYPES.MATCHER_ROW),
4101
- type: JSON_TYPES.MATCHER_ROW,
4102
- items: mapMatcherItemList(row.items, idGenerator, inlineDragIds)
4103
- }));
4104
- const result = {
4105
- type: JSON_TYPES.MATCHER,
4106
- rows,
4107
- groupCount: matcher.answers.length
4108
- };
4109
- if (matcher.colors !== void 0) {
4110
- result.colors = matcher.colors;
4111
- }
4112
- return result;
4113
- }
4114
- function buildPairMatcherWidget(pm, idGenerator, inlineDragIds) {
4115
- return {
4116
- type: JSON_TYPES.PAIR_MATCHER,
4117
- topItems: mapMatcherItemList(pm.topItems, idGenerator, inlineDragIds),
4118
- bottomItems: mapMatcherItemList(pm.bottomItems, idGenerator, inlineDragIds)
4119
- };
4120
- }
4121
- function transformVerticalOperation(vop, idGenerator) {
4122
- const anyRowHasOp = vop.items.some(
4123
- (item) => item.type === AST_TYPES.VOP_ROW && item.op !== void 0
4124
- );
4125
- if (!anyRowHasOp && vop.op === void 0) {
4126
- throw new Error("VERTICAL_OPERATION: at least one op is required (set op on the block or on individual ROWs)");
4127
- }
4128
- let cols = 1;
4129
- for (const item of vop.items) {
4130
- if (item.type === AST_TYPES.VOP_ROW || item.type === AST_TYPES.VOP_CARRY_ROW) {
4131
- cols = Math.max(cols, item.cells.length);
4132
- }
4133
- }
4134
- function buildCells(row) {
4135
- return row.cells.map((cell) => {
4136
- const id = idGenerator.generate(JSON_TYPES.VOP_CELL);
4137
- if (cell.kind === "digit") return { id, type: JSON_TYPES.VOP_CELL, kind: "digit", value: cell.value };
4138
- if (cell.kind === "label") return { id, type: JSON_TYPES.VOP_CELL, kind: "label", value: cell.value };
4139
- if (cell.kind === "color") return { id, type: JSON_TYPES.VOP_CELL, kind: "color", value: cell.value, color: cell.color };
4140
- if (cell.kind === "bold") return { id, type: JSON_TYPES.VOP_CELL, kind: "bold", value: cell.value };
4141
- if (cell.kind === "highlight") return { id, type: JSON_TYPES.VOP_CELL, kind: "highlight", value: cell.value };
4142
- if (cell.kind === "input") return { id, type: JSON_TYPES.VOP_CELL, kind: "input", widgetId: cell.name };
4143
- return { id, type: JSON_TYPES.VOP_CELL, kind: "empty" };
4144
- });
4145
- }
4146
- const items = vop.items.map((item) => {
4147
- if (item.type === AST_TYPES.VOP_SEPARATOR) {
4148
- return {
4149
- id: idGenerator.generate(JSON_TYPES.VOP_SEPARATOR),
4150
- type: JSON_TYPES.VOP_SEPARATOR
4151
- };
4152
- }
4153
- if (item.type === AST_TYPES.VOP_CARRY_ROW) {
4154
- return {
4155
- id: idGenerator.generate(JSON_TYPES.VOP_CARRY_ROW),
4156
- type: JSON_TYPES.VOP_CARRY_ROW,
4157
- cells: buildCells(item)
4158
- };
4159
- }
4160
- const row = item;
4161
- return {
4162
- id: idGenerator.generate(JSON_TYPES.VOP_ROW),
4163
- type: JSON_TYPES.VOP_ROW,
4164
- cells: buildCells(row),
4165
- ...row.op !== void 0 && { op: row.op }
4166
- };
4167
- });
4168
- return {
4169
- id: idGenerator.generate(JSON_TYPES.VERTICAL_OPERATION),
4170
- type: JSON_TYPES.VERTICAL_OPERATION,
4171
- op: vop.op,
4172
- cols,
4173
- items
4174
- };
4175
- }
4176
- function transformTable(table, idGenerator, inlineDragIds) {
4177
- const rows = table.rows.map((row) => {
4178
- const cells = row.cells.map((cell) => {
4179
- const items = cell.inlines.map((inline) => transformInline(inline, idGenerator, inlineDragIds));
4180
- const cellItem = {
4181
- id: idGenerator.generate(JSON_TYPES.TABLE_CELL),
4182
- type: JSON_TYPES.TABLE_CELL,
4183
- items
4184
- };
4185
- if (cell.colspan !== void 0) {
4186
- cellItem.colspan = cell.colspan;
4187
- }
4188
- return cellItem;
4189
- });
4190
- return {
4191
- id: idGenerator.generate(JSON_TYPES.TABLE_ROW),
4192
- type: JSON_TYPES.TABLE_ROW,
4193
- cells
4194
- };
4195
- });
4196
- return {
4197
- id: idGenerator.generate(JSON_TYPES.TABLE),
4198
- type: JSON_TYPES.TABLE,
4199
- cols: table.cols,
4200
- rows
4201
- };
4202
- }
4203
- function transformLine(line, idGenerator, inlineDragIds) {
4204
- const items = [];
4205
- for (const inline of line.inlines) {
4206
- items.push(transformInline(inline, idGenerator, inlineDragIds));
4207
- }
4208
- items.push({
4209
- id: idGenerator.generate(JSON_TYPES.LINEBREAK),
4210
- type: JSON_TYPES.LINEBREAK
4211
- });
4212
- return items;
4213
- }
4214
- function transformInline(inline, idGenerator, inlineDragIds) {
4215
- switch (inline.type) {
4216
- case AST_TYPES.FREE_TEXT: {
4217
- const trimmed = inline.value.trim();
4218
- const operators = ["+", "-", "×", "÷", "=", "<", ">", "?"];
4219
- if (operators.includes(trimmed) && inline.value.trim() === inline.value) {
4220
- return {
4221
- id: idGenerator.generate("operator"),
4222
- type: "operator",
4223
- operator: trimmed
4224
- };
4225
- }
4226
- return {
4227
- id: idGenerator.generate(JSON_TYPES.TEXT),
4228
- type: JSON_TYPES.TEXT,
4229
- value: inline.value
4230
- };
4231
- }
4232
- case AST_TYPES.SPACE:
4233
- return {
4234
- id: idGenerator.generate(JSON_TYPES.SPACE),
4235
- type: JSON_TYPES.SPACE
4236
- };
4237
- case AST_TYPES.HIGHLIGHTED_TEXT:
4238
- return {
4239
- id: idGenerator.generate(JSON_TYPES.HIGHLIGHTED_TEXT),
4240
- type: JSON_TYPES.HIGHLIGHTED_TEXT,
4241
- value: inline.value,
4242
- highlight: inline.highlight
4243
- };
4244
- case AST_TYPES.COLOR_TEXT:
4245
- return {
4246
- id: idGenerator.generate(JSON_TYPES.COLOR_TEXT),
4247
- type: JSON_TYPES.COLOR_TEXT,
4248
- value: inline.value,
4249
- color: inline.color,
4250
- highlight: inline.highlight
4251
- };
4252
- case AST_TYPES.BOLD_TEXT:
4253
- return {
4254
- id: idGenerator.generate(JSON_TYPES.BOLD_TEXT),
4255
- type: JSON_TYPES.BOLD_TEXT,
4256
- value: inline.value,
4257
- highlight: inline.highlight
4258
- };
4259
- case AST_TYPES.INLINE_INPUT:
4260
- return {
4261
- id: idGenerator.generate(JSON_TYPES.WIDGET_REF),
4262
- type: JSON_TYPES.WIDGET_REF,
4263
- widgetId: inline.name
4264
- };
4265
- case AST_TYPES.FRACTION:
4266
- return {
4267
- id: idGenerator.generate(JSON_TYPES.WIDGET_REF),
4268
- type: JSON_TYPES.WIDGET_REF,
4269
- widgetId: inline.name
4270
- };
4271
- case AST_TYPES.IMAGE:
4272
- return {
4273
- id: idGenerator.generate(JSON_TYPES.IMAGE),
4274
- type: JSON_TYPES.IMAGE,
4275
- name: inline.name,
4276
- src: inline.src,
4277
- alt: inline.alt,
4278
- ...inline.width !== void 0 && { width: inline.width },
4279
- ...inline.height !== void 0 && { height: inline.height }
4280
- };
4281
- case AST_TYPES.VISUAL_FRACTION:
4282
- return {
4283
- id: idGenerator.generate(JSON_TYPES.WIDGET_REF),
4284
- type: JSON_TYPES.WIDGET_REF,
4285
- widgetId: inline.name
4286
- };
4287
- case AST_TYPES.INLINE_DRAG_OPTION:
4288
- return {
4289
- id: idGenerator.generate(JSON_TYPES.WIDGET_REF),
4290
- type: JSON_TYPES.WIDGET_REF,
4291
- widgetId: inlineDragIds.get(inline)
4292
- };
4293
- case AST_TYPES.CLOCK: {
4294
- const base = {
4295
- id: idGenerator.generate(JSON_TYPES.CLOCK),
4296
- type: JSON_TYPES.CLOCK,
4297
- mode: inline.mode,
4298
- clockType: inline.clockType,
4299
- value: inline.value,
4300
- ...inline.format !== void 0 && { format: inline.format },
4301
- ...inline.name !== void 0 && { name: inline.name }
4302
- };
4303
- if (inline.mode === "interactive") {
4304
- return { ...base, ...inline.snap !== void 0 && { snap: inline.snap } };
4305
- }
4306
- return base;
4307
- }
4308
- default:
4309
- throw new Error(`Unknown inline type: ${inline.type}`);
4310
- }
4311
- }
4312
- function transformValidation(validation, answers, widgets) {
4313
- if (validation.type === AST_TYPES.SIMPLE_VALIDATION) {
4314
- return {
4315
- mode: VALIDATION_MODES.SIMPLE,
4316
- answers
4317
- };
4318
- } else if (validation.type === AST_TYPES.EXPRESSION_VALIDATION) {
4319
- const validVariableNames = Object.keys(widgets);
4320
- const variables = extractVariables$1(validation.expression, validVariableNames);
4321
- return {
4322
- mode: VALIDATION_MODES.EXPRESSION,
4323
- expression: validation.expression,
4324
- variables
4325
- };
4326
- } else if (validation.type === AST_TYPES.MIXED_VALIDATION) {
4327
- const validVariableNames = Object.keys(widgets);
4328
- const variables = extractVariables$1(validation.expression, validVariableNames);
4329
- const inExpression = new Set(variables);
4330
- for (const [name, widget] of Object.entries(widgets)) {
4331
- if (name.startsWith("_")) continue;
4332
- if (name in answers) continue;
4333
- if (inExpression.has(name)) continue;
4334
- if (widget.type === JSON_TYPES.FRACTION) {
4335
- const w = widget;
4336
- if (w.numerator !== null && w.denominator !== null) continue;
4337
- }
4338
- if (widget.type === JSON_TYPES.VISUAL_FRACTION) {
4339
- const w = widget;
4340
- if (Array.isArray(w.filled) && w.filled.length > 0) continue;
4341
- }
4342
- throw new Error(
4343
- `Mixed validation: widget "${name}" has no \`answer\` and is not referenced in expression "${validation.expression}"`
4344
- );
4345
- }
4346
- return {
4347
- mode: VALIDATION_MODES.MIXED,
4348
- expression: validation.expression,
4349
- answers,
4350
- variables
4351
- };
4352
- }
4353
- throw new Error(`Unknown validation type: ${validation.type}`);
4354
- }
4355
- function extractVariables$1(expression, validVariables) {
4356
- const matches = expression.match(/\b[a-zA-Z_][a-zA-Z0-9_]*\b/g) || [];
4357
- const variables = matches.filter(
4358
- (v) => v !== "true" && v !== "false" && v !== "null" && v !== "undefined"
4359
- );
4360
- const uniqueVars = Array.from(new Set(variables));
4361
- if (validVariables && validVariables.length > 0) {
4362
- return uniqueVars.filter((v) => validVariables.includes(v));
4363
- }
4364
- return uniqueVars;
4365
- }
4366
- function normalize(value) {
4367
- if (typeof value === "number") {
4368
- return String(Math.round(value * 10 ** INTERNAL_PRECISION) / 10 ** INTERNAL_PRECISION);
4369
- }
4370
- const trimmed = String(value).trim().toLowerCase().replace(/^["']|["']$/g, "");
4371
- const asNum = Number(trimmed);
4372
- if (trimmed !== "" && !isNaN(asNum)) {
4373
- return String(Math.round(asNum * 10 ** INTERNAL_PRECISION) / 10 ** INTERNAL_PRECISION);
4374
- }
4375
- return trimmed;
4376
- }
4377
- const NL_DRAG_PREFIX = "\0nl-drag\0";
4378
- const isNLDragEncoded = (v) => v.startsWith(NL_DRAG_PREFIX);
4379
- const encodeNLDragValue = (label, position) => `${NL_DRAG_PREFIX}${label}@${String(position)}`;
4380
- function isNLDragCorrect(encoded, expectedLabel, expectedPosition) {
4381
- const payload = encoded.slice(NL_DRAG_PREFIX.length);
4382
- const atIdx = payload.lastIndexOf("@");
4383
- const label = normalize(payload.slice(0, atIdx));
4384
- const position = normalize(payload.slice(atIdx + 1));
4385
- return label === normalize(expectedLabel) && position === normalize(expectedPosition);
4386
- }
4387
3758
  var commonjsGlobal = typeof globalThis !== "undefined" ? globalThis : typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : {};
4388
3759
  var lodash = { exports: {} };
4389
3760
  /**
@@ -9701,172 +9072,803 @@ lodash.exports;
9701
9072
  arrayEach(["bind", "bindKey", "curry", "curryRight", "partial", "partialRight"], function(methodName) {
9702
9073
  lodash2[methodName].placeholder = lodash2;
9703
9074
  });
9704
- arrayEach(["drop", "take"], function(methodName, index) {
9705
- LazyWrapper.prototype[methodName] = function(n) {
9706
- n = n === undefined$1 ? 1 : nativeMax(toInteger(n), 0);
9707
- var result2 = this.__filtered__ && !index ? new LazyWrapper(this) : this.clone();
9708
- if (result2.__filtered__) {
9709
- result2.__takeCount__ = nativeMin(n, result2.__takeCount__);
9710
- } else {
9711
- result2.__views__.push({
9712
- "size": nativeMin(n, MAX_ARRAY_LENGTH),
9713
- "type": methodName + (result2.__dir__ < 0 ? "Right" : "")
9714
- });
9715
- }
9716
- return result2;
9075
+ arrayEach(["drop", "take"], function(methodName, index) {
9076
+ LazyWrapper.prototype[methodName] = function(n) {
9077
+ n = n === undefined$1 ? 1 : nativeMax(toInteger(n), 0);
9078
+ var result2 = this.__filtered__ && !index ? new LazyWrapper(this) : this.clone();
9079
+ if (result2.__filtered__) {
9080
+ result2.__takeCount__ = nativeMin(n, result2.__takeCount__);
9081
+ } else {
9082
+ result2.__views__.push({
9083
+ "size": nativeMin(n, MAX_ARRAY_LENGTH),
9084
+ "type": methodName + (result2.__dir__ < 0 ? "Right" : "")
9085
+ });
9086
+ }
9087
+ return result2;
9088
+ };
9089
+ LazyWrapper.prototype[methodName + "Right"] = function(n) {
9090
+ return this.reverse()[methodName](n).reverse();
9091
+ };
9092
+ });
9093
+ arrayEach(["filter", "map", "takeWhile"], function(methodName, index) {
9094
+ var type = index + 1, isFilter = type == LAZY_FILTER_FLAG || type == LAZY_WHILE_FLAG;
9095
+ LazyWrapper.prototype[methodName] = function(iteratee2) {
9096
+ var result2 = this.clone();
9097
+ result2.__iteratees__.push({
9098
+ "iteratee": getIteratee(iteratee2, 3),
9099
+ "type": type
9100
+ });
9101
+ result2.__filtered__ = result2.__filtered__ || isFilter;
9102
+ return result2;
9103
+ };
9104
+ });
9105
+ arrayEach(["head", "last"], function(methodName, index) {
9106
+ var takeName = "take" + (index ? "Right" : "");
9107
+ LazyWrapper.prototype[methodName] = function() {
9108
+ return this[takeName](1).value()[0];
9109
+ };
9110
+ });
9111
+ arrayEach(["initial", "tail"], function(methodName, index) {
9112
+ var dropName = "drop" + (index ? "" : "Right");
9113
+ LazyWrapper.prototype[methodName] = function() {
9114
+ return this.__filtered__ ? new LazyWrapper(this) : this[dropName](1);
9115
+ };
9116
+ });
9117
+ LazyWrapper.prototype.compact = function() {
9118
+ return this.filter(identity);
9119
+ };
9120
+ LazyWrapper.prototype.find = function(predicate) {
9121
+ return this.filter(predicate).head();
9122
+ };
9123
+ LazyWrapper.prototype.findLast = function(predicate) {
9124
+ return this.reverse().find(predicate);
9125
+ };
9126
+ LazyWrapper.prototype.invokeMap = baseRest(function(path, args) {
9127
+ if (typeof path == "function") {
9128
+ return new LazyWrapper(this);
9129
+ }
9130
+ return this.map(function(value) {
9131
+ return baseInvoke(value, path, args);
9132
+ });
9133
+ });
9134
+ LazyWrapper.prototype.reject = function(predicate) {
9135
+ return this.filter(negate(getIteratee(predicate)));
9136
+ };
9137
+ LazyWrapper.prototype.slice = function(start, end) {
9138
+ start = toInteger(start);
9139
+ var result2 = this;
9140
+ if (result2.__filtered__ && (start > 0 || end < 0)) {
9141
+ return new LazyWrapper(result2);
9142
+ }
9143
+ if (start < 0) {
9144
+ result2 = result2.takeRight(-start);
9145
+ } else if (start) {
9146
+ result2 = result2.drop(start);
9147
+ }
9148
+ if (end !== undefined$1) {
9149
+ end = toInteger(end);
9150
+ result2 = end < 0 ? result2.dropRight(-end) : result2.take(end - start);
9151
+ }
9152
+ return result2;
9153
+ };
9154
+ LazyWrapper.prototype.takeRightWhile = function(predicate) {
9155
+ return this.reverse().takeWhile(predicate).reverse();
9156
+ };
9157
+ LazyWrapper.prototype.toArray = function() {
9158
+ return this.take(MAX_ARRAY_LENGTH);
9159
+ };
9160
+ baseForOwn(LazyWrapper.prototype, function(func, methodName) {
9161
+ var checkIteratee = /^(?:filter|find|map|reject)|While$/.test(methodName), isTaker = /^(?:head|last)$/.test(methodName), lodashFunc = lodash2[isTaker ? "take" + (methodName == "last" ? "Right" : "") : methodName], retUnwrapped = isTaker || /^find/.test(methodName);
9162
+ if (!lodashFunc) {
9163
+ return;
9164
+ }
9165
+ lodash2.prototype[methodName] = function() {
9166
+ var value = this.__wrapped__, args = isTaker ? [1] : arguments, isLazy = value instanceof LazyWrapper, iteratee2 = args[0], useLazy = isLazy || isArray(value);
9167
+ var interceptor = function(value2) {
9168
+ var result3 = lodashFunc.apply(lodash2, arrayPush([value2], args));
9169
+ return isTaker && chainAll ? result3[0] : result3;
9170
+ };
9171
+ if (useLazy && checkIteratee && typeof iteratee2 == "function" && iteratee2.length != 1) {
9172
+ isLazy = useLazy = false;
9173
+ }
9174
+ var chainAll = this.__chain__, isHybrid = !!this.__actions__.length, isUnwrapped = retUnwrapped && !chainAll, onlyLazy = isLazy && !isHybrid;
9175
+ if (!retUnwrapped && useLazy) {
9176
+ value = onlyLazy ? value : new LazyWrapper(this);
9177
+ var result2 = func.apply(value, args);
9178
+ result2.__actions__.push({ "func": thru, "args": [interceptor], "thisArg": undefined$1 });
9179
+ return new LodashWrapper(result2, chainAll);
9180
+ }
9181
+ if (isUnwrapped && onlyLazy) {
9182
+ return func.apply(this, args);
9183
+ }
9184
+ result2 = this.thru(interceptor);
9185
+ return isUnwrapped ? isTaker ? result2.value()[0] : result2.value() : result2;
9186
+ };
9187
+ });
9188
+ arrayEach(["pop", "push", "shift", "sort", "splice", "unshift"], function(methodName) {
9189
+ var func = arrayProto[methodName], chainName = /^(?:push|sort|unshift)$/.test(methodName) ? "tap" : "thru", retUnwrapped = /^(?:pop|shift)$/.test(methodName);
9190
+ lodash2.prototype[methodName] = function() {
9191
+ var args = arguments;
9192
+ if (retUnwrapped && !this.__chain__) {
9193
+ var value = this.value();
9194
+ return func.apply(isArray(value) ? value : [], args);
9195
+ }
9196
+ return this[chainName](function(value2) {
9197
+ return func.apply(isArray(value2) ? value2 : [], args);
9198
+ });
9199
+ };
9200
+ });
9201
+ baseForOwn(LazyWrapper.prototype, function(func, methodName) {
9202
+ var lodashFunc = lodash2[methodName];
9203
+ if (lodashFunc) {
9204
+ var key2 = lodashFunc.name + "";
9205
+ if (!hasOwnProperty.call(realNames, key2)) {
9206
+ realNames[key2] = [];
9207
+ }
9208
+ realNames[key2].push({ "name": methodName, "func": lodashFunc });
9209
+ }
9210
+ });
9211
+ realNames[createHybrid(undefined$1, WRAP_BIND_KEY_FLAG).name] = [{
9212
+ "name": "wrapper",
9213
+ "func": undefined$1
9214
+ }];
9215
+ LazyWrapper.prototype.clone = lazyClone;
9216
+ LazyWrapper.prototype.reverse = lazyReverse;
9217
+ LazyWrapper.prototype.value = lazyValue;
9218
+ lodash2.prototype.at = wrapperAt;
9219
+ lodash2.prototype.chain = wrapperChain;
9220
+ lodash2.prototype.commit = wrapperCommit;
9221
+ lodash2.prototype.next = wrapperNext;
9222
+ lodash2.prototype.plant = wrapperPlant;
9223
+ lodash2.prototype.reverse = wrapperReverse;
9224
+ lodash2.prototype.toJSON = lodash2.prototype.valueOf = lodash2.prototype.value = wrapperValue;
9225
+ lodash2.prototype.first = lodash2.prototype.head;
9226
+ if (symIterator) {
9227
+ lodash2.prototype[symIterator] = wrapperToIterator;
9228
+ }
9229
+ return lodash2;
9230
+ };
9231
+ var _ = runInContext();
9232
+ if (freeModule) {
9233
+ (freeModule.exports = _)._ = _;
9234
+ freeExports._ = _;
9235
+ } else {
9236
+ root._ = _;
9237
+ }
9238
+ }).call(commonjsGlobal);
9239
+ })(lodash, lodash.exports);
9240
+ var lodashExports = lodash.exports;
9241
+ class LayoutIdGenerator {
9242
+ constructor() {
9243
+ this.counter = 0;
9244
+ }
9245
+ reset() {
9246
+ this.counter = 0;
9247
+ }
9248
+ generate(type) {
9249
+ return `${type}_${++this.counter}`;
9250
+ }
9251
+ }
9252
+ function toJSON(ast) {
9253
+ const idGenerator = new LayoutIdGenerator();
9254
+ const widgets = {};
9255
+ const answers = {};
9256
+ const allInlines = collectAllInlines(ast.lines);
9257
+ const inlineDragIds = /* @__PURE__ */ new Map();
9258
+ {
9259
+ let counter = 0;
9260
+ for (const inline of allInlines) {
9261
+ if (inline.type === AST_TYPES.INLINE_DRAG_OPTION) {
9262
+ inlineDragIds.set(inline, `${INLINE_DRAG_OPT_PREFIX}${counter++}`);
9263
+ }
9264
+ }
9265
+ }
9266
+ const layout = [];
9267
+ if (ast.question) {
9268
+ layout.push({
9269
+ id: idGenerator.generate(JSON_TYPES.QUESTION_TEXT),
9270
+ type: JSON_TYPES.QUESTION_TEXT,
9271
+ value: ast.question.value
9272
+ });
9273
+ }
9274
+ for (const block of ast.lines) {
9275
+ if (block.type === AST_TYPES.TABLE_BLOCK) {
9276
+ layout.push(transformTable(block, idGenerator, inlineDragIds));
9277
+ } else if (block.type === AST_TYPES.NUMBER_LINE_BLOCK) {
9278
+ const nlItem = transformNumberLine(block, idGenerator, inlineDragIds);
9279
+ layout.push(nlItem);
9280
+ if (nlItem.counter?.answer !== void 0) {
9281
+ answers[nlItem.counter.id] = nlItem.counter.answer;
9282
+ }
9283
+ for (const cursor of nlItem.cursors ?? []) {
9284
+ answers[cursor.id] = cursor.answer;
9285
+ }
9286
+ } else if (block.type === AST_TYPES.MATCHER_BLOCK) {
9287
+ const matcherBlock = block;
9288
+ layout.push({
9289
+ id: idGenerator.generate(JSON_TYPES.WIDGET_REF),
9290
+ type: JSON_TYPES.WIDGET_REF,
9291
+ widgetId: matcherBlock.name
9292
+ });
9293
+ layout.push({
9294
+ id: idGenerator.generate(JSON_TYPES.LINEBREAK),
9295
+ type: JSON_TYPES.LINEBREAK
9296
+ });
9297
+ } else if (block.type === AST_TYPES.PAIR_MATCHER_BLOCK) {
9298
+ const pmBlock = block;
9299
+ layout.push({
9300
+ id: idGenerator.generate(JSON_TYPES.WIDGET_REF),
9301
+ type: JSON_TYPES.WIDGET_REF,
9302
+ widgetId: pmBlock.name
9303
+ });
9304
+ layout.push({
9305
+ id: idGenerator.generate(JSON_TYPES.LINEBREAK),
9306
+ type: JSON_TYPES.LINEBREAK
9307
+ });
9308
+ } else if (block.type === AST_TYPES.VERTICAL_OPERATION_BLOCK) {
9309
+ layout.push(transformVerticalOperation(block, idGenerator));
9310
+ } else if (block.type === AST_TYPES.RADIO_OPTIONS) {
9311
+ const radioBlock = block;
9312
+ layout.push({
9313
+ id: idGenerator.generate(JSON_TYPES.WIDGET_REF),
9314
+ type: JSON_TYPES.WIDGET_REF,
9315
+ widgetId: radioBlock.name
9316
+ });
9317
+ layout.push({
9318
+ id: idGenerator.generate(JSON_TYPES.LINEBREAK),
9319
+ type: JSON_TYPES.LINEBREAK
9320
+ });
9321
+ } else {
9322
+ const lineItems = transformLine(block, idGenerator, inlineDragIds);
9323
+ layout.push(...lineItems);
9324
+ }
9325
+ }
9326
+ for (const inline of allInlines) {
9327
+ if (inline.type === AST_TYPES.INLINE_INPUT) {
9328
+ if (inline.inputType === INPUT_TYPES.STRING) {
9329
+ widgets[inline.name] = {
9330
+ type: JSON_TYPES.INLINE_INPUT,
9331
+ inputType: INPUT_TYPES.STRING,
9332
+ length: inline.length,
9333
+ placeholder: inline.placeholder
9717
9334
  };
9718
- LazyWrapper.prototype[methodName + "Right"] = function(n) {
9719
- return this.reverse()[methodName](n).reverse();
9335
+ } else {
9336
+ widgets[inline.name] = {
9337
+ type: JSON_TYPES.INLINE_INPUT,
9338
+ inputType: INPUT_TYPES.NUMERIC,
9339
+ digits: inline.digits,
9340
+ placeholder: inline.placeholder
9720
9341
  };
9721
- });
9722
- arrayEach(["filter", "map", "takeWhile"], function(methodName, index) {
9723
- var type = index + 1, isFilter = type == LAZY_FILTER_FLAG || type == LAZY_WHILE_FLAG;
9724
- LazyWrapper.prototype[methodName] = function(iteratee2) {
9725
- var result2 = this.clone();
9726
- result2.__iteratees__.push({
9727
- "iteratee": getIteratee(iteratee2, 3),
9728
- "type": type
9729
- });
9730
- result2.__filtered__ = result2.__filtered__ || isFilter;
9731
- return result2;
9342
+ }
9343
+ if (inline.answer !== void 0) {
9344
+ answers[inline.name] = inline.answer;
9345
+ }
9346
+ } else if (inline.type === AST_TYPES.CLOCK && inline.mode === "interactive") {
9347
+ answers[inline.name] = inline.answer;
9348
+ }
9349
+ if (inline.type === AST_TYPES.FRACTION) {
9350
+ widgets[inline.name] = {
9351
+ type: JSON_TYPES.FRACTION,
9352
+ numerator: inline.numerator ?? null,
9353
+ denominator: inline.denominator ?? null,
9354
+ simplified: inline.simplified ?? true,
9355
+ placeholder: inline.placeholder
9356
+ };
9357
+ if (inline.answer !== void 0) {
9358
+ answers[inline.name] = inline.answer;
9359
+ }
9360
+ }
9361
+ if (inline.type === AST_TYPES.INLINE_DRAG_OPTION) {
9362
+ const widgetId = inlineDragIds.get(inline);
9363
+ widgets[widgetId] = {
9364
+ type: JSON_TYPES.INLINE_DRAG_OPTION,
9365
+ value: inline.value
9366
+ };
9367
+ }
9368
+ if (inline.type === AST_TYPES.VISUAL_FRACTION) {
9369
+ const answerArr = inline.answer ?? [];
9370
+ widgets[inline.name] = {
9371
+ type: JSON_TYPES.VISUAL_FRACTION,
9372
+ shape: inline.shape,
9373
+ divisions: inline.divisions,
9374
+ filled: inline.filled ?? [],
9375
+ answer: answerArr,
9376
+ ...inline.orientation !== void 0 && { orientation: inline.orientation },
9377
+ ...inline.label !== void 0 && { label: inline.label },
9378
+ ...inline.size !== void 0 && { size: inline.size }
9379
+ };
9380
+ if (answerArr.length > 0) {
9381
+ answers[inline.name] = answerArr;
9382
+ }
9383
+ }
9384
+ }
9385
+ for (const block of ast.lines) {
9386
+ if (block.type !== AST_TYPES.VERTICAL_OPERATION_BLOCK) continue;
9387
+ const vop = block;
9388
+ for (const item of vop.items) {
9389
+ if (item.type !== AST_TYPES.VOP_ROW && item.type !== AST_TYPES.VOP_CARRY_ROW) continue;
9390
+ for (const cell of item.cells) {
9391
+ if (cell.kind !== "input") continue;
9392
+ if (widgets[cell.name]) {
9393
+ throw new Error(`VERTICAL_OPERATION: duplicate INPUT name "${cell.name}"`);
9394
+ }
9395
+ widgets[cell.name] = {
9396
+ type: JSON_TYPES.INLINE_INPUT,
9397
+ inputType: INPUT_TYPES.NUMERIC,
9398
+ digits: cell.digits
9732
9399
  };
9733
- });
9734
- arrayEach(["head", "last"], function(methodName, index) {
9735
- var takeName = "take" + (index ? "Right" : "");
9736
- LazyWrapper.prototype[methodName] = function() {
9737
- return this[takeName](1).value()[0];
9400
+ if (cell.answer !== void 0) {
9401
+ answers[cell.name] = cell.answer;
9402
+ }
9403
+ }
9404
+ }
9405
+ }
9406
+ for (const block of ast.lines) {
9407
+ if (block.type === AST_TYPES.RADIO_OPTIONS) {
9408
+ const radioBlock = block;
9409
+ widgets[radioBlock.name] = {
9410
+ type: JSON_TYPES.RADIO_OPTIONS,
9411
+ // Randomise option order per compile so the answer position isn't fixed.
9412
+ options: lodashExports.shuffle(radioBlock.options)
9413
+ };
9414
+ answers[radioBlock.name] = radioBlock.answer;
9415
+ } else if (block.type === AST_TYPES.MATCHER_BLOCK) {
9416
+ const matcherBlock = block;
9417
+ widgets[matcherBlock.name] = buildMatcherWidget(matcherBlock, idGenerator, inlineDragIds);
9418
+ answers[matcherBlock.name] = matcherBlock.answers;
9419
+ } else if (block.type === AST_TYPES.PAIR_MATCHER_BLOCK) {
9420
+ const pmBlock = block;
9421
+ widgets[pmBlock.name] = buildPairMatcherWidget(pmBlock, idGenerator, inlineDragIds);
9422
+ answers[pmBlock.name] = pmBlock.answers;
9423
+ }
9424
+ }
9425
+ if (ast.tools) {
9426
+ for (const tool of ast.tools) {
9427
+ if (tool.type === AST_TYPES.DRAG_OPTIONS) {
9428
+ widgets["_drag_options"] = {
9429
+ type: JSON_TYPES.DRAG_OPTIONS,
9430
+ // Randomise chip order per compile so the answer position isn't fixed.
9431
+ options: lodashExports.shuffle(tool.options)
9738
9432
  };
9739
- });
9740
- arrayEach(["initial", "tail"], function(methodName, index) {
9741
- var dropName = "drop" + (index ? "" : "Right");
9742
- LazyWrapper.prototype[methodName] = function() {
9743
- return this.__filtered__ ? new LazyWrapper(this) : this[dropName](1);
9433
+ } else if (tool.type === AST_TYPES.NUMERIC_KEYPAD) {
9434
+ widgets["_numeric_keypad"] = {
9435
+ type: JSON_TYPES.NUMERIC_KEYPAD,
9436
+ includeOperators: tool.includeOperators,
9437
+ includeDecimal: tool.includeDecimal,
9438
+ style: tool.style
9439
+ };
9440
+ } else if (tool.type === AST_TYPES.GEOMETRY_RULER) {
9441
+ widgets[RULER_WIDGET_ID] = {
9442
+ type: JSON_TYPES.RULER,
9443
+ size: tool.size
9444
+ };
9445
+ } else if (tool.type === AST_TYPES.GEOMETRY_PROTRACTOR) {
9446
+ widgets[PROTRACTOR_WIDGET_ID] = {
9447
+ type: JSON_TYPES.PROTRACTOR,
9448
+ size: tool.size
9449
+ };
9450
+ } else if (tool.type === AST_TYPES.GEOMETRY_SET_SQUARE) {
9451
+ widgets[SET_SQUARE_WIDGET_ID] = {
9452
+ type: JSON_TYPES.SET_SQUARE,
9453
+ size: tool.size
9454
+ };
9455
+ }
9456
+ }
9457
+ }
9458
+ return {
9459
+ version: "2.0",
9460
+ // Natural DSL version
9461
+ layout,
9462
+ widgets,
9463
+ ...ast.validation ? { validation: transformValidation(ast.validation, answers, widgets) } : {}
9464
+ };
9465
+ }
9466
+ function collectAllInlines(blocks) {
9467
+ const result = [];
9468
+ for (const block of blocks) {
9469
+ if (block.type === AST_TYPES.LINE_BLOCK) {
9470
+ result.push(...block.inlines);
9471
+ } else if (block.type === AST_TYPES.TABLE_BLOCK) {
9472
+ for (const row of block.rows) {
9473
+ for (const cell of row.cells) {
9474
+ result.push(...cell.inlines);
9475
+ }
9476
+ }
9477
+ } else if (block.type === AST_TYPES.NUMBER_LINE_BLOCK) {
9478
+ const numberLine = block;
9479
+ for (const position of numberLine.positions) {
9480
+ result.push(...position.inlines);
9481
+ }
9482
+ for (const arrow of numberLine.arrows ?? []) {
9483
+ result.push(...arrow.inlines);
9484
+ }
9485
+ } else if (block.type === AST_TYPES.MATCHER_BLOCK) {
9486
+ for (const row of block.rows) {
9487
+ for (const item of row.items) {
9488
+ result.push(...item.inlines);
9489
+ }
9490
+ }
9491
+ } else if (block.type === AST_TYPES.PAIR_MATCHER_BLOCK) {
9492
+ const pm = block;
9493
+ for (const item of [...pm.topItems, ...pm.bottomItems]) {
9494
+ result.push(...item.inlines);
9495
+ }
9496
+ }
9497
+ }
9498
+ return result;
9499
+ }
9500
+ function transformNumberLine(numberLine, idGenerator, inlineDragIds) {
9501
+ const positions = numberLine.positions.map((pos) => {
9502
+ const content = pos.inlines.map((inline) => transformInline(inline, idGenerator, inlineDragIds));
9503
+ return {
9504
+ id: idGenerator.generate(JSON_TYPES.NUMBER_LINE_POSITION),
9505
+ type: JSON_TYPES.NUMBER_LINE_POSITION,
9506
+ value: pos.value,
9507
+ content
9508
+ };
9509
+ });
9510
+ let counterItem;
9511
+ if (numberLine.counter !== void 0) {
9512
+ const c = numberLine.counter;
9513
+ counterItem = {
9514
+ id: idGenerator.generate(JSON_TYPES.NUMBER_LINE_COUNTER),
9515
+ type: JSON_TYPES.NUMBER_LINE_COUNTER,
9516
+ min: c.min,
9517
+ max: c.max,
9518
+ ...c.default !== void 0 && { default: c.default },
9519
+ ...c.name !== void 0 && { name: c.name },
9520
+ ...c.answer !== void 0 && { answer: c.answer }
9521
+ };
9522
+ }
9523
+ let highlights;
9524
+ if (numberLine.highlights !== void 0 && numberLine.highlights.length > 0) {
9525
+ highlights = numberLine.highlights.map((h) => ({
9526
+ id: idGenerator.generate(JSON_TYPES.NUMBER_LINE_HIGHLIGHT),
9527
+ type: JSON_TYPES.NUMBER_LINE_HIGHLIGHT,
9528
+ from: h.from,
9529
+ to: h.to,
9530
+ ...h.color !== void 0 && { color: h.color },
9531
+ ...h.label !== void 0 && { label: h.label }
9532
+ }));
9533
+ }
9534
+ let cursors;
9535
+ if (numberLine.cursors !== void 0 && numberLine.cursors.length > 0) {
9536
+ cursors = numberLine.cursors.map((c) => ({
9537
+ id: idGenerator.generate(JSON_TYPES.NUMBER_LINE_CURSOR),
9538
+ type: JSON_TYPES.NUMBER_LINE_CURSOR,
9539
+ value: c.value,
9540
+ answer: c.answer,
9541
+ fillRange: c.fillRange,
9542
+ ...c.color !== void 0 && { color: c.color },
9543
+ ...c.label !== void 0 && { label: c.label }
9544
+ }));
9545
+ }
9546
+ let arrows;
9547
+ if (numberLine.arrows !== void 0 && numberLine.arrows.length > 0) {
9548
+ arrows = numberLine.arrows.map((a) => ({
9549
+ id: idGenerator.generate(JSON_TYPES.NUMBER_LINE_ARROW),
9550
+ type: JSON_TYPES.NUMBER_LINE_ARROW,
9551
+ from: a.from,
9552
+ to: a.to,
9553
+ ...a.color !== void 0 && { color: a.color },
9554
+ content: a.inlines.map((inline) => transformInline(inline, idGenerator, inlineDragIds))
9555
+ }));
9556
+ }
9557
+ const item = {
9558
+ id: idGenerator.generate(JSON_TYPES.NUMBER_LINE),
9559
+ type: JSON_TYPES.NUMBER_LINE,
9560
+ min: numberLine.min,
9561
+ max: numberLine.max,
9562
+ step: numberLine.step,
9563
+ ...numberLine.minorTicks !== void 0 && { minorTicks: numberLine.minorTicks },
9564
+ ...numberLine.style !== void 0 && { style: numberLine.style },
9565
+ ...numberLine.labelVisibility !== void 0 && { labelVisibility: numberLine.labelVisibility },
9566
+ ...counterItem !== void 0 && { counter: counterItem },
9567
+ ...highlights !== void 0 && { highlights },
9568
+ ...cursors !== void 0 && { cursors },
9569
+ ...arrows !== void 0 && { arrows },
9570
+ ...numberLine.dragMode !== void 0 && { dragMode: numberLine.dragMode },
9571
+ positions
9572
+ };
9573
+ return item;
9574
+ }
9575
+ function mapMatcherItemList(items, idGenerator, inlineDragIds) {
9576
+ return items.map((item) => ({
9577
+ id: idGenerator.generate(JSON_TYPES.MATCHER_ITEM),
9578
+ type: JSON_TYPES.MATCHER_ITEM,
9579
+ itemId: item.itemId,
9580
+ content: item.inlines.map((inline) => transformInline(inline, idGenerator, inlineDragIds))
9581
+ }));
9582
+ }
9583
+ function buildMatcherWidget(matcher, idGenerator, inlineDragIds) {
9584
+ const rows = matcher.rows.map((row) => ({
9585
+ id: idGenerator.generate(JSON_TYPES.MATCHER_ROW),
9586
+ type: JSON_TYPES.MATCHER_ROW,
9587
+ items: mapMatcherItemList(row.items, idGenerator, inlineDragIds)
9588
+ }));
9589
+ const result = {
9590
+ type: JSON_TYPES.MATCHER,
9591
+ rows,
9592
+ groupCount: matcher.answers.length
9593
+ };
9594
+ if (matcher.colors !== void 0) {
9595
+ result.colors = matcher.colors;
9596
+ }
9597
+ return result;
9598
+ }
9599
+ function buildPairMatcherWidget(pm, idGenerator, inlineDragIds) {
9600
+ return {
9601
+ type: JSON_TYPES.PAIR_MATCHER,
9602
+ topItems: mapMatcherItemList(pm.topItems, idGenerator, inlineDragIds),
9603
+ bottomItems: mapMatcherItemList(pm.bottomItems, idGenerator, inlineDragIds)
9604
+ };
9605
+ }
9606
+ function transformVerticalOperation(vop, idGenerator) {
9607
+ const anyRowHasOp = vop.items.some(
9608
+ (item) => item.type === AST_TYPES.VOP_ROW && item.op !== void 0
9609
+ );
9610
+ if (!anyRowHasOp && vop.op === void 0) {
9611
+ throw new Error("VERTICAL_OPERATION: at least one op is required (set op on the block or on individual ROWs)");
9612
+ }
9613
+ let cols = 1;
9614
+ for (const item of vop.items) {
9615
+ if (item.type === AST_TYPES.VOP_ROW || item.type === AST_TYPES.VOP_CARRY_ROW) {
9616
+ cols = Math.max(cols, item.cells.length);
9617
+ }
9618
+ }
9619
+ function buildCells(row) {
9620
+ return row.cells.map((cell) => {
9621
+ const id = idGenerator.generate(JSON_TYPES.VOP_CELL);
9622
+ if (cell.kind === "digit") return { id, type: JSON_TYPES.VOP_CELL, kind: "digit", value: cell.value };
9623
+ if (cell.kind === "label") return { id, type: JSON_TYPES.VOP_CELL, kind: "label", value: cell.value };
9624
+ if (cell.kind === "color") return { id, type: JSON_TYPES.VOP_CELL, kind: "color", value: cell.value, color: cell.color };
9625
+ if (cell.kind === "bold") return { id, type: JSON_TYPES.VOP_CELL, kind: "bold", value: cell.value };
9626
+ if (cell.kind === "highlight") return { id, type: JSON_TYPES.VOP_CELL, kind: "highlight", value: cell.value };
9627
+ if (cell.kind === "input") return { id, type: JSON_TYPES.VOP_CELL, kind: "input", widgetId: cell.name };
9628
+ return { id, type: JSON_TYPES.VOP_CELL, kind: "empty" };
9629
+ });
9630
+ }
9631
+ const items = vop.items.map((item) => {
9632
+ if (item.type === AST_TYPES.VOP_SEPARATOR) {
9633
+ return {
9634
+ id: idGenerator.generate(JSON_TYPES.VOP_SEPARATOR),
9635
+ type: JSON_TYPES.VOP_SEPARATOR
9636
+ };
9637
+ }
9638
+ if (item.type === AST_TYPES.VOP_CARRY_ROW) {
9639
+ return {
9640
+ id: idGenerator.generate(JSON_TYPES.VOP_CARRY_ROW),
9641
+ type: JSON_TYPES.VOP_CARRY_ROW,
9642
+ cells: buildCells(item)
9643
+ };
9644
+ }
9645
+ const row = item;
9646
+ return {
9647
+ id: idGenerator.generate(JSON_TYPES.VOP_ROW),
9648
+ type: JSON_TYPES.VOP_ROW,
9649
+ cells: buildCells(row),
9650
+ ...row.op !== void 0 && { op: row.op }
9651
+ };
9652
+ });
9653
+ return {
9654
+ id: idGenerator.generate(JSON_TYPES.VERTICAL_OPERATION),
9655
+ type: JSON_TYPES.VERTICAL_OPERATION,
9656
+ op: vop.op,
9657
+ cols,
9658
+ items
9659
+ };
9660
+ }
9661
+ function transformTable(table, idGenerator, inlineDragIds) {
9662
+ const rows = table.rows.map((row) => {
9663
+ const cells = row.cells.map((cell) => {
9664
+ const items = cell.inlines.map((inline) => transformInline(inline, idGenerator, inlineDragIds));
9665
+ const cellItem = {
9666
+ id: idGenerator.generate(JSON_TYPES.TABLE_CELL),
9667
+ type: JSON_TYPES.TABLE_CELL,
9668
+ items
9669
+ };
9670
+ if (cell.colspan !== void 0) {
9671
+ cellItem.colspan = cell.colspan;
9672
+ }
9673
+ return cellItem;
9674
+ });
9675
+ return {
9676
+ id: idGenerator.generate(JSON_TYPES.TABLE_ROW),
9677
+ type: JSON_TYPES.TABLE_ROW,
9678
+ cells
9679
+ };
9680
+ });
9681
+ return {
9682
+ id: idGenerator.generate(JSON_TYPES.TABLE),
9683
+ type: JSON_TYPES.TABLE,
9684
+ cols: table.cols,
9685
+ rows
9686
+ };
9687
+ }
9688
+ function transformLine(line, idGenerator, inlineDragIds) {
9689
+ const items = [];
9690
+ for (const inline of line.inlines) {
9691
+ items.push(transformInline(inline, idGenerator, inlineDragIds));
9692
+ }
9693
+ items.push({
9694
+ id: idGenerator.generate(JSON_TYPES.LINEBREAK),
9695
+ type: JSON_TYPES.LINEBREAK
9696
+ });
9697
+ return items;
9698
+ }
9699
+ function transformInline(inline, idGenerator, inlineDragIds) {
9700
+ switch (inline.type) {
9701
+ case AST_TYPES.FREE_TEXT: {
9702
+ const trimmed = inline.value.trim();
9703
+ const operators = ["+", "-", "×", "÷", "=", "<", ">", "?"];
9704
+ if (operators.includes(trimmed) && inline.value.trim() === inline.value) {
9705
+ return {
9706
+ id: idGenerator.generate("operator"),
9707
+ type: "operator",
9708
+ operator: trimmed
9744
9709
  };
9745
- });
9746
- LazyWrapper.prototype.compact = function() {
9747
- return this.filter(identity);
9710
+ }
9711
+ return {
9712
+ id: idGenerator.generate(JSON_TYPES.TEXT),
9713
+ type: JSON_TYPES.TEXT,
9714
+ value: inline.value
9715
+ };
9716
+ }
9717
+ case AST_TYPES.SPACE:
9718
+ return {
9719
+ id: idGenerator.generate(JSON_TYPES.SPACE),
9720
+ type: JSON_TYPES.SPACE
9721
+ };
9722
+ case AST_TYPES.HIGHLIGHTED_TEXT:
9723
+ return {
9724
+ id: idGenerator.generate(JSON_TYPES.HIGHLIGHTED_TEXT),
9725
+ type: JSON_TYPES.HIGHLIGHTED_TEXT,
9726
+ value: inline.value,
9727
+ highlight: inline.highlight
9748
9728
  };
9749
- LazyWrapper.prototype.find = function(predicate) {
9750
- return this.filter(predicate).head();
9729
+ case AST_TYPES.COLOR_TEXT:
9730
+ return {
9731
+ id: idGenerator.generate(JSON_TYPES.COLOR_TEXT),
9732
+ type: JSON_TYPES.COLOR_TEXT,
9733
+ value: inline.value,
9734
+ color: inline.color,
9735
+ highlight: inline.highlight
9751
9736
  };
9752
- LazyWrapper.prototype.findLast = function(predicate) {
9753
- return this.reverse().find(predicate);
9737
+ case AST_TYPES.BOLD_TEXT:
9738
+ return {
9739
+ id: idGenerator.generate(JSON_TYPES.BOLD_TEXT),
9740
+ type: JSON_TYPES.BOLD_TEXT,
9741
+ value: inline.value,
9742
+ highlight: inline.highlight
9754
9743
  };
9755
- LazyWrapper.prototype.invokeMap = baseRest(function(path, args) {
9756
- if (typeof path == "function") {
9757
- return new LazyWrapper(this);
9758
- }
9759
- return this.map(function(value) {
9760
- return baseInvoke(value, path, args);
9761
- });
9762
- });
9763
- LazyWrapper.prototype.reject = function(predicate) {
9764
- return this.filter(negate(getIteratee(predicate)));
9744
+ case AST_TYPES.INLINE_INPUT:
9745
+ return {
9746
+ id: idGenerator.generate(JSON_TYPES.WIDGET_REF),
9747
+ type: JSON_TYPES.WIDGET_REF,
9748
+ widgetId: inline.name
9765
9749
  };
9766
- LazyWrapper.prototype.slice = function(start, end) {
9767
- start = toInteger(start);
9768
- var result2 = this;
9769
- if (result2.__filtered__ && (start > 0 || end < 0)) {
9770
- return new LazyWrapper(result2);
9771
- }
9772
- if (start < 0) {
9773
- result2 = result2.takeRight(-start);
9774
- } else if (start) {
9775
- result2 = result2.drop(start);
9776
- }
9777
- if (end !== undefined$1) {
9778
- end = toInteger(end);
9779
- result2 = end < 0 ? result2.dropRight(-end) : result2.take(end - start);
9780
- }
9781
- return result2;
9750
+ case AST_TYPES.FRACTION:
9751
+ return {
9752
+ id: idGenerator.generate(JSON_TYPES.WIDGET_REF),
9753
+ type: JSON_TYPES.WIDGET_REF,
9754
+ widgetId: inline.name
9782
9755
  };
9783
- LazyWrapper.prototype.takeRightWhile = function(predicate) {
9784
- return this.reverse().takeWhile(predicate).reverse();
9756
+ case AST_TYPES.IMAGE:
9757
+ return {
9758
+ id: idGenerator.generate(JSON_TYPES.IMAGE),
9759
+ type: JSON_TYPES.IMAGE,
9760
+ name: inline.name,
9761
+ src: inline.src,
9762
+ alt: inline.alt,
9763
+ ...inline.width !== void 0 && { width: inline.width },
9764
+ ...inline.height !== void 0 && { height: inline.height }
9785
9765
  };
9786
- LazyWrapper.prototype.toArray = function() {
9787
- return this.take(MAX_ARRAY_LENGTH);
9766
+ case AST_TYPES.VISUAL_FRACTION:
9767
+ return {
9768
+ id: idGenerator.generate(JSON_TYPES.WIDGET_REF),
9769
+ type: JSON_TYPES.WIDGET_REF,
9770
+ widgetId: inline.name
9788
9771
  };
9789
- baseForOwn(LazyWrapper.prototype, function(func, methodName) {
9790
- var checkIteratee = /^(?:filter|find|map|reject)|While$/.test(methodName), isTaker = /^(?:head|last)$/.test(methodName), lodashFunc = lodash2[isTaker ? "take" + (methodName == "last" ? "Right" : "") : methodName], retUnwrapped = isTaker || /^find/.test(methodName);
9791
- if (!lodashFunc) {
9792
- return;
9793
- }
9794
- lodash2.prototype[methodName] = function() {
9795
- var value = this.__wrapped__, args = isTaker ? [1] : arguments, isLazy = value instanceof LazyWrapper, iteratee2 = args[0], useLazy = isLazy || isArray(value);
9796
- var interceptor = function(value2) {
9797
- var result3 = lodashFunc.apply(lodash2, arrayPush([value2], args));
9798
- return isTaker && chainAll ? result3[0] : result3;
9799
- };
9800
- if (useLazy && checkIteratee && typeof iteratee2 == "function" && iteratee2.length != 1) {
9801
- isLazy = useLazy = false;
9802
- }
9803
- var chainAll = this.__chain__, isHybrid = !!this.__actions__.length, isUnwrapped = retUnwrapped && !chainAll, onlyLazy = isLazy && !isHybrid;
9804
- if (!retUnwrapped && useLazy) {
9805
- value = onlyLazy ? value : new LazyWrapper(this);
9806
- var result2 = func.apply(value, args);
9807
- result2.__actions__.push({ "func": thru, "args": [interceptor], "thisArg": undefined$1 });
9808
- return new LodashWrapper(result2, chainAll);
9809
- }
9810
- if (isUnwrapped && onlyLazy) {
9811
- return func.apply(this, args);
9812
- }
9813
- result2 = this.thru(interceptor);
9814
- return isUnwrapped ? isTaker ? result2.value()[0] : result2.value() : result2;
9815
- };
9816
- });
9817
- arrayEach(["pop", "push", "shift", "sort", "splice", "unshift"], function(methodName) {
9818
- var func = arrayProto[methodName], chainName = /^(?:push|sort|unshift)$/.test(methodName) ? "tap" : "thru", retUnwrapped = /^(?:pop|shift)$/.test(methodName);
9819
- lodash2.prototype[methodName] = function() {
9820
- var args = arguments;
9821
- if (retUnwrapped && !this.__chain__) {
9822
- var value = this.value();
9823
- return func.apply(isArray(value) ? value : [], args);
9824
- }
9825
- return this[chainName](function(value2) {
9826
- return func.apply(isArray(value2) ? value2 : [], args);
9827
- });
9828
- };
9829
- });
9830
- baseForOwn(LazyWrapper.prototype, function(func, methodName) {
9831
- var lodashFunc = lodash2[methodName];
9832
- if (lodashFunc) {
9833
- var key2 = lodashFunc.name + "";
9834
- if (!hasOwnProperty.call(realNames, key2)) {
9835
- realNames[key2] = [];
9836
- }
9837
- realNames[key2].push({ "name": methodName, "func": lodashFunc });
9838
- }
9839
- });
9840
- realNames[createHybrid(undefined$1, WRAP_BIND_KEY_FLAG).name] = [{
9841
- "name": "wrapper",
9842
- "func": undefined$1
9843
- }];
9844
- LazyWrapper.prototype.clone = lazyClone;
9845
- LazyWrapper.prototype.reverse = lazyReverse;
9846
- LazyWrapper.prototype.value = lazyValue;
9847
- lodash2.prototype.at = wrapperAt;
9848
- lodash2.prototype.chain = wrapperChain;
9849
- lodash2.prototype.commit = wrapperCommit;
9850
- lodash2.prototype.next = wrapperNext;
9851
- lodash2.prototype.plant = wrapperPlant;
9852
- lodash2.prototype.reverse = wrapperReverse;
9853
- lodash2.prototype.toJSON = lodash2.prototype.valueOf = lodash2.prototype.value = wrapperValue;
9854
- lodash2.prototype.first = lodash2.prototype.head;
9855
- if (symIterator) {
9856
- lodash2.prototype[symIterator] = wrapperToIterator;
9772
+ case AST_TYPES.INLINE_DRAG_OPTION:
9773
+ return {
9774
+ id: idGenerator.generate(JSON_TYPES.WIDGET_REF),
9775
+ type: JSON_TYPES.WIDGET_REF,
9776
+ widgetId: inlineDragIds.get(inline)
9777
+ };
9778
+ case AST_TYPES.CLOCK: {
9779
+ const base = {
9780
+ id: idGenerator.generate(JSON_TYPES.CLOCK),
9781
+ type: JSON_TYPES.CLOCK,
9782
+ mode: inline.mode,
9783
+ clockType: inline.clockType,
9784
+ value: inline.value,
9785
+ ...inline.format !== void 0 && { format: inline.format },
9786
+ ...inline.name !== void 0 && { name: inline.name }
9787
+ };
9788
+ if (inline.mode === "interactive") {
9789
+ return { ...base, ...inline.snap !== void 0 && { snap: inline.snap } };
9857
9790
  }
9858
- return lodash2;
9791
+ return base;
9792
+ }
9793
+ default:
9794
+ throw new Error(`Unknown inline type: ${inline.type}`);
9795
+ }
9796
+ }
9797
+ function transformValidation(validation, answers, widgets) {
9798
+ if (validation.type === AST_TYPES.SIMPLE_VALIDATION) {
9799
+ return {
9800
+ mode: VALIDATION_MODES.SIMPLE,
9801
+ answers
9859
9802
  };
9860
- var _ = runInContext();
9861
- if (freeModule) {
9862
- (freeModule.exports = _)._ = _;
9863
- freeExports._ = _;
9864
- } else {
9865
- root._ = _;
9803
+ } else if (validation.type === AST_TYPES.EXPRESSION_VALIDATION) {
9804
+ const validVariableNames = Object.keys(widgets);
9805
+ const variables = extractVariables$1(validation.expression, validVariableNames);
9806
+ return {
9807
+ mode: VALIDATION_MODES.EXPRESSION,
9808
+ expression: validation.expression,
9809
+ variables
9810
+ };
9811
+ } else if (validation.type === AST_TYPES.MIXED_VALIDATION) {
9812
+ const validVariableNames = Object.keys(widgets);
9813
+ const variables = extractVariables$1(validation.expression, validVariableNames);
9814
+ const inExpression = new Set(variables);
9815
+ for (const [name, widget] of Object.entries(widgets)) {
9816
+ if (name.startsWith("_")) continue;
9817
+ if (name in answers) continue;
9818
+ if (inExpression.has(name)) continue;
9819
+ if (widget.type === JSON_TYPES.FRACTION) {
9820
+ const w = widget;
9821
+ if (w.numerator !== null && w.denominator !== null) continue;
9822
+ }
9823
+ if (widget.type === JSON_TYPES.VISUAL_FRACTION) {
9824
+ const w = widget;
9825
+ if (Array.isArray(w.filled) && w.filled.length > 0) continue;
9826
+ }
9827
+ throw new Error(
9828
+ `Mixed validation: widget "${name}" has no \`answer\` and is not referenced in expression "${validation.expression}"`
9829
+ );
9866
9830
  }
9867
- }).call(commonjsGlobal);
9868
- })(lodash, lodash.exports);
9869
- var lodashExports = lodash.exports;
9831
+ return {
9832
+ mode: VALIDATION_MODES.MIXED,
9833
+ expression: validation.expression,
9834
+ answers,
9835
+ variables
9836
+ };
9837
+ }
9838
+ throw new Error(`Unknown validation type: ${validation.type}`);
9839
+ }
9840
+ function extractVariables$1(expression, validVariables) {
9841
+ const matches = expression.match(/\b[a-zA-Z_][a-zA-Z0-9_]*\b/g) || [];
9842
+ const variables = matches.filter(
9843
+ (v) => v !== "true" && v !== "false" && v !== "null" && v !== "undefined"
9844
+ );
9845
+ const uniqueVars = Array.from(new Set(variables));
9846
+ if (validVariables && validVariables.length > 0) {
9847
+ return uniqueVars.filter((v) => validVariables.includes(v));
9848
+ }
9849
+ return uniqueVars;
9850
+ }
9851
+ function normalize(value) {
9852
+ if (typeof value === "number") {
9853
+ return String(Math.round(value * 10 ** INTERNAL_PRECISION) / 10 ** INTERNAL_PRECISION);
9854
+ }
9855
+ const trimmed = String(value).trim().toLowerCase().replace(/^["']|["']$/g, "");
9856
+ const asNum = Number(trimmed);
9857
+ if (trimmed !== "" && !isNaN(asNum)) {
9858
+ return String(Math.round(asNum * 10 ** INTERNAL_PRECISION) / 10 ** INTERNAL_PRECISION);
9859
+ }
9860
+ return trimmed;
9861
+ }
9862
+ const NL_DRAG_PREFIX = "\0nl-drag\0";
9863
+ const isNLDragEncoded = (v) => v.startsWith(NL_DRAG_PREFIX);
9864
+ const encodeNLDragValue = (label, position) => `${NL_DRAG_PREFIX}${label}@${String(position)}`;
9865
+ function isNLDragCorrect(encoded, expectedLabel, expectedPosition) {
9866
+ const payload = encoded.slice(NL_DRAG_PREFIX.length);
9867
+ const atIdx = payload.lastIndexOf("@");
9868
+ const label = normalize(payload.slice(0, atIdx));
9869
+ const position = normalize(payload.slice(atIdx + 1));
9870
+ return label === normalize(expectedLabel) && position === normalize(expectedPosition);
9871
+ }
9870
9872
  function areMatcherEqual(expected, result) {
9871
9873
  if (!Array.isArray(result)) return false;
9872
9874
  if (expected.length !== result.length) return false;
@@ -15245,21 +15247,19 @@ const DragOptions = ({
15245
15247
  options,
15246
15248
  onOptionClick
15247
15249
  }) => {
15248
- const [shuffledOptions] = React.useState(
15249
- () => lodashExports.shuffle(options.map((option, idx) => ({
15250
- optionId: `drag-option-${idx}`,
15251
- value: String(option)
15252
- })))
15253
- );
15254
- return /* @__PURE__ */ jsxRuntime.jsx(PillBackground, { children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "drag-options", children: shuffledOptions.map(({ optionId, value }) => /* @__PURE__ */ jsxRuntime.jsx(
15255
- SingleDragOption,
15256
- {
15257
- optionId,
15258
- value,
15259
- onOptionClick
15260
- },
15261
- optionId
15262
- )) }) });
15250
+ return /* @__PURE__ */ jsxRuntime.jsx(PillBackground, { children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "drag-options", children: options.map((option, idx) => {
15251
+ const optionId = `drag-option-${idx}`;
15252
+ const optionValue = String(option);
15253
+ return /* @__PURE__ */ jsxRuntime.jsx(
15254
+ SingleDragOption,
15255
+ {
15256
+ optionId,
15257
+ value: optionValue,
15258
+ onOptionClick
15259
+ },
15260
+ optionId
15261
+ );
15262
+ }) }) });
15263
15263
  };
15264
15264
  const InlineDragOption = ({
15265
15265
  widgetId,
@@ -15843,8 +15843,7 @@ const RadioOptions = ({
15843
15843
  onChange
15844
15844
  }) => {
15845
15845
  const playSound = useSound();
15846
- const [shuffledOptions] = React.useState(() => lodashExports.shuffle(options));
15847
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "radio-options", children: shuffledOptions.map((option, idx) => {
15846
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "radio-options", children: options.map((option, idx) => {
15848
15847
  const isSelected = value === option;
15849
15848
  const statusClass = isSelected && validationStatus ? ` widget--${validationStatus}` : "";
15850
15849
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -18136,6 +18135,8 @@ function groupLineItems(items) {
18136
18135
  return groups;
18137
18136
  }
18138
18137
  const toValidationStatus = (v) => v === true ? "correct" : v === false ? "incorrect" : null;
18138
+ const isNumericText = (value) => /^[\d.,\s]+$/.test(value);
18139
+ const numberClass = (value) => isNumericText(value) ? "text-item-number" : "";
18139
18140
  const LayoutRenderer = ({
18140
18141
  item,
18141
18142
  widgets,
@@ -18152,47 +18153,49 @@ const LayoutRenderer = ({
18152
18153
  return /* @__PURE__ */ jsxRuntime.jsx("span", { className: `question-text ${hasArabic ? "question-text-arabic" : ""}`, children: item.value });
18153
18154
  }
18154
18155
  case JSON_TYPES.TEXT: {
18155
- const isNum = /^[\d.,\s]+$/.test(item.value);
18156
- return /* @__PURE__ */ jsxRuntime.jsx("span", { className: `text-item ${isNum ? "text-item-number" : ""} ${isArabic(item.value) ? "text-item-arabic" : "text-item-not-arabic"}`, children: item.value });
18156
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: `text-item ${numberClass(item.value)} ${isArabic(item.value) ? "text-item-arabic" : "text-item-not-arabic"}`, children: item.value });
18157
18157
  }
18158
18158
  case JSON_TYPES.HIGHLIGHTED_TEXT: {
18159
18159
  const hasArabic = isArabic(item.value);
18160
18160
  const dirAttr = !hasArabic ? "ltr" : void 0;
18161
+ const numClass = numberClass(item.value);
18161
18162
  if (item.highlight) {
18162
18163
  const [start, end] = item.highlight;
18163
- return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "highlighted-text text-item", dir: dirAttr, children: [
18164
+ return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: `highlighted-text text-item ${numClass}`, dir: dirAttr, children: [
18164
18165
  item.value.slice(0, start),
18165
18166
  /* @__PURE__ */ jsxRuntime.jsx("mark", { className: "highlight text-item", children: item.value.slice(start, end) }),
18166
18167
  item.value.slice(end)
18167
18168
  ] });
18168
18169
  }
18169
- return /* @__PURE__ */ jsxRuntime.jsx("mark", { className: "highlighted-text text-item", dir: dirAttr, children: item.value });
18170
+ return /* @__PURE__ */ jsxRuntime.jsx("mark", { className: `highlighted-text text-item ${numClass}`, dir: dirAttr, children: item.value });
18170
18171
  }
18171
18172
  case JSON_TYPES.COLOR_TEXT: {
18172
18173
  const hasArabic = isArabic(item.value);
18173
18174
  const dirAttr = !hasArabic ? "ltr" : void 0;
18175
+ const numClass = numberClass(item.value);
18174
18176
  if (item.highlight) {
18175
18177
  const [start, end] = item.highlight;
18176
- return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "color-text text-item", dir: dirAttr, children: [
18178
+ return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: `color-text text-item ${numClass}`, dir: dirAttr, children: [
18177
18179
  item.value.slice(0, start),
18178
18180
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "color-text-colored", style: { color: item.color }, children: item.value.slice(start, end) }),
18179
18181
  item.value.slice(end)
18180
18182
  ] });
18181
18183
  }
18182
- return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "color-text text-item", style: { color: item.color }, dir: dirAttr, children: item.value });
18184
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: `color-text text-item ${numClass}`, style: { color: item.color }, dir: dirAttr, children: item.value });
18183
18185
  }
18184
18186
  case JSON_TYPES.BOLD_TEXT: {
18185
18187
  const hasArabic = isArabic(item.value);
18186
18188
  const dirAttr = !hasArabic ? "ltr" : void 0;
18189
+ const numClass = numberClass(item.value);
18187
18190
  if (item.highlight) {
18188
18191
  const [start, end] = item.highlight;
18189
- return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "bold-text text-item", dir: dirAttr, children: [
18192
+ return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: `bold-text text-item ${numClass}`, dir: dirAttr, children: [
18190
18193
  item.value.slice(0, start),
18191
18194
  /* @__PURE__ */ jsxRuntime.jsx("strong", { className: "bold-text-strong text-item", children: item.value.slice(start, end) }),
18192
18195
  item.value.slice(end)
18193
18196
  ] });
18194
18197
  }
18195
- return /* @__PURE__ */ jsxRuntime.jsx("strong", { className: "bold-text text-item", dir: dirAttr, children: item.value });
18198
+ return /* @__PURE__ */ jsxRuntime.jsx("strong", { className: `bold-text text-item ${numClass}`, dir: dirAttr, children: item.value });
18196
18199
  }
18197
18200
  case JSON_TYPES.IMAGE:
18198
18201
  return /* @__PURE__ */ jsxRuntime.jsx(