@mathscan/math-exercise-engine 1.0.2 → 1.0.4

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/style.css CHANGED
@@ -1,17 +1,45 @@
1
- /* ========================================
2
- MATH EXERCISE ENGINE - DEFAULT STYLES
3
- ========================================
4
- Minimal, responsive styles for math-exercise-engine
5
- All styles use scoped CSS custom properties for easy theming
6
- ======================================== */
7
-
8
- /* ========================================
9
- MAIN CONTAINER
10
- ======================================== */
1
+ /**
2
+ * Math Exercise Engine - Default Styles
3
+ *
4
+ * This file is maintained for backward compatibility.
5
+ * All styles have been reorganized into modular CSS files.
6
+ *
7
+ * New structure: src/renderer/styles/
8
+ * - variables.css - CSS custom properties
9
+ * - layout.css - Layout & structure
10
+ * - text.css - Text content & bidi
11
+ * - inputs.css - Input fields
12
+ * - widgets.css - Drag options & keypad
13
+ * - buttons.css - Submit button
14
+ * - responsive.css - Media queries
15
+ */
16
+
17
+ /**
18
+ * Math Exercise Engine - Main Stylesheet
19
+ *
20
+ * Modular CSS architecture for maintainability and clarity.
21
+ *
22
+ * Import order matters:
23
+ * 1. Variables (must be first - used by all other modules)
24
+ * 2. Core layout and structure
25
+ * 3. Content styles (text, inputs, widgets)
26
+ * 4. Responsive overrides (must be last to take precedence)
27
+ */
28
+
29
+ /* CSS Custom Properties - must be imported first */
30
+
31
+ /**
32
+ * CSS Custom Properties (Variables)
33
+ *
34
+ * All theming variables for the math exercise renderer.
35
+ * These can be overridden by consumers for custom themes.
36
+ */
11
37
 
12
- /* Scoped CSS Custom Properties for Theming */
13
38
  .math-exercise-renderer {
14
- /* Colors */
39
+ /* ========================================
40
+ COLOR VARIABLES
41
+ ======================================== */
42
+
15
43
  --exercise-bg: white;
16
44
  --exercise-text: #333;
17
45
  --exercise-text-light: #555;
@@ -20,27 +48,73 @@
20
48
  --exercise-highlight: #ffd700;
21
49
  --exercise-border: #ddd;
22
50
  --exercise-border-focus: var(--exercise-primary);
51
+ --exercise-highlight-color: #aef59d;
52
+
53
+ /* ========================================
54
+ SPACING VARIABLES
55
+ ======================================== */
23
56
 
24
- /* Spacing */
57
+ /** Main container padding - responsive with clamp() */
25
58
  --exercise-padding: clamp(1rem, 3vw, 2rem);
59
+
60
+ /** Bottom margin for major sections */
26
61
  --exercise-margin-bottom: 1.5rem;
62
+
63
+ /** Gap between exercise lines (0.25rem = 4px) */
27
64
  --exercise-line-gap: 0.25rem;
65
+
66
+ /** Margin between inline elements (0.25rem = 4px) */
28
67
  --exercise-element-margin: 0.25rem;
29
68
 
30
- /* Typography */
31
- --exercise-font-size: clamp(0.875rem, 2vw, 1.25rem);
69
+ /* ========================================
70
+ TYPOGRAPHY VARIABLES
71
+ ======================================== */
72
+
73
+ /** Base font size - responsive (14px to 20px) */
74
+ --exercise-font-size: clamp(0.875rem, 2vw, 1.1rem);
75
+
76
+ /** Larger font size for inputs/buttons (12px to 16px) */
32
77
  --exercise-font-size-large: clamp(1rem, 1.5vw, 0.75rem);
78
+
79
+ /** Normal font weight */
33
80
  --exercise-font-weight: 500;
81
+
82
+ /** Bold font weight */
34
83
  --exercise-font-weight-bold: 600;
35
84
 
36
- /* Layout */
85
+ /* ========================================
86
+ LAYOUT VARIABLES
87
+ ======================================== */
88
+
89
+ /** Input field width - responsive (40px to 64px) */
37
90
  --exercise-input-width: clamp(2.5rem, 5vw, 4rem);
91
+
92
+ /** Input padding (6px 10px) */
38
93
  --exercise-input-padding: 0.375rem 0.625rem;
94
+
95
+ /** Button width - responsive (48px to 64px) */
39
96
  --exercise-button-width: clamp(3rem, 6vw, 4rem);
97
+
98
+ /** Border radius for rounded corners */
40
99
  --exercise-border-radius: 0.25rem;
100
+
101
+ /** Border width for inputs and buttons */
41
102
  --exercise-border-width: 2px;
103
+ }
104
+
105
+ /* Layout & Structure */
106
+
107
+ /**
108
+ * Layout & Structure Styles
109
+ *
110
+ * Main container and layout grid styles.
111
+ */
112
+
113
+ /* ========================================
114
+ MAIN CONTAINER
115
+ ======================================== */
42
116
 
