@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/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;
@@ -21,26 +49,71 @@
21
49
  --exercise-border: #ddd;
22
50
  --exercise-border-focus: var(--exercise-primary);
23
51
 
24
- /* Spacing */
52
+ /* ========================================
53
+ SPACING VARIABLES
54
+ ======================================== */
55
+
56
+ /** Main container padding - responsive with clamp() */
25
57
  --exercise-padding: clamp(1rem, 3vw, 2rem);
58
+
59
+ /** Bottom margin for major sections */
26
60
  --exercise-margin-bottom: 1.5rem;
61
+
62
+ /** Gap between exercise lines (0.25rem = 4px) */
27
63
  --exercise-line-gap: 0.25rem;
64
+
65
+ /** Margin between inline elements (0.25rem = 4px) */
28
66
  --exercise-element-margin: 0.25rem;
29
67
 
30
- /* Typography */
68
+ /* ========================================
69
+ TYPOGRAPHY VARIABLES
70
+ ======================================== */
71
+
72
+ /** Base font size - responsive (14px to 20px) */
31
73
  --exercise-font-size: clamp(0.875rem, 2vw, 1.25rem);
74
+
75
+ /** Larger font size for inputs/buttons (12px to 16px) */
32
76
  --exercise-font-size-large: clamp(1rem, 1.5vw, 0.75rem);
77
+
78
+ /** Normal font weight */
33
79
  --exercise-font-weight: 500;
80
+
81
+ /** Bold font weight */
34
82
  --exercise-font-weight-bold: 600;
35
83
 
36
- /* Layout */
84
+ /* ========================================
85
+ LAYOUT VARIABLES
86
+ ======================================== */
87
+
88
+ /** Input field width - responsive (40px to 64px) */
37
89
  --exercise-input-width: clamp(2.5rem, 5vw, 4rem);
90
+
91
+ /** Input padding (6px 10px) */
38
92
  --exercise-input-padding: 0.375rem 0.625rem;
93
+
94
+ /** Button width - responsive (48px to 64px) */
39
95
  --exercise-button-width: clamp(3rem, 6vw, 4rem);
96
+
97
+ /** Border radius for rounded corners */
40
98
  --exercise-border-radius: 0.25rem;
99
+
100
+ /** Border width for inputs and buttons */
41
101
  --exercise-border-width: 2px;
102
+ }
103
+
104
+ /* Layout & Structure */
105
+
106
+ /**
107
+ * Layout & Structure Styles
108
+ *
109
+ * Main container and layout grid styles.
110
+ */
111
+
112
+ /* ========================================
113
+ MAIN CONTAINER
114
+ ======================================== */
42
115
 
43
- /* Component styles */
116
+ .math-exercise-renderer {
44
117
  position: relative;
45
118
  background: var(--exercise-bg);
46
119
  padding: var(--exercise-padding);
@@ -50,18 +123,25 @@
50
123
  }
51
124
 
52
125
  /* ========================================
53
- LAYOUT & STRUCTURE
126
+ LAYOUT CONTAINER
54
127
  ======================================== */
55
128
 
56
129
  .exercise-layout-container {
57
- /* Default: no flex when no keypad */
130
+ /* Default: regular block layout */
58
131
  }
59
132
 
60
133
  .exercise-layout-container.has-keypad {
134
+ /* When keypad is present, use flex layout for side-by-side */
61
135
  display: flex;
136
+ justify-content: space-between;
62
137
  }
63
-
138
+
139
+ /* ========================================
140
+ EXERCISE LAYOUT
141
+ ======================================== */
142
+
64
143
  .exercise-layout {
144
+ justify-content: space-evenly;
65
145
  display: flex;
66
146
  flex-direction: column;
67
147
  gap: var(--exercise-line-gap);
@@ -69,63 +149,109 @@
69
149
  margin-bottom: var(--exercise-margin-bottom);
70
150
  }
71
151
 
152
+ /* ========================================
153
+ EXERCISE LINES
154
+ ======================================== */
155
+
72
156
  .exercise-line {
73
157
  display: flex;
74
158
  flex-wrap: wrap;
75
159
  align-items: center;
76
160
  justify-content: center;
161
+ align-items: baseline;
77
162
  gap: var(--exercise-element-margin);
78
163
  padding-bottom: calc(var(--exercise-line-gap) * 0.5);
79
164
  }
