@mathscan/math-exercise-engine 1.0.1 → 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,66 +149,110 @@
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 {
129
- background: var(--exercise-highlight);
130
- padding: 0.125rem 0.25rem;
131
- border-radius: calc(var(--exercise-border-radius) * 0.75);
254
+ .highlighted-text .highlight {
255
+ color: red;
132
256
  font-weight: var(--exercise-font-weight-bold);
133
257
  }
134
258
 
@@ -137,10 +261,10 @@
137
261
  ======================================== */
138
262
 
139
263
  .operator {
140
- margin: 0 calc(var(--exercise-element-margin) * 2);
141
- font-weight: var(--exercise-font-weight-bold);
264
+ font-weight: 900;
142
265
  color: var(--exercise-accent);
143
- font-size: var(--exercise-font-size-large);
266
+ /** Operator size: 20px - larger than text for visual emphasis */
267
+ font-size: 20px;
144
268
  }
145
269
 
146
270
  .group {
@@ -148,8 +272,14 @@
148
272
  color: var(--exercise-text-light);
149
273
  }
150
274
 
275
+ /**
276
+ * Input Styles
277
+ *
278
+ * Styles for interactive input fields.
279
+ */
280
+
151
281
  /* ========================================
152
- INTERACTIVE ELEMENTS
282
+ INLINE INPUT
153
283
  ======================================== */
154
284
 
155
285
  .inline-input {
@@ -162,11 +292,18 @@
162
292
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
163
293
  background: var(--exercise-bg);
164
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;
165
301
  }
166
302
 
167
303
  .inline-input:focus {
168
304
  outline: none;
169
305
  border-color: var(--exercise-border-focus);
306
+ /** Focus shadow: 3px blur with 10% opacity primary color */
170
307
  box-shadow: 0 0 0 3px rgba(0, 122, 204, 0.1);
171
308
  }
172
309
 
@@ -174,14 +311,18 @@
174
311
  border-color: var(--exercise-border-focus);
175
312
  }
176
313
 
314
+ /**
315
+ * Widget Styles
316
+ *
317
+ * Styles for drag options, numeric keypad, and other interactive widgets.
318
+ */
319
+
177
320
  /* ========================================
178
- WIDGETS & TOOLS
321
+ DRAG OPTIONS
179
322
  ======================================== */
180
323
 
181
- /* Drag Options */
182
324
  .drag-options-container {
183
- /* margin-top: var(--exercise-margin-bottom); */
184
- /* padding: var(--exercise-padding); */
325
+ /* Container for drag options widget */
185
326
  }
186
327
 
187
328
  .drag-options {
@@ -192,6 +333,7 @@
192
333
  }
193
334
 
194
335
  .drag-option {
336
+ /** Padding: 10px vertical, 16px horizontal for comfortable touch targets */
195
337
  padding: 10px 16px;
196
338
  border: var(--exercise-border-width) solid var(--exercise-border);
197
339
  border-radius: var(--exercise-border-radius);
@@ -208,11 +350,11 @@
208
350
 
209
351
  .drag-option:hover {
210
352
  border-color: var(--exercise-primary);
211
- /* background: rgba(0, 122, 204, 0.05); */
212
353
  }
213
354
 
214
355
  .drag-option:active {
215
356
  cursor: grabbing;
357
+ /** Scale down slightly (98%) when dragging for visual feedback */
216
358
  transform: scale(0.98);
217
359
  }
218
360
 
@@ -220,34 +362,45 @@
220
362
  background: var(--exercise-primary);
221
363
  color: white;
222
364
  border-color: var(--exercise-primary);
365
+ /** Shadow: 2px offset, 4px blur, 20% opacity for depth */
223
366
  box-shadow: 0 2px 4px rgba(0, 122, 204, 0.2);
224
367
  }
225
368
 
226
- /* Legacy button styles - for backward compatibility */
369
+ /* Legacy button-style drag options for backward compatibility */
370
+
227
371
  button.drag-option {
228
- /* padding: calc(var(--exercise-input-padding) * 2); */
229
372
  border: none;
230
373
  border-radius: var(--exercise-border-radius);
231
374
  cursor: pointer;
232
375
  font-size: var(--exercise-font-size);
233
376
  color: white;
234
377
  background: var(--exercise-accent);
235
- /* width: var(--exercise-button-width); */
236
378
  font-weight: var(--exercise-font-weight);
237
379
  }
238
380
 
239
- /* 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
+
240
396
  .numeric-keypad {
241
397
  display: grid;
242
398
  grid-template-columns: repeat(3, 1fr);
243
- gap: calc(var(--exercise-element-margin) * 1);
244
- /* margin-top: var(--exercise-margin-bottom); */
399
+ gap: var(--exercise-element-margin);
245
400
  }
246
401
 
