@mathscan/math-exercise-engine 1.0.0

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 ADDED
@@ -0,0 +1,346 @@
1
+ @import './dnd/dnd.css';
2
+ /* ========================================
3
+ MATH EXERCISE ENGINE - DEFAULT STYLES
4
+ ========================================
5
+ Minimal, responsive styles for math-exercise-engine
6
+ All styles use scoped CSS custom properties for easy theming
7
+ ======================================== */
8
+
9
+ /* ========================================
10
+ MAIN CONTAINER
11
+ ======================================== */
12
+
13
+ /* Scoped CSS Custom Properties for Theming */
14
+ .math-exercise-renderer {
15
+ /* Colors */
16
+ --exercise-bg: white;
17
+ --exercise-text: #333;
18
+ --exercise-text-light: #555;
19
+ --exercise-primary: #007acc;
20
+ --exercise-accent: #705DC0;
21
+ --exercise-highlight: #ffd700;
22
+ --exercise-border: #ddd;
23
+ --exercise-border-focus: var(--exercise-primary);
24
+
25
+ /* Spacing */
26
+ --exercise-padding: clamp(1rem, 3vw, 2rem);
27
+ --exercise-margin-bottom: 1.5rem;
28
+ --exercise-line-gap: 0.25rem;
29
+ --exercise-element-margin: 0.25rem;
30
+
31
+ /* Typography */
32
+ --exercise-font-size: clamp(0.875rem, 2vw, 1.25rem);
33
+ --exercise-font-size-large: clamp(1rem, 1.5vw, 0.75rem);
34
+ --exercise-font-weight: 500;
35
+ --exercise-font-weight-bold: 600;
36
+
37
+ /* Layout */
38
+ --exercise-input-width: clamp(2.5rem, 5vw, 4rem);
39
+ --exercise-input-padding: 0.375rem 0.625rem;
40
+ --exercise-button-width: clamp(3rem, 6vw, 4rem);
41
+ --exercise-border-radius: 0.25rem;
42
+ --exercise-border-width: 2px;
43
+
44
+ /* Component styles */
45
+ position: relative;
46
+ background: var(--exercise-bg);
47
+ padding: var(--exercise-padding);
48
+ border-radius: calc(var(--exercise-border-radius) * 2);
49
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
50
+ font-family: system-ui, -apple-system, sans-serif;
51
+ }
52
+
53
+ /* ========================================
54
+ LAYOUT & STRUCTURE
55
+ ======================================== */
56
+
57
+ .exercise-layout-container {
58
+ /* Default: no flex when no keypad */
59
+ }
60
+
61
+ .exercise-layout-container.has-keypad {
62
+ display: flex;
63
+ }
64
+
65
+ .exercise-layout {
66
+ display: flex;
67
+ flex-direction: column;
68
+ gap: var(--exercise-line-gap);
69
+ font-size: var(--exercise-font-size);
70
+ margin-bottom: var(--exercise-margin-bottom);
71
+ }
72
+
73
+ .exercise-line {
74
+ display: flex;
75
+ flex-wrap: wrap;
76
+ align-items: center;
77
+ justify-content: center;
78
+ gap: var(--exercise-element-margin);
79
+ padding-bottom: calc(var(--exercise-line-gap) * 0.5);
80
+ }
81
+
82
+ .exercise-line-arabic {
83
+ /* Arabic text alignment - can be overridden by consuming apps */
84
+ justify-content: flex-end;
85
+ }
86
+
87
+ .linebreak {
88
+ /* Line break element - handled by flex layout */
89
+ }
90
+
91
+ /* ========================================
92
+ TEXT CONTENT
93
+ ======================================== */
94
+
95
+ .question-text {
96
+ display: block;
97
+ width: 100%;
98
+ font-size: calc(var(--exercise-font-size-large) * 1.2);
99
+ font-weight: var(--exercise-font-weight-bold);
100
+ margin-bottom: var(--exercise-margin-bottom);
101
+ color: var(--exercise-text);
102
+ line-height: 1.4;
103
+ }
104
+
105
+ .question-text-arabic {
106
+ direction: rtl;
107
+ text-align: right;
108
+ }
109
+
110
+ .text-item {
111
+ margin: 0 var(--exercise-element-margin);
112
+ color: var(--exercise-text);
113
+ }
114
+
115
+ .text-item-arabic {
116
+ text-align: end;
117
+ }
118
+
119
+ .text-item-not-arabic {
120
+ text-align: start;
121
+ }
122
+
123
+ .number-text {
124
+ font-weight: var(--exercise-font-weight);
125
+ margin: 0 var(--exercise-element-margin);
126
+ color: var(--exercise-text);
127
+ }
128
+
129
+ .number-text .highlight {
130
+ background: var(--exercise-highlight);
131
+ padding: 0.125rem 0.25rem;
132
+ border-radius: calc(var(--exercise-border-radius) * 0.75);
133
+ font-weight: var(--exercise-font-weight-bold);
134
+ }
135
+
136
+ /* ========================================
137
+ OPERATORS & SYMBOLS
138
+ ======================================== */
139
+
140
+ .operator {
141
+ margin: 0 calc(var(--exercise-element-margin) * 2);
142
+ font-weight: var(--exercise-font-weight-bold);
143
+ color: var(--exercise-accent);
144
+ font-size: var(--exercise-font-size-large);
145
+ }
146
+
147
+ .group {
148
+ margin: 0 var(--exercise-element-margin);
149
+ color: var(--exercise-text-light);
150
+ }
151
+
152
+ /* ========================================
153
+ INTERACTIVE ELEMENTS
154
+ ======================================== */
155
+
156
+ .inline-input {
157
+ width: var(--exercise-input-width);
158
+ padding: var(--exercise-input-padding);
159
+ border: var(--exercise-border-width) solid var(--exercise-border);
160
+ border-radius: var(--exercise-border-radius);
161
+ font-size: var(--exercise-font-size-large);
162
+ text-align: center;
163
+ transition: border-color 0.2s ease, box-shadow 0.2s ease;
164
+ background: var(--exercise-bg);
165
+ color: var(--exercise-text);
166
+ }
167
+
168
+ .inline-input:focus {
169
+ outline: none;
170
+ border-color: var(--exercise-border-focus);
171
+ box-shadow: 0 0 0 3px rgba(0, 122, 204, 0.1);
172
+ }
173
+
174
+ .inline-input:hover {
175
+ border-color: var(--exercise-border-focus);
176
+ }
177
+
178
+ /* ========================================
179
+ WIDGETS & TOOLS
180
+ ======================================== */
181
+
182
+ /* Drag Options */
183
+ .drag-options-container {
184
+ /* margin-top: var(--exercise-margin-bottom); */
185
+ /* padding: var(--exercise-padding); */
186
+ }
187
+
188
+ .drag-options {
189
+ display: flex;
190
+ justify-content: center;
191
+ gap: calc(var(--exercise-element-margin) * 2);
192
+ flex-wrap: wrap;
193
+ }
194
+
195
+ .drag-option {
196
+ padding: 10px 16px;
197
+ border: var(--exercise-border-width) solid var(--exercise-border);
198
+ border-radius: var(--exercise-border-radius);
199
+ background: var(--exercise-bg);
200
+ color: var(--exercise-text);
201
+ cursor: grab;
202
+ user-select: none;
203
+ font-size: var(--exercise-font-size);
204
+ font-weight: var(--exercise-font-weight);
205
+ transition: all 0.2s ease;
206
+ min-width: var(--exercise-button-width);
207
+ text-align: center;
208
+ }
209
+
210
+ .drag-option:hover {
211
+ border-color: var(--exercise-primary);
212
+ /* background: rgba(0, 122, 204, 0.05); */
213
+ }
214
+
215
+ .drag-option:active {
216
+ cursor: grabbing;
217
+ transform: scale(0.98);
218
+ }
219
+
220
+ .drag-option.selected {
221
+ background: var(--exercise-primary);
222
+ color: white;
223
+ border-color: var(--exercise-primary);
224
+ box-shadow: 0 2px 4px rgba(0, 122, 204, 0.2);
225
+ }
226
+
227
+ /* Legacy button styles - for backward compatibility */
228
+ button.drag-option {
229
+ /* padding: calc(var(--exercise-input-padding) * 2); */
230
+ border: none;
231
+ border-radius: var(--exercise-border-radius);
232
+ cursor: pointer;
233
+ font-size: var(--exercise-font-size);
234
+ color: white;
235
+ background: var(--exercise-accent);
236
+ /* width: var(--exercise-button-width); */
237
+ font-weight: var(--exercise-font-weight);
238
+ }
239
+
240
+ /* Numeric Keypad */
241
+ .numeric-keypad {
242
+ display: grid;
243
+ grid-template-columns: repeat(3, 1fr);
244
+ gap: calc(var(--exercise-element-margin) * 1);
245
+ /* margin-top: var(--exercise-margin-bottom); */
246
+ }
247
+
248
+ .numeric-keypad-container {
249
+ min-width: 150px;
250
+ margin-right: 5px;
251
+ margin-left: 5px;
252
+ }
253
+
254
+ .keypad-btn {
255
+ padding: var(--exercise-input-padding);
256
+ border: var(--exercise-border-width) solid var(--exercise-border);
257
+ border-radius: var(--exercise-border-radius);
258
+ background: var(--exercise-bg);
259
+ color: var(--exercise-text);
260
+ font-size: var(--exercise-font-size-large);
261
+ font-weight: var(--exercise-font-weight);
262
+ cursor: pointer;
263
+ transition: all 0.2s ease;
264
+ aspect-ratio: 1;
265
+ display: flex;
266
+ align-items: center;
267
+ justify-content: center;
268
+ }
269
+
270
+ .keypad-btn:hover {
271
+ border-color: var(--exercise-primary);
272
+ background: rgba(0, 122, 204, 0.05);
273
+ }
274
+
275
+ .keypad-btn:active {
276
+ transform: scale(0.95);
277
+ background: var(--exercise-primary);
278
+ color: white;
279
+ }
280
+
281
+ /* ========================================
282
+ ACTIONS & CONTROLS
283
+ ======================================== */
284
+
285
+ .exercise-submit-btn {
286
+ padding: var(--exercise-input-padding);
287
+ border: var(--exercise-border-width) solid var(--exercise-primary);
288
+ border-radius: var(--exercise-border-radius);
289
+ background: var(--exercise-primary);
290
+ color: white;
291
+ font-size: var(--exercise-font-size);
292
+ font-weight: var(--exercise-font-weight-bold);
293
+ cursor: pointer;
294
+ transition: all 0.2s ease;
295
+ margin-top: var(--exercise-margin-bottom);
296
+ }
297
+
298
+ .exercise-submit-btn:hover {
299
+ background: var(--exercise-bg);
300
+ color: var(--exercise-primary);
301
+ }
302
+
303
+ .exercise-submit-btn:active {
304
+ transform: scale(0.98);
305
+ }
306
+
307
+ /* ========================================
308
+ RESPONSIVE DESIGN
309
+ ======================================== */
310
+
311
+ @media (max-width: 768px) {
312
+ .math-exercise-renderer {
313
+ padding: calc(var(--exercise-padding) * 0.75);
314
+ }
315
+
316
+ .exercise-line {
317
+ gap: calc(var(--exercise-element-margin) * 0.75);
318
+ }
319
+
320
+ .drag-options {
321
+ gap: var(--exercise-element-margin);
322
+ }
323
+
324
+ .numeric-keypad {
325
+ gap: var(--exercise-element-margin);
326
+ }
327
+ }
328
+
329
+ @media (max-width: 480px) {
330
+ .question-text {
331
+ font-size: var(--exercise-font-size-large);
332
+ }
333
+
334
+ .inline-input {
335
+ width: calc(var(--exercise-input-width) * 0.8);
336
+ }
337
+
338
+ .drag-option {
339
+ min-width: calc(var(--exercise-button-width) * 0.8);
340
+ }
341
+
342
+ button.drag-option {
343
+ width: calc(var(--exercise-button-width) * 0.8);
344
+ }
345
+ }
346
+
package/package.json ADDED
@@ -0,0 +1,87 @@
1
+ {
2
+ "name": "@mathscan/math-exercise-engine",
3
+ "version": "1.0.0",
4
+ "description": "A DSL parser and validator for math exercises - transforms text-based exercise definitions into structured AST and JSON",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.mjs",
11
+ "require": "./dist/index.js",
12
+ "types": "./dist/index.d.ts"
13
+ },
14
+ "./style.css": "./dist/style.css"
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "scripts": {
20
+ "build": "vite build",
21
+ "dev": "vite build --watch",
22
+ "demo": "node examples/demo.mjs",
23
+ "demo:ts": "tsx --conditions=import examples/demo.ts",
24
+ "demo:build": "pnpm run build && node examples/demo.mjs",
25
+ "test": "vitest",
26
+ "test:run": "vitest run",
27
+ "test:coverage": "vitest run --coverage",
28
+ "lint": "echo 'ESLint disabled for now'",
29
+ "lint:fix": "echo 'ESLint disabled for now'",
30
+ "format": "prettier --write \"src/**/*.ts\" \"tests/**/*.ts\" \"*.{json,md}\"",
31
+ "format:check": "prettier --check \"src/**/*.ts\" \"tests/**/*.ts\" \"*.{json,md}\"",
32
+ "typecheck": "tsc --noEmit",
33
+ "prepublishOnly": "pnpm run build"
34
+ },
35
+ "keywords": [
36
+ "math",
37
+ "exercise",
38
+ "dsl",
39
+ "parser",
40
+ "chevrotain",
41
+ "ast",
42
+ "education",
43
+ "edtech"
44
+ ],
45
+ "author": "mathscan",
46
+ "license": "mathscan-license",
47
+ "dependencies": {
48
+ "@dnd-kit/core": "^6.3.1",
49
+ "@dnd-kit/utilities": "^3.2.2",
50
+ "chevrotain": "^11.0.3",
51
+ "mathjs": "^12.4.0",
52
+ "zod": "^3.22.4"
53
+ },
54
+ "peerDependencies": {
55
+ "react": "^18.0.0",
56
+ "react-dom": "^18.0.0"
57
+ },
58
+ "peerDependenciesMeta": {
59
+ "react": {
60
+ "optional": true
61
+ },
62
+ "react-dom": {
63
+ "optional": true
64
+ }
65
+ },
66
+ "devDependencies": {
67
+ "@types/node": "^20.11.0",
68
+ "@types/react": "^18.2.0",
69
+ "@types/react-dom": "^18.2.0",
70
+ "@typescript-eslint/eslint-plugin": "^6.19.0",
71
+ "@typescript-eslint/parser": "^6.19.0",
72
+ "eslint": "^8.56.0",
73
+ "prettier": "^3.2.4",
74
+ "react": "^18.2.0",
75
+ "react-dom": "^18.2.0",
76
+ "ts-node": "^10.9.2",
77
+ "tsx": "^4.7.0",
78
+ "typescript": "^5.3.3",
79
+ "vite": "^5.0.11",
80
+ "vite-plugin-dts": "^3.7.0",
81
+ "vitest": "^1.2.0"
82
+ },
83
+ "engines": {
84
+ "node": ">=18.0.0"
85
+ },
86
+ "packageManager": "pnpm@8.15.0"
87
+ }