80
165
 
81
166
  .exercise-line-arabic {
82
- /* Arabic text alignment - can be overridden by consuming apps */
83
- justify-content: flex-end;
167
+ /* RTL layout handled by dir="rtl" attribute on text groups */
168
+ /* No additional styling needed */
84
169
  }
85
170
 
86
- .linebreak {
87
- /* Line break element - handled by flex layout */
88
- }
171
+ /* Content Styles */
172
+
173
+ /**
174
+ * Text Content Styles
175
+ *
176
+ * Styles for text items, questions, operators, and bidirectional text.
177
+ */
89
178
 
90
179
  /* ========================================
91
- TEXT CONTENT
180
+ QUESTION TEXT
92
181
  ======================================== */
93
182
 
94
183
  .question-text {
95
184
  display: block;
96
185
  width: 100%;
97
- font-size: calc(var(--exercise-font-size-large) * 1.2);
186
+ font-size: calc(var(--exercise-font-size-large) * 1);
98
187
  font-weight: var(--exercise-font-weight-bold);
99
188
  margin-bottom: var(--exercise-margin-bottom);
100
189
  color: var(--exercise-text);
101
190
  line-height: 1.4;
191
+ text-align: center;
102
192
  }
103
193
 
104
194
  .question-text-arabic {
105
- direction: rtl;
106
- text-align: right;
195
+
107
196
  }
108
197
 
198
+ /* ========================================
199
+ TEXT ITEMS
200
+ ======================================== */
201
+
109
202
  .text-item {
110
- margin: 0 var(--exercise-element-margin);
111
203
  color: var(--exercise-text);
204
+ font-size: medium;
205
+ }
206
+
207
+ .text-group {
208
+ /**
209
+ * Wrapper for text segments with bidirectional content.
210
+ * unicode-bidi: plaintext lets the browser auto-detect direction
211
+ * based on the first strong directional character.
212
+ */
213
+ display: inline;
214
+ unicode-bidi: plaintext;
112
215
  }
113
216
 
114
217
  .text-item-arabic {
115
- text-align: end;
218
+ /* Direction is handled by parent text-group with unicode-bidi: plaintext */
116
219
  }
117
220
 
118
221
  .text-item-not-arabic {
119
- text-align: start;
222
+ /* Direction is handled by parent text-group with unicode-bidi: plaintext */
120
223
  }
121
224
 