247
- .numeric-keypad-container {
248
- min-width: 150px;
249
- margin-right: 5px;
250
- margin-left: 5px;
402
+ .numeric-keypad-with-operators {
403
+ grid-template-columns: repeat(4, 1fr);
251
404
  }
252
405
 
253
406
  .keypad-btn {
@@ -268,17 +421,25 @@ button.drag-option {
268
421
 
269
422
  .keypad-btn:hover {
270
423
  border-color: var(--exercise-primary);
424
+ /** Hover background: 5% opacity primary color */
271
425
  background: rgba(0, 122, 204, 0.05);
272
426
  }
273
427
 
274
428
  .keypad-btn:active {
429
+ /** Scale down to 95% when clicked for tactile feedback */
275
430
  transform: scale(0.95);
276
431
  background: var(--exercise-primary);
277
432
  color: white;
278
433
  }
279
434
 
435
+ /**
436
+ * Button & Action Styles
437
+ *
438
+ * Styles for submit buttons and other action controls.
439
+ */
440
+
280
441
  /* ========================================
281
- ACTIONS & CONTROLS
442
+ SUBMIT BUTTON
282
443
  ======================================== */
283
444
 
284
445
  .exercise-submit-btn {
@@ -300,49 +461,69 @@ button.drag-option {
300
461
  }
301
462
 
302
463
  .exercise-submit-btn:active {
464
+ /** Scale down to 98% when clicked for tactile feedback */
303
465
  transform: scale(0.98);
304
466
  }
305
467
 
468
+ /* Responsive Overrides - must be last */
469
+
470
+ /**
471
+ * Responsive Design Styles
472
+ *
473
+ * Media queries for different screen sizes.
474
+ */
475
+
306
476
  /* ========================================
307
- RESPONSIVE DESIGN
477
+ TABLET & SMALLER (max-width: 768px)
308
478
  ======================================== */
309
479
 
310
480
  @media (max-width: 768px) {
311
481
  .math-exercise-renderer {
482
+ /** Reduce padding by 25% on tablets */
312
483
  padding: calc(var(--exercise-padding) * 0.75);
313
484
  }
314
485
 
315
486
  .exercise-line {
487
+ /** Reduce gap between inline elements by 25% */
316
488
  gap: calc(var(--exercise-element-margin) * 0.75);
317
489
  }
318
490
 
319
491
  .drag-options {
492
+ /** Reduce gap between drag options */
320
493
  gap: var(--exercise-element-margin);
321
494
  }
322
495
 
323
496
  .numeric-keypad {
497
+ /** Reduce gap between keypad buttons */
324
498
  gap: var(--exercise-element-margin);
325
499
  }
326
500
  }
327
501
 
502
+ /* ========================================
503
+ MOBILE (max-width: 480px)
504
+ ======================================== */
505
+
328
506
  @media (max-width: 480px) {
329
507
  .question-text {
508
+ /** Smaller question text on mobile */
330
509
  font-size: var(--exercise-font-size-large);
331
510
  }
332
511
 
333
512
  .inline-input {
513
+ /** Reduce input width by 20% on mobile */
334
514
  width: calc(var(--exercise-input-width) * 0.8);
335
515
  }
336
516
 
337
517
  .drag-option {
518
+ /** Reduce drag option width by 20% on mobile */
338
519
  min-width: calc(var(--exercise-button-width) * 0.8);
339
520
  }
340
521
 
341
522
  button.drag-option {
523
+ /** Reduce button drag option width by 20% on mobile */
342
524
  width: calc(var(--exercise-button-width) * 0.8);
343
525
  }
344
526
  }
345
-
346
527
  /* ============================================
347
528
  DND STYLES
348
529
  ============================================ */
@@ -443,8 +624,8 @@ button.drag-option {
443
624
  align-items: center;
444
625
  justify-content: center;
445
626
  border-radius: 4px;
446
- min-width: 50px;
447
- min-height: 40px;
627
+ /* min-width: 50px;
628
+ min-height: 40px; */
448
629
  }
449
630
 
450
631
  .inline-input {
package/package.json CHANGED
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "@mathscan/math-exercise-engine",
3
- "version": "1.0.1",
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",
7
7
  "types": "dist/index.d.ts",
8
8
  "exports": {
9
9
  ".": {
10
+ "types": "./dist/index.d.ts",
10
11
  "import": "./dist/index.mjs",
11
- "require": "./dist/index.js",
12
- "types": "./dist/index.d.ts"
12
+ "require": "./dist/index.js"
13
13
  },
14
14
  "./style.css": "./dist/style.css"
15
15
  },
@@ -64,12 +64,17 @@
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",
70
73
  "@typescript-eslint/eslint-plugin": "^6.19.0",
71
74
  "@typescript-eslint/parser": "^6.19.0",
75
+ "@vitest/coverage-v8": "^4.0.17",
72
76
  "eslint": "^8.56.0",
77
+ "jsdom": "^28.0.0",
73
78
  "prettier": "^3.2.4",
74
79
  "react": "^18.2.0",
75
80
  "react-dom": "^18.2.0",
@@ -78,7 +83,7 @@
78
83
  "typescript": "^5.3.3",
79
84
  "vite": "^5.0.11",
80
85
  "vite-plugin-dts": "^3.7.0",
81
- "vitest": "^1.2.0"
86
+ "vitest": "^4.0.17"
82
87
  },
83
88
  "engines": {
84
89
  "node": ">=18.0.0"