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