122
- .number-text {
225
+ /* ========================================
226
+ SPACE ELEMENT
227
+ ======================================== */
228
+
229
+ .space {
230
+ /**
231
+ * Minimal width to create visual spacing between inline elements.
232
+ * 0.5px is intentionally small - actual spacing comes from flex gap.
233
+ * This just ensures the element exists in the layout.
234
+ */
235
+ width: 0.5px;
236
+ }
237
+
238
+ /* ========================================
239
+ HIGHLIGHTED TEXT
240
+ ======================================== */
241
+
242
+ .highlighted-text {
123
243
  font-weight: var(--exercise-font-weight);
124
244
  margin: 0 var(--exercise-element-margin);
125
245
  color: var(--exercise-text);
246
+
247
+ /**
248
+ * unicode-bidi: isolate prevents highlighted text from being affected
249
+ * by parent RTL context, keeping numbers in correct order.
250
+ */
251
+ unicode-bidi: isolate;
126
252
  }
127
253
 
128
- .number-text .highlight {
254
+ .highlighted-text .highlight {
129
255
  color: red;
130
256
  font-weight: var(--exercise-font-weight-bold);
131
257
  }
@@ -135,10 +261,10 @@
135
261
  ======================================== */
136
262
 
137
263
  .operator {
138
- margin: 0 calc(var(--exercise-element-margin) * 2);
139
- font-weight: var(--exercise-font-weight-bold);
264
+ font-weight: 900;
140
265
  color: var(--exercise-accent);
141
- font-size: var(--exercise-font-size-large);
266
+ /** Operator size: 20px - larger than text for visual emphasis */
267
+ font-size: 20px;
142
268
  }
143
269
 
144
270
  .group {
@@ -146,8 +272,14 @@
146
272
  color: var(--exercise-text-light);
147
273
  }
148
274
 
275
+ /**
276
+ * Input Styles
277
+ *
278
+ * Styles for interactive input fields.
279
+ */
280
+
149
281
  /* ========================================
150
- INTERACTIVE ELEMENTS
282
+ INLINE INPUT
151
283
  ======================================== */
152
284
 
153
285
  .inline-input {
@@ -160,11 +292,18 @@
160
292
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
161
293
  background: var(--exercise-bg);
162
294
  color: var(--exercise-text);
295
+
296
+ /**
297
+ * Always LTR for inputs - even in RTL context, numbers should
298
+ * be entered left-to-right for consistent UX.
299
+ */
300
+ direction: ltr;
163
301
  }
164
302
 
165
303
  .inline-input:focus {
166
304
  outline: none;
167
305
  border-color: var(--exercise-border-focus);
306
+ /** Focus shadow: 3px blur with 10% opacity primary color */
168
307
  box-shadow: 0 0 0 3px rgba(0, 122, 204, 0.1);
169
308
  }
170
309
 
@@ -172,14 +311,18 @@
172
311
  border-color: var(--exercise-border-focus);
173
312
  }
174
313
 
314
+ /**
315
+ * Widget Styles
316
+ *
317
+ * Styles for drag options, numeric keypad, and other interactive widgets.
318
+ */
319
+
175
320
  /* ========================================
176
- WIDGETS & TOOLS
321
+ DRAG OPTIONS
177
322
  ======================================== */
178
323
 
179
- /* Drag Options */
180
324
  .drag-options-container {
181
- /* margin-top: var(--exercise-margin-bottom); */
182
- /* padding: var(--exercise-padding); */
325
+ /* Container for drag options widget */
183
326
  }
184
327
 
185
328
  .drag-options {
@@ -190,6 +333,7 @@
190
333
  }
191
334
 
192
335
  .drag-option {
336
+ /** Padding: 10px vertical, 16px horizontal for comfortable touch targets */
193
337
  padding: 10px 16px;
194
338
  border: var(--exercise-border-width) solid var(--exercise-border);
195
339
  border-radius: var(--exercise-border-radius);
@@ -206,11 +350,11 @@
206
350
 
207
351
  .drag-option:hover {
208
352
  border-color: var(--exercise-primary);
209
- /* background: rgba(0, 122, 204, 0.05); */
210
353
  }
211
354
 
212
355
  .drag-option:active {
213
356
  cursor: grabbing;
357
+ /** Scale down slightly (98%) when dragging for visual feedback */
214
358
  transform: scale(0.98);
215
359
  }
216
360
 
@@ -218,34 +362,45 @@
218
362
  background: var(--exercise-primary);
219
363
  color: white;
220
364
  border-color: var(--exercise-primary);
365
+ /** Shadow: 2px offset, 4px blur, 20% opacity for depth */
221
366
  box-shadow: 0 2px 4px rgba(0, 122, 204, 0.2);
222
367
  }
223
368
 
224
- /* Legacy button styles - for backward compatibility */
369
+ /* Legacy button-style drag options for backward compatibility */
370
+
225
371
  button.drag-option {
226
- /* padding: calc(var(--exercise-input-padding) * 2); */
227
372
  border: none;
228
373
  border-radius: var(--exercise-border-radius);
229
374
  cursor: pointer;
230
375
  font-size: var(--exercise-font-size);
231
376
  color: white;
232
377
  background: var(--exercise-accent);
233
- /* width: var(--exercise-button-width); */
234
378
  font-weight: var(--exercise-font-weight);
235
379
  }
236
380
 
237
- /* Numeric Keypad */
381
+ /* ========================================
382
+ NUMERIC KEYPAD
383
+ ======================================== */
384
+
385
+ .numeric-keypad-container {
386
+ /**
387
+ * Keypad container dimensions:
388
+ * - min-width: 150px ensures keypad is always usable
389
+ * - margins: 5px on each side for spacing
390
+ */
391
+ min-width: 150px;
392
+ margin-right: 5px;
393
+ margin-left: 5px;
394
+ }
395
+
238
396
  .numeric-keypad {
239
397
  display: grid;
240
398
  grid-template-columns: repeat(3, 1fr);
241
- gap: calc(var(--exercise-element-margin) * 1);
242
- /* margin-top: var(--exercise-margin-bottom); */
399
+ gap: var(--exercise-element-margin);
243
400
  }
244
401
 
245
- .numeric-keypad-container {
246
- min-width: 150px;
247
- margin-right: 5px;
248
- margin-left: 5px;
402
+ .numeric-keypad-with-operators {
403
+ grid-template-columns: repeat(4, 1fr);
249
404
  }
250
405
 
251
406
  .keypad-btn {
@@ -266,17 +421,25 @@ button.drag-option {
266
421
 
267
422
  .keypad-btn:hover {
268
423
  border-color: var(--exercise-primary);
424
+ /** Hover background: 5% opacity primary color */
269
425
  background: rgba(0, 122, 204, 0.05);
270
426
  }
271
427
 
272
428
  .keypad-btn:active {
429
+ /** Scale down to 95% when clicked for tactile feedback */
273
430
  transform: scale(0.95);
274
431
  background: var(--exercise-primary);
275
432
  color: white;
276
433
  }
277
434
 
435
+ /**
436
+ * Button & Action Styles
437
+ *
438
+ * Styles for submit buttons and other action controls.
439
+ */
440
+
278
441
  /* ========================================
279
- ACTIONS & CONTROLS
442
+ SUBMIT BUTTON
280
443
  ======================================== */
281
444
 
282
445
  .exercise-submit-btn {
@@ -298,49 +461,69 @@ button.drag-option {
298
461
  }
299
462
 
300
463
  .exercise-submit-btn:active {
464
+ /** Scale down to 98% when clicked for tactile feedback */
301
465
  transform: scale(0.98);
302
466
  }
303
467
 
468
+ /* Responsive Overrides - must be last */
469
+
470
+ /**
471
+ * Responsive Design Styles
472
+ *
473
+ * Media queries for different screen sizes.
474
+ */
475
+
304
476
  /* ========================================
305
- RESPONSIVE DESIGN
477
+ TABLET & SMALLER (max-width: 768px)
306
478
  ======================================== */
307
479
 
308
480
  @media (max-width: 768px) {
309
481
  .math-exercise-renderer {
482
+ /** Reduce padding by 25% on tablets */
310
483
  padding: calc(var(--exercise-padding) * 0.75);
311
484
  }
312
485
 
313
486
  .exercise-line {
487
+ /** Reduce gap between inline elements by 25% */
314
488
  gap: calc(var(--exercise-element-margin) * 0.75);
315
489
  }
316
490
 
317
491
  .drag-options {
492
+ /** Reduce gap between drag options */
318
493
  gap: var(--exercise-element-margin);
319
494
  }
320
495
 
321
496
  .numeric-keypad {
497
+ /** Reduce gap between keypad buttons */
322
498
  gap: var(--exercise-element-margin);
323
499
  }
324
500
  }
325
501
 
502
+ /* ========================================
503
+ MOBILE (max-width: 480px)
504
+ ======================================== */
505
+
326
506
  @media (max-width: 480px) {
327
507
  .question-text {
508
+ /** Smaller question text on mobile */
328
509
  font-size: var(--exercise-font-size-large);
329
510
  }
330
511
 
331
512
  .inline-input {
513
+ /** Reduce input width by 20% on mobile */
332
514
  width: calc(var(--exercise-input-width) * 0.8);
333
515
  }
334
516
 
335
517
  .drag-option {
518
+ /** Reduce drag option width by 20% on mobile */
336
519
  min-width: calc(var(--exercise-button-width) * 0.8);
337
520
  }
338
521
 
339
522
  button.drag-option {
523
+ /** Reduce button drag option width by 20% on mobile */
340
524
  width: calc(var(--exercise-button-width) * 0.8);
341
525
  }
342
526
  }
343
-
344
527
  /* ============================================
345
528
  DND STYLES
346
529
  ============================================ */
@@ -441,8 +624,8 @@ button.drag-option {
441
624
  align-items: center;
442
625
  justify-content: center;
443
626
  border-radius: 4px;
444
- min-width: 50px;
445
- min-height: 40px;
627
+ /* min-width: 50px;
628
+ min-height: 40px; */
446
629
  }
447
630
 
448
631
  .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.3",
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",
@@ -64,6 +64,9 @@
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
71
  "@types/react": "^18.2.0",
69
72
  "@types/react-dom": "^18.2.0",
@@ -71,6 +74,7 @@
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
79
  "react": "^18.2.0",
76
80
  "react-dom": "^18.2.0",