@mathscan/math-exercise-engine 1.0.2 → 1.0.3

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
@@ -4,6 +4,87 @@ import { create, all } from "mathjs";
4
4
  import { jsx, jsxs } from "react/jsx-runtime";
5
5
  import React, { useLayoutEffect, useEffect, useRef, useMemo, useCallback, useState, createContext, memo, useReducer, useContext, forwardRef, cloneElement, useImperativeHandle } from "react";
6
6
  import require$$0, { unstable_batchedUpdates, createPortal } from "react-dom";
7
+ const AST_TYPES = {
8
+ // ═══════════════════════════════════════════════════════════════════════════
9
+ // Inline Elements
10
+ // ═══════════════════════════════════════════════════════════════════════════
11
+ /** Regular text without special formatting */
12
+ FREE_TEXT: "FreeText",
13
+ /** Explicit spacing element */
14
+ SPACE: "Space",
15
+ /** Text with optional character-range highlighting */
16
+ HIGHLIGHTED_TEXT: "HighlightedText",
17
+ /** Inline input widget (string or numeric) */
18
+ INLINE_INPUT: "InlineInput",
19
+ // ═══════════════════════════════════════════════════════════════════════════
20
+ // Blocks
21
+ // ═══════════════════════════════════════════════════════════════════════════
22
+ /** Question/instruction text block */
23
+ TEXT_BLOCK: "TextBlock",
24
+ /** Line containing inline elements */
25
+ LINE_BLOCK: "LineBlock",
26
+ // ═══════════════════════════════════════════════════════════════════════════
27
+ // Tools
28
+ // ═══════════════════════════════════════════════════════════════════════════
29
+ /** Drag-and-drop options tool */
30
+ DRAG_OPTIONS: "DragOptions",
31
+ /** Numeric keypad input tool */
32
+ NUMERIC_KEYPAD: "NumericKeypad",
33
+ // ═══════════════════════════════════════════════════════════════════════════
34
+ // Validation
35
+ // ═══════════════════════════════════════════════════════════════════════════
36
+ /** Simple answer matching validation */
37
+ SIMPLE_VALIDATION: "SimpleValidation",
38
+ /** Expression-based validation */
39
+ EXPRESSION_VALIDATION: "ExpressionValidation",
40
+ // ═══════════════════════════════════════════════════════════════════════════
41
+ // Document
42
+ // ═══════════════════════════════════════════════════════════════════════════
43
+ /** Root exercise document */
44
+ EXERCISE_DOCUMENT: "ExerciseDocument"
45
+ };
46
+ const JSON_TYPES = {
47
+ // ═══════════════════════════════════════════════════════════════════════════
48
+ // Layout Items
49
+ // ═══════════════════════════════════════════════════════════════════════════
50
+ /** Question/instruction text */
51
+ QUESTION_TEXT: "question-text",
52
+ /** Regular text element */
53
+ TEXT: "text",
54
+ /** Spacing element */
55
+ SPACE: "space",
56
+ /** Text with optional highlighting */
57
+ HIGHLIGHTED_TEXT: "highlighted-text",
58
+ /** Reference to a widget */
59
+ WIDGET_REF: "widget-ref",
60
+ /** Line break element */
61
+ LINEBREAK: "linebreak",
62
+ /** Grouped layout items */
63
+ GROUP: "group",
64
+ /** Operator symbol */
65
+ OPERATOR: "operator",
66
+ // ═══════════════════════════════════════════════════════════════════════════
67
+ // Widget Types
68
+ // ═══════════════════════════════════════════════════════════════════════════
69
+ /** Inline input widget (string or numeric) */
70
+ INLINE_INPUT: "inline-input",
71
+ /** Drag-and-drop options widget */
72
+ DRAG_OPTIONS: "drag-options",
73
+ /** Numeric keypad widget */
74
+ NUMERIC_KEYPAD: "numeric-keypad"
75
+ };
76
+ const VALIDATION_MODES = {
77
+ /** Simple answer matching validation */
78
+ SIMPLE: "simple",
79
+ /** Expression-based validation using JavaScript expressions */
80
+ EXPRESSION: "expression"
81
+ };
82
+ const INPUT_TYPES = {
83
+ /** String/text input */
84
+ STRING: "string",
85
+ /** Numeric input (digits only) */
86
+ NUMERIC: "numeric"
87
+ };
7
88
  const Newline = createToken({
8
89
  name: "Newline",
9
90
  pattern: /\r?\n/,
@@ -130,7 +211,7 @@ const Comma = createToken({
130
211
  });
131
212
  const FreeText = createToken({
132
213
  name: "FreeText",
133
- pattern: /(?:[^A-Z\n\r]|(?![A-Z_]+\[)[A-Z])+/,
214
+ pattern: /(?:[^A-Z\n\r \t+\-−×*÷/=<>?()\[\],]|(?![A-Z_]+\[)[A-Z])+/,
134
215
  line_breaks: false
135
216
  });
136
217
  const allTokens = [
@@ -372,7 +453,9 @@ class MathExerciseParser extends CstParser {
372
453
  this.numericKeypad = this.RULE("numericKeypad", () => {
373
454
  this.CONSUME(NUMERIC_KEYPAD);
374
455
  this.CONSUME(LBracket);
375
- this.SUBRULE(this.parameterList);
456
+ this.OPTION(() => {
457
+ this.SUBRULE(this.parameterList);
458
+ });
376
459
  this.CONSUME(RBracket);
377
460
  this.MANY(() => {
378
461
  this.OR([
@@ -469,29 +552,29 @@ class MathExerciseParser extends CstParser {
469
552
  }
470
553
  const parser = new MathExerciseParser();
471
554
  const FreeTextInlineSchema = z.object({
472
- type: z.literal("FreeText"),
555
+ type: z.literal(AST_TYPES.FREE_TEXT),
473
556
  value: z.string()
474
557
  });
475
558
  const SpaceInlineSchema = z.object({
476
- type: z.literal("Space")
559
+ type: z.literal(AST_TYPES.SPACE)
477
560
  });
478
- const NumberTextInlineSchema = z.object({
479
- type: z.literal("NumberText"),
561
+ const HighlightedTextInlineSchema = z.object({
562
+ type: z.literal(AST_TYPES.HIGHLIGHTED_TEXT),
480
563
  value: z.string(),
481
564
  highlight: z.tuple([z.number(), z.number()]).optional()
482
565
  });
483
566
  const InlineInputWidgetSchema = z.discriminatedUnion("inputType", [
484
567
  z.object({
485
- type: z.literal("InlineInput"),
568
+ type: z.literal(AST_TYPES.INLINE_INPUT),
486
569
  name: z.string(),
487
- inputType: z.literal("string"),
570
+ inputType: z.literal(INPUT_TYPES.STRING),
488
571
  length: z.number().int().positive(),
489
572
  answer: z.union([z.string(), z.number()]).optional()
490
573
  }),
491
574
  z.object({
492
- type: z.literal("InlineInput"),
575
+ type: z.literal(AST_TYPES.INLINE_INPUT),
493
576
  name: z.string(),
494
- inputType: z.literal("numeric"),
577
+ inputType: z.literal(INPUT_TYPES.NUMERIC),
495
578
  digits: z.number().int().positive(),
496
579
  answer: z.union([z.string(), z.number()]).optional()
497
580
  })
@@ -499,41 +582,41 @@ const InlineInputWidgetSchema = z.discriminatedUnion("inputType", [
499
582
  const InlineNodeSchema = z.union([
500
583
  FreeTextInlineSchema,
501
584
  SpaceInlineSchema,
502
- NumberTextInlineSchema,
585
+ HighlightedTextInlineSchema,
503
586
  InlineInputWidgetSchema
504
587
  ]);
505
588
  function createFreeText(value) {
506
589
  return FreeTextInlineSchema.parse({
507
- type: "FreeText",
590
+ type: AST_TYPES.FREE_TEXT,
508
591
  value
509
592
  });
510
593
  }
511
594
  function createSpace() {
512
595
  return SpaceInlineSchema.parse({
513
- type: "Space"
596
+ type: AST_TYPES.SPACE
514
597
  });
515
598
  }
516
- function createNumberText(value, highlight) {
517
- return NumberTextInlineSchema.parse({
518
- type: "NumberText",
599
+ function createHighlightedText(value, highlight) {
600
+ return HighlightedTextInlineSchema.parse({
601
+ type: AST_TYPES.HIGHLIGHTED_TEXT,
519
602
  value,
520
603
  highlight
521
604
  });
522
605
  }
523
606
  function createStringInput(name, length, answer) {
524
607
  return InlineInputWidgetSchema.parse({
525
- type: "InlineInput",
608
+ type: AST_TYPES.INLINE_INPUT,
526
609
  name,
527
- inputType: "string",
610
+ inputType: INPUT_TYPES.STRING,
528
611
  length,
529
612
  answer
530
613
  });
531
614
  }
532
615
  function createNumericInput(name, digits, answer) {
533
616
  return InlineInputWidgetSchema.parse({
534
- type: "InlineInput",
617
+ type: AST_TYPES.INLINE_INPUT,
535
618
  name,
536
- inputType: "numeric",
619
+ inputType: INPUT_TYPES.NUMERIC,
537
620
  digits,
538
621
  answer
539
622
  });
@@ -551,22 +634,22 @@ function isInlineNode(value) {
551
634
  return InlineNodeSchema.safeParse(value).success;
552
635
  }
553
636
  const TextBlockSchema = z.object({
554
- type: z.literal("TextBlock"),
637
+ type: z.literal(AST_TYPES.TEXT_BLOCK),
555
638
  value: z.string()
556
639
  });
557
640
  const LineBlockSchema = z.object({
558
- type: z.literal("LineBlock"),
641
+ type: z.literal(AST_TYPES.LINE_BLOCK),
559
642
  inlines: z.array(InlineNodeSchema)
560
643
  });
561
644
  function createTextBlock(value) {
562
645
  return TextBlockSchema.parse({
563
- type: "TextBlock",
646
+ type: AST_TYPES.TEXT_BLOCK,
564
647
  value
565
648
  });
566
649
  }
567
650
  function createLineBlock(inlines) {
568
651
  return LineBlockSchema.parse({
569
- type: "LineBlock",
652
+ type: AST_TYPES.LINE_BLOCK,
570
653
  inlines
571
654
  });
572
655
  }
@@ -577,13 +660,13 @@ function isLineBlock(value) {
577
660
  return LineBlockSchema.safeParse(value).success;
578
661
  }
579
662
  const DragOptionsToolSchema = z.object({
580
- type: z.literal("DragOptions"),
663
+ type: z.literal(AST_TYPES.DRAG_OPTIONS),
581
664
  options: z.array(z.string()).min(1)
582
665
  });
583
666
  const NumericKeypadToolSchema = z.object({
584
- type: z.literal("NumericKeypad"),
585
- includeDecimal: z.boolean().default(false),
586
- includeNegative: z.boolean().default(false)
667
+ type: z.literal(AST_TYPES.NUMERIC_KEYPAD),
668
+ includeOperators: z.boolean().optional(),
669
+ includeDecimal: z.boolean().optional()
587
670
  });
588
671
  const ToolNodeSchema = z.discriminatedUnion("type", [
589
672
  DragOptionsToolSchema,
@@ -591,15 +674,15 @@ const ToolNodeSchema = z.discriminatedUnion("type", [
591
674
  ]);
592
675
  function createDragOptions(options) {
593
676
  return DragOptionsToolSchema.parse({
594
- type: "DragOptions",
677
+ type: AST_TYPES.DRAG_OPTIONS,
595
678
  options
596
679
  });
597
680
  }
598
- function createNumericKeypad(includeDecimal = false, includeNegative = false) {
681
+ function createNumericKeypad(includeOperators, includeDecimal) {
599
682
  return NumericKeypadToolSchema.parse({
600
- type: "NumericKeypad",
601
- includeDecimal,
602
- includeNegative
683
+ type: AST_TYPES.NUMERIC_KEYPAD,
684
+ includeOperators,
685
+ includeDecimal
603
686
  });
604
687
  }
605
688
  function isDragOptions(value) {
@@ -612,10 +695,10 @@ function isToolNode(value) {
612
695
  return ToolNodeSchema.safeParse(value).success;
613
696
  }
614
697
  const SimpleValidationSchema = z.object({
615
- type: z.literal("SimpleValidation")
698
+ type: z.literal(AST_TYPES.SIMPLE_VALIDATION)
616
699
  });
617
700
  const ExpressionValidationSchema = z.object({
618
- type: z.literal("ExpressionValidation"),
701
+ type: z.literal(AST_TYPES.EXPRESSION_VALIDATION),
619
702
  expression: z.string()
620
703
  });
621
704
  const ValidationNodeSchema = z.discriminatedUnion("type", [
@@ -624,12 +707,12 @@ const ValidationNodeSchema = z.discriminatedUnion("type", [
624
707
  ]);
625
708
  function createSimpleValidation() {
626
709
  return SimpleValidationSchema.parse({
627
- type: "SimpleValidation"
710
+ type: AST_TYPES.SIMPLE_VALIDATION
628
711
  });
629
712
  }
630
713
  function createExpressionValidation(expression) {
631
714
  return ExpressionValidationSchema.parse({
632
- type: "ExpressionValidation",
715
+ type: AST_TYPES.EXPRESSION_VALIDATION,
633
716
  expression
634
717
  });
635
718
  }
@@ -643,7 +726,7 @@ function isValidationNode(value) {
643
726
  return ValidationNodeSchema.safeParse(value).success;
644
727
  }
645
728
  const ExerciseDocumentSchema = z.object({
646
- type: z.literal("ExerciseDocument"),
729
+ type: z.literal(AST_TYPES.EXERCISE_DOCUMENT),
647
730
  question: TextBlockSchema.optional(),
648
731
  lines: z.array(LineBlockSchema),
649
732
  tools: z.array(ToolNodeSchema).optional(),
@@ -651,7 +734,7 @@ const ExerciseDocumentSchema = z.object({
651
734
  });
652
735
  function createExerciseDocument(question, lines, validation, tools) {
653
736
  return ExerciseDocumentSchema.parse({
654
- type: "ExerciseDocument",
737
+ type: AST_TYPES.EXERCISE_DOCUMENT,
655
738
  question,
656
739
  lines,
657
740
  tools,
@@ -771,7 +854,7 @@ class NaturalDSLVisitor extends BaseCstVisitor {
771
854
  if (params.start !== void 0 && params.end !== void 0) {
772
855
  highlight = [params.start, params.end];
773
856
  }
774
- return createNumberText(value, highlight);
857
+ return createHighlightedText(value, highlight);
775
858
  }
776
859
  inlineInput(ctx) {
777
860
  const params = this.visit(ctx.parameterList);
@@ -801,9 +884,10 @@ class NaturalDSLVisitor extends BaseCstVisitor {
801
884
  return createDragOptions(strings);
802
885
  }
803
886
  numericKeypad(ctx) {
804
- const params = this.visit(ctx.parameterList);
887
+ const params = ctx.parameterList ? this.visit(ctx.parameterList) : {};
888
+ const includeOperators = params.includeOperators === true || params.includeOperators === "true";
805
889
  const includeDecimal = params.includeDecimal === true || params.includeDecimal === "true";
806
- return createNumericKeypad(includeDecimal);
890
+ return createNumericKeypad(includeOperators || void 0, includeDecimal || void 0);
807
891
  }
808
892
  // ═══════════════════════════════════════════════════════════════════════════
809
893
  // Validation
@@ -895,33 +979,33 @@ function toAST(cst) {
895
979
  }
896
980
  const TextItemSchema = z.object({
897
981
  id: z.string(),
898
- type: z.literal("text"),
982
+ type: z.literal(JSON_TYPES.TEXT),
899
983
  value: z.string()
900
984
  });
901
985
  const QuestionTextSchema = z.object({
902
986
  id: z.string(),
903
- type: z.literal("question-text"),
987
+ type: z.literal(JSON_TYPES.QUESTION_TEXT),
904
988
  value: z.string()
905
989
  });
906
990
  const WidgetRefSchema = z.object({
907
991
  id: z.string(),
908
- type: z.literal("widget-ref"),
992
+ type: z.literal(JSON_TYPES.WIDGET_REF),
909
993
  widgetId: z.string()
910
994
  });
911
- const NumberTextSchema = z.object({
995
+ const HighlightedTextSchema = z.object({
912
996
  id: z.string(),
913
- type: z.literal("number-text"),
997
+ type: z.literal(JSON_TYPES.HIGHLIGHTED_TEXT),
914
998
  value: z.string(),
915
999
  highlight: z.tuple([z.number(), z.number()]).optional()
916
1000
  });
917
1001
  const LinebreakSchema = z.object({
918
1002
  id: z.string(),
919
- type: z.literal("linebreak")
1003
+ type: z.literal(JSON_TYPES.LINEBREAK)
920
1004
  });
921
1005
  const OperatorSchema = z.enum(["+", "-", "×", "÷", "=", "<", ">", "?"]);
922
1006
  const OperatorItemSchema = z.object({
923
1007
  id: z.string(),
924
- type: z.literal("operator"),
1008
+ type: z.literal(JSON_TYPES.OPERATOR),
925
1009
  operator: OperatorSchema
926
1010
  });
927
1011
  const LayoutItemSchema = z.lazy(
@@ -929,7 +1013,7 @@ const LayoutItemSchema = z.lazy(
929
1013
  QuestionTextSchema,
930
1014
  TextItemSchema,
931
1015
  WidgetRefSchema,
932
- NumberTextSchema,
1016
+ HighlightedTextSchema,
933
1017
  LinebreakSchema,
934
1018
  GroupItemSchema,
935
1019
  OperatorItemSchema
@@ -937,17 +1021,17 @@ const LayoutItemSchema = z.lazy(
937
1021
  );
938
1022
  const GroupItemSchema = z.object({
939
1023
  id: z.string(),
940
- type: z.literal("group"),
1024
+ type: z.literal(JSON_TYPES.GROUP),
941
1025
  items: z.array(LayoutItemSchema)
942
1026
  });
943
1027
  const StringInputJSONSchema = z.object({
944
- type: z.literal("inline-input"),
945
- inputType: z.literal("string"),
1028
+ type: z.literal(JSON_TYPES.INLINE_INPUT),
1029
+ inputType: z.literal(INPUT_TYPES.STRING),
946
1030
  length: z.number().int().positive()
947
1031
  });
948
1032
  const NumericInputJSONSchema = z.object({
949
- type: z.literal("inline-input"),
950
- inputType: z.literal("numeric"),
1033
+ type: z.literal(JSON_TYPES.INLINE_INPUT),
1034
+ inputType: z.literal(INPUT_TYPES.NUMERIC),
951
1035
  digits: z.number().int().positive()
952
1036
  });
953
1037
  const InlineInputJSONSchema = z.discriminatedUnion("inputType", [
@@ -955,12 +1039,13 @@ const InlineInputJSONSchema = z.discriminatedUnion("inputType", [
955
1039
  NumericInputJSONSchema
956
1040
  ]);
957
1041
  const DragOptionsJSONSchema = z.object({
958
- type: z.literal("drag-options"),
1042
+ type: z.literal(JSON_TYPES.DRAG_OPTIONS),
959
1043
  options: z.array(z.string())
960
1044
  });
961
1045
  const NumericKeypadJSONSchema = z.object({
962
- type: z.literal("numeric-keypad"),
963
- includeDecimal: z.boolean()
1046
+ type: z.literal(JSON_TYPES.NUMERIC_KEYPAD),
1047
+ includeOperators: z.boolean().optional(),
1048
+ includeDecimal: z.boolean().optional()
964
1049
  });
965
1050
  const WidgetJSONSchema = z.union([
966
1051
  InlineInputJSONSchema,
@@ -968,7 +1053,7 @@ const WidgetJSONSchema = z.union([
968
1053
  NumericKeypadJSONSchema
969
1054
  ]);
970
1055
  const ValidationJSONSchema = z.object({
971
- mode: z.enum(["simple", "expression"]),
1056
+ mode: z.enum([VALIDATION_MODES.SIMPLE, VALIDATION_MODES.EXPRESSION]),
972
1057
  answers: z.record(z.union([z.string(), z.number()])).optional(),
973
1058
  expression: z.string().optional(),
974
1059
  variables: z.array(z.string()).optional()
@@ -1000,34 +1085,34 @@ function validateWidgetJSONSafe(data) {
1000
1085
  return WidgetJSONSchema.safeParse(data);
1001
1086
  }
1002
1087
  function isQuestionText(item) {
1003
- return item.type === "question-text";
1088
+ return item.type === JSON_TYPES.QUESTION_TEXT;
1004
1089
  }
1005
- function isTextItem(item) {
1006
- return item.type === "text";
1090
+ function isTextItem$1(item) {
1091
+ return item.type === JSON_TYPES.TEXT;
1007
1092
  }
1008
1093
  function isWidgetRef(item) {
1009
- return item.type === "widget-ref";
1094
+ return item.type === JSON_TYPES.WIDGET_REF;
1010
1095
  }
1011
- function isNumberText(item) {
1012
- return item.type === "number-text";
1096
+ function isHighlightedText(item) {
1097
+ return item.type === JSON_TYPES.HIGHLIGHTED_TEXT;
1013
1098
  }
1014
1099
  function isLinebreak(item) {
1015
- return item.type === "linebreak";
1100
+ return item.type === JSON_TYPES.LINEBREAK;
1016
1101
  }
1017
1102
  function isGroupItem(item) {
1018
- return item.type === "group";
1103
+ return item.type === JSON_TYPES.GROUP;
1019
1104
  }
1020
1105
  function isOperatorItem(item) {
1021
- return item.type === "operator";
1106
+ return item.type === JSON_TYPES.OPERATOR;
1022
1107
  }
1023
1108
  function isInlineInputJSON(widget) {
1024
- return widget.type === "inline-input";
1109
+ return widget.type === JSON_TYPES.INLINE_INPUT;
1025
1110
  }
1026
1111
  function isDragOptionsJSON(widget) {
1027
- return widget.type === "drag-options";
1112
+ return widget.type === JSON_TYPES.DRAG_OPTIONS;
1028
1113
  }
1029
1114
  function isNumericKeypadJSON(widget) {
1030
- return widget.type === "numeric-keypad";
1115
+ return widget.type === JSON_TYPES.NUMERIC_KEYPAD;
1031
1116
  }
1032
1117
  class LayoutIdGenerator {
1033
1118
  constructor() {
@@ -1045,8 +1130,8 @@ function toJSON(ast) {
1045
1130
  const layout = [];
1046
1131
  if (ast.question) {
1047
1132
  layout.push({
1048
- id: idGenerator.generate("question-text"),
1049
- type: "question-text",
1133
+ id: idGenerator.generate(JSON_TYPES.QUESTION_TEXT),
1134
+ type: JSON_TYPES.QUESTION_TEXT,
1050
1135
  value: ast.question.value
1051
1136
  });
1052
1137
  }
@@ -1058,17 +1143,17 @@ function toJSON(ast) {
1058
1143
  const answers = {};
1059
1144
  for (const line of ast.lines) {
1060
1145
  for (const inline of line.inlines) {
1061
- if (inline.type === "InlineInput") {
1062
- if (inline.inputType === "string") {
1146
+ if (inline.type === AST_TYPES.INLINE_INPUT) {
1147
+ if (inline.inputType === INPUT_TYPES.STRING) {
1063
1148
  widgets[inline.name] = {
1064
- type: "inline-input",
1065
- inputType: "string",
1149
+ type: JSON_TYPES.INLINE_INPUT,
1150
+ inputType: INPUT_TYPES.STRING,
1066
1151
  length: inline.length
1067
1152
  };
1068
1153
  } else {
1069
1154
  widgets[inline.name] = {
1070
- type: "inline-input",
1071
- inputType: "numeric",
1155
+ type: JSON_TYPES.INLINE_INPUT,
1156
+ inputType: INPUT_TYPES.NUMERIC,
1072
1157
  digits: inline.digits
1073
1158
  };
1074
1159
  }
@@ -1080,14 +1165,15 @@ function toJSON(ast) {
1080
1165
  }
1081
1166
  if (ast.tools) {
1082
1167
  for (const tool of ast.tools) {
1083
- if (tool.type === "DragOptions") {
1168
+ if (tool.type === AST_TYPES.DRAG_OPTIONS) {
1084
1169
  widgets["_drag_options"] = {
1085
- type: "drag-options",
1170
+ type: JSON_TYPES.DRAG_OPTIONS,
1086
1171
  options: tool.options
1087
1172
  };
1088
- } else if (tool.type === "NumericKeypad") {
1173
+ } else if (tool.type === AST_TYPES.NUMERIC_KEYPAD) {
1089
1174
  widgets["_numeric_keypad"] = {
1090
- type: "numeric-keypad",
1175
+ type: JSON_TYPES.NUMERIC_KEYPAD,
1176
+ includeOperators: tool.includeOperators,
1091
1177
  includeDecimal: tool.includeDecimal
1092
1178
  };
1093
1179
  }
@@ -1108,35 +1194,44 @@ function transformLine(line, idGenerator) {
1108
1194
  items.push(transformInline(inline, idGenerator));
1109
1195
  }
1110
1196
  items.push({
1111
- id: idGenerator.generate("linebreak"),
1112
- type: "linebreak"
1197
+ id: idGenerator.generate(JSON_TYPES.LINEBREAK),
1198
+ type: JSON_TYPES.LINEBREAK
1113
1199
  });
1114
1200
  return items;
1115
1201
  }
1116
1202
  function transformInline(inline, idGenerator) {
1117
1203
  switch (inline.type) {
1118
- case "FreeText":
1204
+ case AST_TYPES.FREE_TEXT:
1205
+ const trimmed = inline.value.trim();
1206
+ const operators = ["+", "-", "×", "÷", "=", "<", ">", "?"];
1207
+ if (operators.includes(trimmed) && inline.value.trim() === inline.value) {
1208
+ return {
1209
+ id: idGenerator.generate("operator"),
1210
+ type: "operator",
1211
+ operator: trimmed
1212
+ };
1213
+ }
1119
1214
  return {
1120
- id: idGenerator.generate("text"),
1121
- type: "text",
1215
+ id: idGenerator.generate(JSON_TYPES.TEXT),
1216
+ type: JSON_TYPES.TEXT,
1122
1217
  value: inline.value
1123
1218
  };
1124
- case "Space":
1219
+ case AST_TYPES.SPACE:
1125
1220
  return {
1126
- id: idGenerator.generate("space"),
1127
- type: "space"
1221
+ id: idGenerator.generate(JSON_TYPES.SPACE),
1222
+ type: JSON_TYPES.SPACE
1128
1223
  };
1129
- case "NumberText":
1224
+ case AST_TYPES.HIGHLIGHTED_TEXT:
1130
1225
  return {
1131
- id: idGenerator.generate("number-text"),
1132
- type: "number-text",
1226
+ id: idGenerator.generate(JSON_TYPES.HIGHLIGHTED_TEXT),
1227
+ type: JSON_TYPES.HIGHLIGHTED_TEXT,
1133
1228
  value: inline.value,
1134
1229
  highlight: inline.highlight
1135
1230
  };
1136
- case "InlineInput":
1231
+ case AST_TYPES.INLINE_INPUT:
1137
1232
  return {
1138
- id: idGenerator.generate("widget-ref"),
1139
- type: "widget-ref",
1233
+ id: idGenerator.generate(JSON_TYPES.WIDGET_REF),
1234
+ type: JSON_TYPES.WIDGET_REF,
1140
1235
  widgetId: inline.name
1141
1236
  };
1142
1237
  default:
@@ -1144,16 +1239,16 @@ function transformInline(inline, idGenerator) {
1144
1239
  }
1145
1240
  }
1146
1241
  function transformValidation(validation, answers, widgets) {
1147
- if (validation.type === "SimpleValidation") {
1242
+ if (validation.type === AST_TYPES.SIMPLE_VALIDATION) {
1148
1243
  return {
1149
- mode: "simple",
1244
+ mode: VALIDATION_MODES.SIMPLE,
1150
1245
  answers
1151
1246
  };
1152
- } else if (validation.type === "ExpressionValidation") {
1247
+ } else if (validation.type === AST_TYPES.EXPRESSION_VALIDATION) {
1153
1248
  const validVariableNames = Object.keys(widgets);
1154
1249
  const variables = extractVariables$1(validation.expression, validVariableNames);
1155
1250
  return {
1156
- mode: "expression",
1251
+ mode: VALIDATION_MODES.EXPRESSION,
1157
1252
  expression: validation.expression,
1158
1253
  variables
1159
1254
  };
@@ -1479,9 +1574,9 @@ function findValidCombination(expression, ranges) {
1479
1574
  }
1480
1575
  function validate(exercise, userInputs) {
1481
1576
  switch (exercise.validation.mode) {
1482
- case "simple":
1577
+ case VALIDATION_MODES.SIMPLE:
1483
1578
  return validateSimple(exercise, userInputs);
1484
- case "expression":
1579
+ case VALIDATION_MODES.EXPRESSION:
1485
1580
  return validateExpression(exercise, userInputs);
1486
1581
  default:
1487
1582
  return {
@@ -1493,13 +1588,13 @@ function validate(exercise, userInputs) {
1493
1588
  }
1494
1589
  function getMissingInputs(exercise, userInputs) {
1495
1590
  const missing = [];
1496
- if (exercise.validation.mode === "simple" && exercise.validation.answers) {
1591
+ if (exercise.validation.mode === VALIDATION_MODES.SIMPLE && exercise.validation.answers) {
1497
1592
  for (const widgetId of Object.keys(exercise.validation.answers)) {
1498
1593
  if (!(widgetId in userInputs) || userInputs[widgetId] === "") {
1499
1594
  missing.push(widgetId);
1500
1595
  }
1501
1596
  }
1502
- } else if (exercise.validation.mode === "expression" && exercise.validation.variables) {
1597
+ } else if (exercise.validation.mode === VALIDATION_MODES.EXPRESSION && exercise.validation.variables) {
1503
1598
  for (const varName of exercise.validation.variables) {
1504
1599
  if (!(varName in userInputs) || userInputs[varName] === "") {
1505
1600
  missing.push(varName);
@@ -1515,7 +1610,7 @@ function validateInputFormats(exercise, userInputs) {
1515
1610
  if (value === void 0 || value === "") {
1516
1611
  continue;
1517
1612
  }
1518
- if (widget.type === "inline-input" && widget.inputType === "numeric") {
1613
+ if (widget.type === JSON_TYPES.INLINE_INPUT && widget.inputType === INPUT_TYPES.NUMERIC) {
1519
1614
  const numValue = typeof value === "number" ? value : parseFloat(String(value));
1520
1615
  if (!Number.isFinite(numValue)) {
1521
1616
  errors[widgetId] = "Must be a valid number";
@@ -5545,7 +5640,8 @@ const InlineInput = ({
5545
5640
  inputType,
5546
5641
  length,
5547
5642
  digits,
5548
- onChange
5643
+ onChange,
5644
+ onFocus
5549
5645
  }) => {
5550
5646
  const handleChange = useCallback((newValue, fromDrop = false) => {
5551
5647
  onChange(widgetId, newValue, fromDrop);
@@ -5586,6 +5682,7 @@ const InlineInput = ({
5586
5682
  className: "inline-input",
5587
5683
  value,
5588
5684
  onChange: (e) => handleChange(e.target.value, false),
5685
+ onFocus: () => onFocus?.(widgetId),
5589
5686
  maxLength: expectedLength,
5590
5687
  style: { width: dynamicWidth }
5591
5688
  }
@@ -5630,6 +5727,7 @@ const DragOptions = ({
5630
5727
  };
5631
5728
  const NumericKeypad = ({
5632
5729
  value,
5730
+ includeOperators = false,
5633
5731
  includeDecimal = false,
5634
5732
  onChange
5635
5733
  }) => {
@@ -5639,32 +5737,23 @@ const NumericKeypad = ({
5639
5737
  const handleBackspace = () => {
5640
5738
  onChange(value.slice(0, -1));
5641
5739
  };
5642
- return /* @__PURE__ */ jsxs("div", { className: "numeric-keypad", children: [
5643
- ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"].map((digit) => /* @__PURE__ */ jsx(
5644
- "button",
5645
- {
5646
- className: "keypad-btn",
5647
- onClick: () => handleDigit(digit),
5648
- children: digit
5649
- },
5650
- digit
5651
- )),
5652
- includeDecimal && /* @__PURE__ */ jsx(
5653
- "button",
5654
- {
5655
- className: "keypad-btn",
5656
- onClick: () => handleDigit("."),
5657
- children: "."
5658
- }
5659
- ),
5660
- /* @__PURE__ */ jsx(
5661
- "button",
5662
- {
5663
- className: "keypad-btn",
5664
- onClick: handleBackspace,
5665
- children: "⌫"
5666
- }
5667
- )
5740
+ return /* @__PURE__ */ jsxs("div", { className: `numeric-keypad ${includeOperators ? "numeric-keypad-with-operators" : ""}`, children: [
5741
+ /* @__PURE__ */ jsx("button", { className: "keypad-btn keypad-btn-digit", onClick: () => handleDigit("1"), children: "1" }),
5742
+ /* @__PURE__ */ jsx("button", { className: "keypad-btn keypad-btn-digit", onClick: () => handleDigit("2"), children: "2" }),
5743
+ /* @__PURE__ */ jsx("button", { className: "keypad-btn keypad-btn-digit", onClick: () => handleDigit("3"), children: "3" }),
5744
+ includeOperators && /* @__PURE__ */ jsx("button", { className: "keypad-btn keypad-btn-operator", onClick: () => handleDigit("+"), children: "+" }),
5745
+ /* @__PURE__ */ jsx("button", { className: "keypad-btn keypad-btn-digit", onClick: () => handleDigit("4"), children: "4" }),
5746
+ /* @__PURE__ */ jsx("button", { className: "keypad-btn keypad-btn-digit", onClick: () => handleDigit("5"), children: "5" }),
5747
+ /* @__PURE__ */ jsx("button", { className: "keypad-btn keypad-btn-digit", onClick: () => handleDigit("6"), children: "6" }),
5748
+ includeOperators && /* @__PURE__ */ jsx("button", { className: "keypad-btn keypad-btn-operator", onClick: () => handleDigit("-"), children: "-" }),
5749
+ /* @__PURE__ */ jsx("button", { className: "keypad-btn keypad-btn-digit", onClick: () => handleDigit("7"), children: "7" }),
5750
+ /* @__PURE__ */ jsx("button", { className: "keypad-btn keypad-btn-digit", onClick: () => handleDigit("8"), children: "8" }),
5751
+ /* @__PURE__ */ jsx("button", { className: "keypad-btn keypad-btn-digit", onClick: () => handleDigit("9"), children: "9" }),
5752
+ includeOperators && /* @__PURE__ */ jsx("button", { className: "keypad-btn keypad-btn-operator", onClick: () => handleDigit("×"), children: "×" }),
5753
+ includeDecimal ? /* @__PURE__ */ jsx("button", { className: "keypad-btn keypad-btn-decimal", onClick: () => handleDigit(","), children: "," }) : /* @__PURE__ */ jsx("div", {}),
5754
+ /* @__PURE__ */ jsx("button", { className: "keypad-btn keypad-btn-digit", onClick: () => handleDigit("0"), children: "0" }),
5755
+ /* @__PURE__ */ jsx("button", { className: "keypad-btn keypad-btn-backspace", onClick: handleBackspace, children: "⌫" }),
5756
+ includeOperators && /* @__PURE__ */ jsx("button", { className: "keypad-btn keypad-btn-operator", onClick: () => handleDigit("÷"), children: "÷" })
5668
5757
  ] });
5669
5758
  };
5670
5759
  function isInlineInputWidget(widget) {
@@ -5681,11 +5770,10 @@ const WidgetRenderer = ({
5681
5770
  widgetId,
5682
5771
  value,
5683
5772
  isDropped = false,
5684
- onChange
5773
+ onChange,
5774
+ onInputFocus,
5775
+ onKeypadChange
5685
5776
  }) => {
5686
- const handleChange = useCallback((newValue, fromDrop = false) => {
5687
- onChange(widgetId, newValue, fromDrop);
5688
- }, [onChange, widgetId]);
5689
5777
  if (isInlineInputWidget(widget)) {
5690
5778
  return /* @__PURE__ */ jsx(
5691
5779
  InlineInput,
@@ -5696,7 +5784,8 @@ const WidgetRenderer = ({
5696
5784
  inputType: widget.inputType,
5697
5785
  length: widget.inputType === "string" ? widget.length : void 0,
5698
5786
  digits: widget.inputType === "numeric" ? widget.digits : void 0,
5699
- onChange
5787
+ onChange,
5788
+ onFocus: onInputFocus
5700
5789
  }
5701
5790
  );
5702
5791
  }
@@ -5715,41 +5804,102 @@ const WidgetRenderer = ({
5715
5804
  NumericKeypad,
5716
5805
  {
5717
5806
  value,
5807
+ includeOperators: widget.includeOperators,
5718
5808
  includeDecimal: widget.includeDecimal,
5719
- onChange: handleChange
5809
+ onChange: onKeypadChange || (() => {
5810
+ })
5720
5811
  }
5721
5812
  );
5722
5813
  }
5723
5814
  return null;
5724
5815
  };
5725
- function isArabic$1(text) {
5816
+ function isArabic(text) {
5726
5817
  return /[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\uFB50-\uFDFF\uFE70-\uFEFF]/.test(text);
5727
5818
  }
5819
+ function hasLatinChars(text) {
5820
+ return /[A-Za-z]/.test(text);
5821
+ }
5822
+ function itemContainsArabic(item) {
5823
+ if (item.type === JSON_TYPES.TEXT && "value" in item) {
5824
+ return isArabic(item.value);
5825
+ }
5826
+ if (item.type === JSON_TYPES.HIGHLIGHTED_TEXT && "value" in item) {
5827
+ return isArabic(item.value);
5828
+ }
5829
+ if (item.type === JSON_TYPES.GROUP && "items" in item) {
5830
+ return item.items.some((subItem) => itemContainsArabic(subItem));
5831
+ }
5832
+ return false;
5833
+ }
5834
+ function itemContainsLatin(item) {
5835
+ if (item.type === JSON_TYPES.TEXT && "value" in item) {
5836
+ return hasLatinChars(item.value);
5837
+ }
5838
+ if (item.type === JSON_TYPES.HIGHLIGHTED_TEXT && "value" in item) {
5839
+ return hasLatinChars(item.value);
5840
+ }
5841
+ if (item.type === JSON_TYPES.GROUP && "items" in item) {
5842
+ return item.items.some((subItem) => itemContainsLatin(subItem));
5843
+ }
5844
+ return false;
5845
+ }
5846
+ function isTextItem(item) {
5847
+ return item.type === JSON_TYPES.TEXT || item.type === JSON_TYPES.HIGHLIGHTED_TEXT || item.type === JSON_TYPES.OPERATOR || item.type === JSON_TYPES.SPACE || item.type === JSON_TYPES.GROUP;
5848
+ }
5849
+ function groupLineItems(items) {
5850
+ const groups = [];
5851
+ let currentTextGroup = [];
5852
+ for (const item of items) {
5853
+ if (isTextItem(item)) {
5854
+ currentTextGroup.push(item);
5855
+ } else {
5856
+ if (currentTextGroup.length > 0) {
5857
+ const hasArabic = currentTextGroup.some(itemContainsArabic);
5858
+ const hasLatin = currentTextGroup.some(itemContainsLatin);
5859
+ const shouldApplyRTL = hasArabic && !hasLatin;
5860
+ groups.push({ items: currentTextGroup, isTextGroup: true, hasArabic, hasLatin, shouldApplyRTL });
5861
+ currentTextGroup = [];
5862
+ }
5863
+ groups.push({ items: [item], isTextGroup: false, hasArabic: false, hasLatin: false, shouldApplyRTL: false });
5864
+ }
5865
+ }
5866
+ if (currentTextGroup.length > 0) {
5867
+ const hasArabic = currentTextGroup.some(itemContainsArabic);
5868
+ const hasLatin = currentTextGroup.some(itemContainsLatin);
5869
+ const shouldApplyRTL = hasArabic && !hasLatin;
5870
+ groups.push({ items: currentTextGroup, isTextGroup: true, hasArabic, hasLatin, shouldApplyRTL });
5871
+ }
5872
+ return groups;
5873
+ }
5728
5874
  const LayoutRenderer = ({
5729
5875
  item,
5730
5876
  widgets,
5731
5877
  values,
5732
5878
  droppedInputs,
5733
- onChange
5879
+ onChange,
5880
+ onInputFocus
5734
5881
  }) => {
5735
5882
  switch (item.type) {
5736
- case "question-text": {
5737
- const hasArabic = isArabic$1(item.value);
5883
+ case JSON_TYPES.QUESTION_TEXT: {
5884
+ const hasArabic = isArabic(item.value);
5738
5885
  return /* @__PURE__ */ jsx("span", { className: `question-text ${hasArabic ? "question-text-arabic" : ""}`, children: item.value });
5739
5886
  }
5740
- case "text":
5741
- return /* @__PURE__ */ jsx("span", { className: `text-item ${isArabic$1(item.value) ? "text-item-arabic" : "text-item-not-arabic"}`, children: item.value });
5742
- case "number-text":
5887
+ case JSON_TYPES.TEXT:
5888
+ return /* @__PURE__ */ jsx("span", { className: `text-item ${isArabic(item.value) ? "text-item-arabic" : "text-item-not-arabic"}`, children: item.value });
5889
+ case JSON_TYPES.HIGHLIGHTED_TEXT: {
5890
+ const hasArabic = isArabic(item.value);
5891
+ const dirAttr = !hasArabic ? "ltr" : void 0;
5743
5892
  if (item.highlight) {
5744
5893
  const [start, end] = item.highlight;
5745
- return /* @__PURE__ */ jsxs("span", { className: "number-text", children: [
5894
+ return /* @__PURE__ */ jsxs("span", { className: "highlighted-text", dir: dirAttr, children: [
5746
5895
  item.value.slice(0, start),
5747
5896
  /* @__PURE__ */ jsx("span", { className: "highlight", children: item.value.slice(start, end) }),
5748
5897
  item.value.slice(end)
5749
5898
  ] });
5750
5899
  }
5751
- return /* @__PURE__ */ jsx("span", { className: "number-text", children: item.value });
5752
- case "widget-ref": {
5900
+ return /* @__PURE__ */ jsx("span", { className: "highlighted-text", dir: dirAttr, children: item.value });
5901
+ }
5902
+ case JSON_TYPES.WIDGET_REF: {
5753
5903
  const widget = widgets[item.widgetId];
5754
5904
  if (!widget) return null;
5755
5905
  return /* @__PURE__ */ jsx(
@@ -5759,15 +5909,18 @@ const LayoutRenderer = ({
5759
5909
  widgetId: item.widgetId,
5760
5910
  value: values[item.widgetId] || "",
5761
5911
  isDropped: droppedInputs.has(item.widgetId),
5762
- onChange
5912
+ onChange,
5913
+ onInputFocus
5763
5914
  }
5764
5915
  );
5765
5916
  }
5766
- case "linebreak":
5917
+ case JSON_TYPES.SPACE:
5918
+ return /* @__PURE__ */ jsx("span", { className: "space", children: " " });
5919
+ case JSON_TYPES.LINEBREAK:
5767
5920
  return null;
5768
- case "operator":
5921
+ case JSON_TYPES.OPERATOR:
5769
5922
  return /* @__PURE__ */ jsx("span", { className: "operator", children: item.operator });
5770
- case "group":
5923
+ case JSON_TYPES.GROUP:
5771
5924
  return /* @__PURE__ */ jsx("span", { className: "group", children: item.items.map((subItem) => /* @__PURE__ */ jsx(
5772
5925
  LayoutRenderer,
5773
5926
  {
@@ -5775,7 +5928,8 @@ const LayoutRenderer = ({
5775
5928
  widgets,
5776
5929
  values,
5777
5930
  droppedInputs,
5778
- onChange
5931
+ onChange,
5932
+ onInputFocus
5779
5933
  },
5780
5934
  subItem.id
5781
5935
  )) });
@@ -5792,21 +5946,6 @@ const DEFAULT_RULER_TOOL = {
5792
5946
  width: 300,
5793
5947
  allowRotate: true
5794
5948
  };
5795
- function isArabic(text) {
5796
- return /[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\uFB50-\uFDFF\uFE70-\uFEFF]/.test(text);
5797
- }
5798
- function itemContainsArabic(item) {
5799
- if (item.type === "text" && "value" in item) {
5800
- return isArabic(item.value);
5801
- }
5802
- if (item.type === "number-text" && "value" in item) {
5803
- return isArabic(item.value);
5804
- }
5805
- if (item.type === "group" && "items" in item) {
5806
- return item.items.some((subItem) => itemContainsArabic(subItem));
5807
- }
5808
- return false;
5809
- }
5810
5949
  const ExerciseRenderer = forwardRef(({
5811
5950
  json,
5812
5951
  onSubmit,
@@ -5816,6 +5955,7 @@ const ExerciseRenderer = forwardRef(({
5816
5955
  }, ref) => {
5817
5956
  const [values, setValues] = useState({});
5818
5957
  const [droppedInputs, setDroppedInputs] = useState(/* @__PURE__ */ new Set());
5958
+ const [focusedInputId, setFocusedInputId] = useState(null);
5819
5959
  useImperativeHandle(ref, () => ({
5820
5960
  submit: () => {
5821
5961
  const result = validate(json, values);
@@ -5826,6 +5966,7 @@ const ExerciseRenderer = forwardRef(({
5826
5966
  reset: () => {
5827
5967
  setValues({});
5828
5968
  setDroppedInputs(/* @__PURE__ */ new Set());
5969
+ setFocusedInputId(null);
5829
5970
  },
5830
5971
  focusFirstInput: () => {
5831
5972
  const container = document.querySelector(".math-exercise-renderer");
@@ -5856,10 +5997,20 @@ const ExerciseRenderer = forwardRef(({
5856
5997
  const groups = [];
5857
5998
  let currentGroup = [];
5858
5999
  for (const item of json.layout) {
5859
- if (item.type === "linebreak") {
6000
+ if (item.type === JSON_TYPES.QUESTION_TEXT) {
6001
+ groups.push({
6002
+ items: [item],
6003
+ hasArabic: itemContainsArabic(item),
6004
+ subGroups: []
6005
+ // Question text doesn't need sub-grouping
6006
+ });
6007
+ continue;
6008
+ }
6009
+ if (item.type === JSON_TYPES.LINEBREAK) {
5860
6010
  if (currentGroup.length > 0) {
5861
6011
  const hasArabic = currentGroup.some((item2) => itemContainsArabic(item2));
5862
- groups.push({ items: currentGroup, hasArabic });
6012
+ const subGroups = groupLineItems(currentGroup);
6013
+ groups.push({ items: currentGroup, hasArabic, subGroups });
5863
6014
  currentGroup = [];
5864
6015
  }
5865
6016
  } else {
@@ -5868,7 +6019,8 @@ const ExerciseRenderer = forwardRef(({
5868
6019
  }
5869
6020
  if (currentGroup.length > 0) {
5870
6021
  const hasArabic = currentGroup.some((item) => itemContainsArabic(item));
5871
- groups.push({ items: currentGroup, hasArabic });
6022
+ const subGroups = groupLineItems(currentGroup);
6023
+ groups.push({ items: currentGroup, hasArabic, subGroups });
5872
6024
  }
5873
6025
  return groups;
5874
6026
  }, [json.layout]);
@@ -5889,17 +6041,52 @@ const ExerciseRenderer = forwardRef(({
5889
6041
  "div",
5890
6042
  {
5891
6043
  className: `exercise-line ${group.hasArabic ? "exercise-line-arabic" : ""}`,
5892
- children: group.items.map((item) => /* @__PURE__ */ jsx(
5893
- LayoutRenderer,
5894
- {
5895
- item,
5896
- widgets: json.widgets,
5897
- values,
5898
- droppedInputs,
5899
- onChange: handleChange
5900
- },
5901
- item.id
5902
- ))
6044
+ children: group.subGroups.length > 0 ? (
6045
+ // Render sub-groups for regular lines
6046
+ group.subGroups.map((subGroup, subIdx) => subGroup.isTextGroup ? (
6047
+ // Text group - only apply RTL for pure Arabic (no mixed languages)
6048
+ /* @__PURE__ */ jsx("span", { className: "text-group", dir: subGroup.shouldApplyRTL ? "rtl" : void 0, children: subGroup.items.map((item) => /* @__PURE__ */ jsx(
6049
+ LayoutRenderer,
6050
+ {
6051
+ item,
6052
+ widgets: json.widgets,
6053
+ values,
6054
+ droppedInputs,
6055
+ onChange: handleChange,
6056
+ onInputFocus: setFocusedInputId
6057
+ },
6058
+ item.id
6059
+ )) }, subIdx)
6060
+ ) : (
6061
+ // Widget group - no dir wrapper, render directly
6062
+ subGroup.items.map((item) => /* @__PURE__ */ jsx(
6063
+ LayoutRenderer,
6064
+ {
6065
+ item,
6066
+ widgets: json.widgets,
6067
+ values,
6068
+ droppedInputs,
6069
+ onChange: handleChange,
6070
+ onInputFocus: setFocusedInputId
6071
+ },
6072
+ item.id
6073
+ ))
6074
+ ))
6075
+ ) : (
6076
+ // For QUESTION_TEXT items (no sub-groups)
6077
+ group.items.map((item) => /* @__PURE__ */ jsx(
6078
+ LayoutRenderer,
6079
+ {
6080
+ item,
6081
+ widgets: json.widgets,
6082
+ values,
6083
+ droppedInputs,
6084
+ onChange: handleChange,
6085
+ onInputFocus: setFocusedInputId
6086
+ },
6087
+ item.id
6088
+ ))
6089
+ )
5903
6090
  },
5904
6091
  groupIdx
5905
6092
  )) }),
@@ -5908,9 +6095,13 @@ const ExerciseRenderer = forwardRef(({
5908
6095
  {
5909
6096
  widget: numericKeypadWidget,
5910
6097
  widgetId: "_numeric_keypad",
5911
- value: "",
5912
- onChange: (v, w, f) => {
5913
- console.log(v, w, f, values);
6098
+ value: focusedInputId ? values[focusedInputId] || "" : "",
6099
+ onChange: () => {
6100
+ },
6101
+ onKeypadChange: (newValue) => {
6102
+ if (focusedInputId) {
6103
+ handleChange(focusedInputId, newValue, false);
6104
+ }
5914
6105
  }
5915
6106
  }
5916
6107
  ) })
@@ -5981,6 +6172,7 @@ function compileWithAST(dsl) {
5981
6172
  return { ast, json };
5982
6173
  }
5983
6174
  export {
6175
+ AST_TYPES,
5984
6176
  DragOptionsJSONSchema,
5985
6177
  DragOptionsToolSchema,
5986
6178
  EngineError,
@@ -5991,16 +6183,17 @@ export {
5991
6183
  ExpressionValidationSchema,
5992
6184
  FreeTextInlineSchema,
5993
6185
  GroupItemSchema,
6186
+ HighlightedTextInlineSchema,
6187
+ HighlightedTextSchema,
5994
6188
  InlineInputJSONSchema,
5995
6189
  InlineInputWidgetSchema,
5996
6190
  InlineNodeSchema,
6191
+ JSON_TYPES,
5997
6192
  LayoutItemSchema,
5998
6193
  LayoutRenderer,
5999
6194
  LexerError,
6000
6195
  LineBlockSchema,
6001
6196
  LinebreakSchema,
6002
- NumberTextInlineSchema,
6003
- NumberTextSchema,
6004
6197
  NumericKeypadJSONSchema,
6005
6198
  NumericKeypadToolSchema,
6006
6199
  OperatorItemSchema,
@@ -6025,8 +6218,8 @@ export {
6025
6218
  createExerciseDocument,
6026
6219
  createExpressionValidation,
6027
6220
  createFreeText,
6221
+ createHighlightedText,
6028
6222
  createLineBlock,
6029
- createNumberText,
6030
6223
  createNumericInput,
6031
6224
  createNumericKeypad,
6032
6225
  createSimpleValidation,
@@ -6043,12 +6236,12 @@ export {
6043
6236
  isExpressionValidation,
6044
6237
  isFreeText,
6045
6238
  isGroupItem,
6239
+ isHighlightedText,
6046
6240
  isInlineInput,
6047
6241
  isInlineInputJSON,
6048
6242
  isInlineNode,
6049
6243
  isLineBlock,
6050
6244
  isLinebreak,
6051
- isNumberText,
6052
6245
  isNumericKeypad,
6053
6246
  isNumericKeypadJSON,
6054
6247
  isOperatorItem,
@@ -6056,7 +6249,7 @@ export {
6056
6249
  isSimpleValidation,
6057
6250
  isSpace,
6058
6251
  isTextBlock,
6059
- isTextItem,
6252
+ isTextItem$1 as isTextItem,
6060
6253
  isToolNode,
6061
6254
  isValidExpression,
6062
6255
  isValidationNode,