43
- /* Component styles */
117
+ .math-exercise-renderer {
44
118
  position: relative;
45
119
  background: var(--exercise-bg);
46
120
  padding: var(--exercise-padding);
@@ -50,18 +124,25 @@
50
124
  }
51
125
 
52
126
  /* ========================================
53
- LAYOUT & STRUCTURE
127
+ LAYOUT CONTAINER
54
128
  ======================================== */
55
129
 
56
130
  .exercise-layout-container {
57
- /* Default: no flex when no keypad */
131
+ /* Default: regular block layout */
58
132
  }
59
133
 
60
134
  .exercise-layout-container.has-keypad {
135
+ /* When keypad is present, use flex layout for side-by-side */
61
136
  display: flex;
137
+ justify-content: space-between;
62
138
  }
63
-
139
+
140
+ /* ========================================
141
+ EXERCISE LAYOUT
142
+ ======================================== */
143
+
64
144
  .exercise-layout {
145
+ justify-content: space-evenly;
65
146
  display: flex;
66
147
  flex-direction: column;
67
148
  gap: var(--exercise-line-gap);
@@ -69,65 +150,126 @@
69
150
  margin-bottom: var(--exercise-margin-bottom);
70
151
  }
71
152
 
153
+ /* ========================================
154
+ EXERCISE LINES
155
+ ======================================== */
156
+
72
157
  .exercise-line {
73
158
  display: flex;
74
159
  flex-wrap: wrap;
75
160
  align-items: center;
76
161
  justify-content: center;
162
+ align-items: baseline;
77
163
  gap: var(--exercise-element-margin);
78
164
  padding-bottom: calc(var(--exercise-line-gap) * 0.5);
79
165
  }
80
166
 
81
167
  .exercise-line-arabic {
82
- /* Arabic text alignment - can be overridden by consuming apps */
83
- justify-content: flex-end;
168
+ /* RTL layout handled by dir="rtl" attribute on text groups */
169
+ /* No additional styling needed */
84
170
  }
85
171
 
86
- .linebreak {
87
- /* Line break element - handled by flex layout */
88
- }
172
+ /* Content Styles */
173
+
174
+ /**
175
+ * Text Content Styles
176
+ *
177
+ * Styles for text items, questions, operators, and bidirectional text.
178
+ */
89
179
 
90
180
  /* ========================================
91
- TEXT CONTENT
181
+ QUESTION TEXT
92
182
  ======================================== */
93
183
 
94
184
  .question-text {
95
185
  display: block;
96
186
  width: 100%;
97
- font-size: calc(var(--exercise-font-size-large) * 1.2);
187
+ font-size: calc(var(--exercise-font-size-large) * 1);
98
188
  font-weight: var(--exercise-font-weight-bold);
99
189
  margin-bottom: var(--exercise-margin-bottom);
100
190
  color: var(--exercise-text);
101
191
  line-height: 1.4;
192
+ text-align: center;
102
193
  }
103
194
 
104
195
  .question-text-arabic {
105
- direction: rtl;
106
- text-align: right;
196
+
107
197
  }
108
198
 
199
+ /* ========================================
200
+ TEXT ITEMS
201
+ ======================================== */
202
+
109
203
  .text-item {
110
- margin: 0 var(--exercise-element-margin);
111
204
  color: var(--exercise-text);
205
+ /* font-size: medium; */
206
+ }
207
+
208
+ .text-group {
209
+ /**
210
+ * Wrapper for text segments with bidirectional content.
211
+ * unicode-bidi: plaintext lets the browser auto-detect direction
212
+ * based on the first strong directional character.
213
+ */
214
+ display: inline;
215
+ unicode-bidi: plaintext;
112
216
  }
113
217
 
114
218
  .text-item-arabic {
115
- text-align: end;
219
+ /* Direction is handled by parent text-group with unicode-bidi: plaintext */
116
220
  }
117
221
 
118
222
  .text-item-not-arabic {
119
- text-align: start;
223
+ /* Direction is handled by parent text-group with unicode-bidi: plaintext */
120
224
  }
121
225
 
122
- .number-text {
123
- font-weight: var(--exercise-font-weight);
124
- margin: 0 var(--exercise-element-margin);
125
- color: var(--exercise-text);
226
+ /* ========================================
227
+ SPACE ELEMENT
228
+ ======================================== */
229
+
230
+ .space {
231
+ /**
232
+ * Minimal width to create visual spacing between inline elements.
233
+ * 0.5px is intentionally small - actual spacing comes from flex gap.
234
+ * This just ensures the element exists in the layout.
235
+ */
236
+ width: 0.5px;
126
237
  }
127
238
 
128
- .number-text .highlight {
129
- color: red;
130
- font-weight: var(--exercise-font-weight-bold);
239
+ /* ========================================
240
+ HIGHLIGHTED TEXT
241
+ ======================================== */
242
+
243
+ .highlighted-text {
244
+ /* margin: 0 var(--exercise-element-margin); */
245
+ color: var(--exercise-text);
246
+ /**
247
+ * unicode-bidi: isolate prevents highlighted text from being affected
248
+ * by parent RTL context, keeping numbers in correct order.
249
+ */
250
+ unicode-bidi: isolate;
251
+ }
252
+
253
+ .highlighted-text .highlight {
254
+ /* background-color: var(--exercise-highlight-color); */
255
+ }
256
+
257
+ mark {
258
+ /* Default background color */
259
+ background-color: #aef59d;
260
+
261
+ /* Add padding for space around the text within the highlight */
262
+ padding: 0.1em 0.4em;
263
+
264
+ /* Apply border-radius for rounded corners */
265
+ border-radius: 0.8em;
266
+
267
+ /* Ensures rounded corners are applied to each line when text wraps */
268
+ box-decoration-break: clone;
269
+ -webkit-box-decoration-break: clone; /* for older Safari/Chrome */
270
+
271
+ /* Optional: add a border */
272
+ border: 1px solid #aef59d;
131
273
  }
132
274
 
133
275
  /* ========================================
@@ -135,10 +277,10 @@
135
277
  ======================================== */
136
278
 
137
279
  .operator {
138
- margin: 0 calc(var(--exercise-element-margin) * 2);
139
- font-weight: var(--exercise-font-weight-bold);
280
+ font-weight: 900;
140
281
  color: var(--exercise-accent);
141
- font-size: var(--exercise-font-size-large);
282
+ /** Operator size: 20px - larger than text for visual emphasis */
283
+ font-size: 20px;
142
284
  }
143
285
 
144
286
  .group {
@@ -146,8 +288,14 @@
146
288
  color: var(--exercise-text-light);
147
289
  }
148
290
 
291
+ /**
292
+ * Input Styles
293
+ *
294
+ * Styles for interactive input fields.
295
+ */
296
+
149
297
  /* ========================================
150
- INTERACTIVE ELEMENTS
298
+ INLINE INPUT
151
299
  ======================================== */
152
300
 
153
301
  .inline-input {
@@ -160,11 +308,18 @@
160
308
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
161
309
  background: var(--exercise-bg);
162
310
  color: var(--exercise-text);
311
+
312
+ /**
313
+ * Always LTR for inputs - even in RTL context, numbers should
314
+ * be entered left-to-right for consistent UX.
315
+ */
316
+ direction: ltr;
163
317
  }
164
318
 
165
319
  .inline-input:focus {
166
320
  outline: none;
167
321
  border-color: var(--exercise-border-focus);
322
+ /** Focus shadow: 3px blur with 10% opacity primary color */
168
323
  box-shadow: 0 0 0 3px rgba(0, 122, 204, 0.1);
169
324
  }
170
325
 
@@ -172,14 +327,18 @@
172
327
  border-color: var(--exercise-border-focus);
173
328
  }
174
329
 
330
+ /**
331
+ * Widget Styles
332
+ *
333
+ * Styles for drag options, numeric keypad, and other interactive widgets.
334
+ */
335
+
175
336
  /* ========================================
176
- WIDGETS & TOOLS
337
+ DRAG OPTIONS
177
338
  ======================================== */
178
339
 
179
- /* Drag Options */
180
340
  .drag-options-container {
181
- /* margin-top: var(--exercise-margin-bottom); */
182
- /* padding: var(--exercise-padding); */
341
+ /* Container for drag options widget */
183
342
  }
184
343
 
185
344
  .drag-options {
@@ -190,6 +349,7 @@
190
349
  }
191
350
 
192
351
  .drag-option {
352
+ /** Padding: 10px vertical, 16px horizontal for comfortable touch targets */
193
353
  padding: 10px 16px;
194
354
  border: var(--exercise-border-width) solid var(--exercise-border);
195
355
  border-radius: var(--exercise-border-radius);
@@ -206,11 +366,11 @@
206
366
 
207
367
  .drag-option:hover {
208
368
  border-color: var(--exercise-primary);
209
- /* background: rgba(0, 122, 204, 0.05); */
210
369
  }
211
370
 
212
371
  .drag-option:active {
213
372
  cursor: grabbing;
373
+ /** Scale down slightly (98%) when dragging for visual feedback */
214
374
  transform: scale(0.98);
215
375
  }
216
376
 
@@ -218,34 +378,45 @@
218
378
  background: var(--exercise-primary);
219
379
  color: white;
220
380
  border-color: var(--exercise-primary);
381
+ /** Shadow: 2px offset, 4px blur, 20% opacity for depth */
221
382
  box-shadow: 0 2px 4px rgba(0, 122, 204, 0.2);
222
383
  }
223
384
 
224
- /* Legacy button styles - for backward compatibility */
385
+ /* Legacy button-style drag options for backward compatibility */
386
+
225
387
  button.drag-option {
226
- /* padding: calc(var(--exercise-input-padding) * 2); */
227
388
  border: none;
228
389
  border-radius: var(--exercise-border-radius);
229
390
  cursor: pointer;
230
391
  font-size: var(--exercise-font-size);
231
392
  color: white;
232
393
  background: var(--exercise-accent);
233
- /* width: var(--exercise-button-width); */
234
394
  font-weight: var(--exercise-font-weight);
235
395
  }
236
396
 
237
- /* Numeric Keypad */
397
+ /* ========================================
398
+ NUMERIC KEYPAD
399
+ ======================================== */
400
+
401
+ .numeric-keypad-container {
402
+ /**
403
+ * Keypad container dimensions:
404
+ * - min-width: 150px ensures keypad is always usable
405
+ * - margins: 5px on each side for spacing
406
+ */
407
+ min-width: 150px;
408
+ margin-right: 5px;
409
+ margin-left: 5px;
410
+ }
411
+
238
412
  .numeric-keypad {
239
413
  display: grid;
240
414
  grid-template-columns: repeat(3, 1fr);
241
- gap: calc(var(--exercise-element-margin) * 1);
242
- /* margin-top: var(--exercise-margin-bottom); */
415
+ gap: var(--exercise-element-margin);
243
416
  }
244
417
 
245
- .numeric-keypad-container {
246
- min-width: 150px;
247
- margin-right: 5px;
248
- margin-left: 5px;
418
+ .numeric-keypad-with-operators {
419
+ grid-template-columns: repeat(4, 1fr);
249
420
  }
250
421
 
251
422
  .keypad-btn {
@@ -266,17 +437,25 @@ button.drag-option {
266
437
 
267
438
  .keypad-btn:hover {
268
439
  border-color: var(--exercise-primary);
440
+ /** Hover background: 5% opacity primary color */
269
441
  background: rgba(0, 122, 204, 0.05);
270
442
  }
271
443
 
272
444
  .keypad-btn:active {
445
+ /** Scale down to 95% when clicked for tactile feedback */
273
446
  transform: scale(0.95);
274
447
  background: var(--exercise-primary);
275
448
  color: white;
276
449
  }
277
450
 
451
+ /**
452
+ * Button & Action Styles
453
+ *
454
+ * Styles for submit buttons and other action controls.
455
+ */
456
+
278
457
  /* ========================================
279
- ACTIONS & CONTROLS
458
+ SUBMIT BUTTON
280
459
  ======================================== */
281
460
 
282
461
  .exercise-submit-btn {
@@ -298,49 +477,69 @@ button.drag-option {
298
477
  }
299
478
 
300
479
  .exercise-submit-btn:active {
480
+ /** Scale down to 98% when clicked for tactile feedback */
301
481
  transform: scale(0.98);
302
482
  }
303
483
 
484
+ /* Responsive Overrides - must be last */
485
+
486
+ /**
487
+ * Responsive Design Styles
488
+ *
489
+ * Media queries for different screen sizes.
490
+ */
491
+
304
492
  /* ========================================
305
- RESPONSIVE DESIGN
493
+ TABLET & SMALLER (max-width: 768px)
306
494
  ======================================== */
307
495
 
308
496
  @media (max-width: 768px) {
309
497
  .math-exercise-renderer {
498
+ /** Reduce padding by 25% on tablets */
310
499
  padding: calc(var(--exercise-padding) * 0.75);
311
500
  }
312
501
 
313
502
  .exercise-line {
503
+ /** Reduce gap between inline elements by 25% */
314
504
  gap: calc(var(--exercise-element-margin) * 0.75);
315
505
  }
316
506
 
317
507
  .drag-options {
508
+ /** Reduce gap between drag options */
318
509
  gap: var(--exercise-element-margin);
319
510
  }
320
511
 
321
512
  .numeric-keypad {
513
+ /** Reduce gap between keypad buttons */
322
514
  gap: var(--exercise-element-margin);
323
515
  }
324
516
  }
325
517
 
518
+ /* ========================================
519
+ MOBILE (max-width: 480px)
520
+ ======================================== */
521
+
326
522
  @media (max-width: 480px) {
327
523
  .question-text {
524
+ /** Smaller question text on mobile */
328
525
  font-size: var(--exercise-font-size-large);
329
526
  }
330
527
 
331
528
  .inline-input {
529
+ /** Reduce input width by 20% on mobile */
332
530
  width: calc(var(--exercise-input-width) * 0.8);
333
531
  }
334
532
 
335
533
  .drag-option {
534
+ /** Reduce drag option width by 20% on mobile */
336
535
  min-width: calc(var(--exercise-button-width) * 0.8);
337
536
  }
338
537
 
339
538
  button.drag-option {
539
+ /** Reduce button drag option width by 20% on mobile */
340
540
  width: calc(var(--exercise-button-width) * 0.8);
341
541
  }
342
542
  }
343
-
344
543
  /* ============================================
345
544
  DND STYLES
346
545
  ============================================ */
@@ -441,8 +640,8 @@ button.drag-option {
441
640
  align-items: center;
442
641
  justify-content: center;
443
642
  border-radius: 4px;
444
- min-width: 50px;
445
- min-height: 40px;
643
+ /* min-width: 50px;
644
+ min-height: 40px; */
446
645
  }
447
646
 
448
647
  .inline-input {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mathscan/math-exercise-engine",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "A DSL parser and validator for math exercises - transforms text-based exercise definitions into structured AST and JSON",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -52,8 +52,8 @@
52
52
  "zod": "^3.22.4"
53
53
  },
54
54
  "peerDependencies": {
55
- "react": "^18.0.0",
56
- "react-dom": "^18.0.0"
55
+ "react": "^18.0.0 || ^19.0.0",
56
+ "react-dom": "^18.0.0 || ^19.0.0"
57
57
  },
58
58
  "peerDependenciesMeta": {
59
59
  "react": {
@@ -64,16 +64,20 @@
64
64
  }
65
65
  },
66
66
  "devDependencies": {
67
+ "@testing-library/jest-dom": "^6.9.1",
68
+ "@testing-library/react": "^16.3.2",
69
+ "@testing-library/user-event": "^14.6.1",
67
70
  "@types/node": "^20.11.0",
68
- "@types/react": "^18.2.0",
69
- "@types/react-dom": "^18.2.0",
71
+ "@types/react": "^19.0.0",
72
+ "@types/react-dom": "^19.0.0",
70
73
  "@typescript-eslint/eslint-plugin": "^6.19.0",
71
74
  "@typescript-eslint/parser": "^6.19.0",
72
75
  "@vitest/coverage-v8": "^4.0.17",
73
76
  "eslint": "^8.56.0",
77
+ "jsdom": "^28.0.0",
74
78
  "prettier": "^3.2.4",
75
- "react": "^18.2.0",
76
- "react-dom": "^18.2.0",
79
+ "react": "^19.0.0",
80
+ "react-dom": "^19.0.0",
77
81
  "ts-node": "^10.9.2",
78
82
  "tsx": "^4.7.0",
79
83
  "typescript": "^5.3.3",