mtrl 0.2.8 → 0.2.9

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.
Files changed (42) hide show
  1. package/index.ts +2 -0
  2. package/package.json +1 -1
  3. package/src/components/navigation/api.ts +131 -96
  4. package/src/components/navigation/features/controller.ts +273 -0
  5. package/src/components/navigation/features/items.ts +133 -64
  6. package/src/components/navigation/navigation.ts +17 -2
  7. package/src/components/navigation/system-types.ts +124 -0
  8. package/src/components/navigation/system.ts +776 -0
  9. package/src/components/slider/config.ts +20 -2
  10. package/src/components/slider/features/controller.ts +761 -0
  11. package/src/components/slider/features/handlers.ts +18 -15
  12. package/src/components/slider/features/index.ts +3 -2
  13. package/src/components/slider/features/range.ts +104 -0
  14. package/src/components/slider/slider.ts +34 -14
  15. package/src/components/slider/structure.ts +152 -0
  16. package/src/components/textfield/api.ts +53 -0
  17. package/src/components/textfield/features.ts +322 -0
  18. package/src/components/textfield/textfield.ts +8 -0
  19. package/src/components/textfield/types.ts +12 -3
  20. package/src/components/timepicker/clockdial.ts +1 -4
  21. package/src/core/compose/features/textinput.ts +15 -2
  22. package/src/core/composition/features/dom.ts +33 -0
  23. package/src/core/composition/features/icon.ts +131 -0
  24. package/src/core/composition/features/index.ts +11 -0
  25. package/src/core/composition/features/label.ts +156 -0
  26. package/src/core/composition/features/structure.ts +22 -0
  27. package/src/core/composition/index.ts +26 -0
  28. package/src/core/index.ts +1 -1
  29. package/src/core/structure.ts +288 -0
  30. package/src/index.ts +1 -0
  31. package/src/styles/components/_navigation-mobile.scss +244 -0
  32. package/src/styles/components/_navigation-system.scss +151 -0
  33. package/src/styles/components/_textfield.scss +250 -11
  34. package/demo/build.ts +0 -349
  35. package/demo/index.html +0 -110
  36. package/demo/main.js +0 -448
  37. package/demo/styles.css +0 -239
  38. package/server.ts +0 -86
  39. package/src/components/slider/features/slider.ts +0 -318
  40. package/src/components/slider/features/structure.ts +0 -181
  41. package/src/components/slider/features/ui.ts +0 -388
  42. package/src/components/textfield/constants.ts +0 -100
@@ -28,7 +28,8 @@ $component: '#{base.$prefix}-textfield';
28
28
  border-radius: 2px;
29
29
  color: t.color('on-surface-variant');
30
30
  transition: transform v.motion('duration-short4') v.motion('easing-emphasized'),
31
- color v.motion('duration-short2') v.motion('easing-standard');
31
+ color v.motion('duration-short2') v.motion('easing-standard'),
32
+ left v.motion('duration-short4') v.motion('easing-emphasized');
32
33
  }
33
34
 
34
35
  // Input element
@@ -68,6 +69,65 @@ $component: '#{base.$prefix}-textfield';
68
69
  }
69
70
  }
70
71
  }
72
+
73
+ // Leading icon
74
+ &-leading-icon {
75
+ position: absolute;
76
+ left: 12px;
77
+ top: 50%;
78
+ transform: translateY(-50%);
79
+ display: flex;
80
+ align-items: center;
81
+ justify-content: center;
82
+ width: 24px;
83
+ height: 24px;
84
+ pointer-events: none;
85
+ color: t.color('on-surface-variant');
86
+ z-index: 1;
87
+
88
+ svg {
89
+ width: 20px;
90
+ height: 20px;
91
+ }
92
+ }
93
+
94
+ // Trailing icon
95
+ &-trailing-icon {
96
+ position: absolute;
97
+ right: 12px;
98
+ top: 50%;
99
+ transform: translateY(-50%);
100
+ display: flex;
101
+ align-items: center;
102
+ justify-content: center;
103
+ width: 24px;
104
+ height: 24px;
105
+ color: t.color('on-surface-variant');
106
+ z-index: 1;
107
+ cursor: pointer;
108
+
109
+ svg {
110
+ width: 20px;
111
+ height: 20px;
112
+ }
113
+ }
114
+
115
+ // Adjustments when icons are present
116
+ &--with-leading-icon {
117
+ .#{$component}-label {
118
+ left: 44px;
119
+ }
120
+
121
+ .#{$component}-input {
122
+ padding-left: 44px;
123
+ }
124
+ }
125
+
126
+ &--with-trailing-icon {
127
+ .#{$component}-input {
128
+ padding-right: 44px;
129
+ }
130
+ }
71
131
 
72
132
  // Error state
73
133
  &--error {
@@ -76,6 +136,11 @@ $component: '#{base.$prefix}-textfield';
76
136
  .#{$component}-label {
77
137
  color: t.color('error');
78
138
  }
139
+
140
+ .#{$component}-leading-icon,
141
+ .#{$component}-trailing-icon {
142
+ color: t.color('error');
143
+ }
79
144
  }
80
145
 
81
146
  // Disabled state
@@ -85,15 +150,20 @@ $component: '#{base.$prefix}-textfield';
85
150
  background-color: t.alpha('on-surface', 0.04);
86
151
  pointer-events: none;
87
152
 
88
- & ~ .#{$component}-label {
153
+ & ~ .#{$component}-label,
154
+ & ~ .#{$component}-leading-icon,
155
+ & ~ .#{$component}-trailing-icon {
89
156
  color: t.color('on-surface');
90
157
  opacity: 0.38;
91
158
  }
92
159
  }
93
160
 
94
- // Helper text
161
+ // Helper text / Supporting text
95
162
  &-helper {
96
163
  @include m.typography('body-small');
164
+ position: absolute;
165
+ bottom: -18px;
166
+ left: 16px;
97
167
  margin-top: 4px;
98
168
  color: t.color('on-surface-variant');
99
169
 
@@ -127,6 +197,35 @@ $component: '#{base.$prefix}-textfield';
127
197
  margin-left: 0;
128
198
  margin-right: 4px;
129
199
  }
200
+
201
+ &-leading-icon {
202
+ left: auto;
203
+ right: 12px;
204
+ }
205
+
206
+ &-trailing-icon {
207
+ right: auto;
208
+ left: 12px;
209
+ }
210
+
211
+ &--with-leading-icon {
212
+ .#{$component}-label {
213
+ left: auto;
214
+ right: 44px;
215
+ }
216
+
217
+ .#{$component}-input {
218
+ padding-left: 16px;
219
+ padding-right: 44px;
220
+ }
221
+ }
222
+
223
+ &--with-trailing-icon {
224
+ .#{$component}-input {
225
+ padding-right: 16px;
226
+ padding-left: 44px;
227
+ }
228
+ }
130
229
  }
131
230
 
132
231
  // ===== FILLED VARIANT =====
@@ -139,10 +238,6 @@ $component: '#{base.$prefix}-textfield';
139
238
  @include m.motion-transition(background-color, border-color);
140
239
 
141
240
 
142
- &:focus {
143
- padding-bottom: 6px;
144
- }
145
-
146
241
  // Autofill styles for filled variant
147
242
  &:-webkit-autofill {
148
243
  border-radius: f.get-shape('extra-small') f.get-shape('extra-small') 0 0;
@@ -171,7 +266,7 @@ $component: '#{base.$prefix}-textfield';
171
266
  background-color: t.color('primary');
172
267
  border-radius: 0;
173
268
  pointer-events: none;
174
- transition: 0.3s opacity ease;
269
+ transition: 0.2s opacity ease;
175
270
  }
176
271
 
177
272
  // Populated field (not empty) or focused field label position
@@ -220,12 +315,68 @@ $component: '#{base.$prefix}-textfield';
220
315
  }
221
316
  }
222
317
 
318
+ // Icon adjustments for filled variant
319
+ &.#{$component}--with-leading-icon {
320
+ .#{$component}-input {
321
+ padding: 20px 16px 7px 44px;
322
+ }
323
+
324
+ .#{$component}-label {
325
+ left: 44px;
326
+ }
327
+
328
+ &:not(.#{$component}--empty) .#{$component}-label,
329
+ &.#{$component}--focused .#{$component}-label {
330
+ transform: translateY(-95%) scale(0.75);
331
+ // Keep the label aligned with input text when focused/filled
332
+ left: 44px;
333
+ }
334
+
335
+ .#{$component}-leading-icon {
336
+ top: 28px;
337
+ }
338
+ }
339
+
340
+ &.#{$component}--with-trailing-icon {
341
+ .#{$component}-input {
342
+ padding-right: 44px;
343
+ }
344
+
345
+ .#{$component}-trailing-icon {
346
+ top: 28px;
347
+ }
348
+ }
349
+
223
350
  // RTL support
224
351
  @include m.rtl {
225
352
  .#{$component}-label {
226
353
  left: auto;
227
354
  right: 16px;
228
355
  }
356
+
357
+ &.#{$component}--with-leading-icon {
358
+ .#{$component}-input {
359
+ padding: 20px 44px 7px 16px;
360
+ }
361
+
362
+ .#{$component}-label {
363
+ left: auto;
364
+ right: 44px;
365
+ }
366
+
367
+ &:not(.#{$component}--empty) .#{$component}-label,
368
+ &.#{$component}--focused .#{$component}-label {
369
+ // Keep the label aligned with input text when focused/filled in RTL
370
+ right: 44px;
371
+ }
372
+ }
373
+
374
+ &.#{$component}--with-trailing-icon {
375
+ .#{$component}-input {
376
+ padding-right: 16px;
377
+ padding-left: 44px;
378
+ }
379
+ }
229
380
  }
230
381
  }
231
382
 
@@ -270,10 +421,10 @@ $component: '#{base.$prefix}-textfield';
270
421
  opacity: 0;
271
422
  width: 100%;
272
423
  height: 100%;
273
- border: 2px solid t.color('primary');
424
+ border: 1.5px solid t.color('primary');
274
425
  border-radius: f.get-shape('extra-small');
275
426
  pointer-events: none;
276
- transition: 0.2s opacity ease;
427
+ transition: 0.1s opacity ease;
277
428
  }
278
429
 
279
430
  // Populated field (not empty) or focused field label position
@@ -282,7 +433,6 @@ $component: '#{base.$prefix}-textfield';
282
433
  padding: 0 4px;
283
434
  background-color: t.color('surface');
284
435
  transform: translateY(-147%) scale(0.75);
285
-
286
436
  }
287
437
 
288
438
  // Focus state
@@ -330,6 +480,29 @@ $component: '#{base.$prefix}-textfield';
330
480
  }
331
481
  }
332
482
 
483
+ // Icon adjustments for outlined variant
484
+ &.#{$component}--with-leading-icon {
485
+ .#{$component}-input {
486
+ padding-left: 44px;
487
+ }
488
+
489
+ .#{$component}-label {
490
+ left: 44px;
491
+ }
492
+
493
+ &:not(.#{$component}--empty) .#{$component}-label,
494
+ &.#{$component}--focused .#{$component}-label {
495
+ // For outlined variant, move label to default position
496
+ left: 13px;
497
+ }
498
+ }
499
+
500
+ &.#{$component}--with-trailing-icon {
501
+ .#{$component}-input {
502
+ padding-right: 44px;
503
+ }
504
+ }
505
+
333
506
  // RTL support
334
507
  @include m.rtl {
335
508
  &:not(.#{$component}--empty) .#{$component}-label,
@@ -345,6 +518,72 @@ $component: '#{base.$prefix}-textfield';
345
518
  &.#{$component}--error .#{$component}-label {
346
519
  right: 12px;
347
520
  }
521
+
522
+ &.#{$component}--with-leading-icon {
523
+ .#{$component}-input {
524
+ padding-left: 16px;
525
+ padding-right: 44px;
526
+ }
527
+
528
+ .#{$component}-label {
529
+ left: auto;
530
+ right: 44px;
531
+ }
532
+
533
+ &:not(.#{$component}--empty) .#{$component}-label,
534
+ &.#{$component}--focused .#{$component}-label {
535
+ // For outlined variant in RTL, move label to default position
536
+ right: 13px;
537
+ left: auto;
538
+ }
539
+ }
540
+
541
+ &.#{$component}--with-trailing-icon {
542
+ .#{$component}-input {
543
+ padding-right: 16px;
544
+ padding-left: 44px;
545
+ }
546
+ }
547
+ }
548
+ }
549
+
550
+ // Multiline styles
551
+ &--multiline {
552
+ .#{$component}-input {
553
+ min-height: 100px;
554
+ height: auto;
555
+ resize: vertical;
556
+ padding-top: 12px;
557
+ }
558
+
559
+ &--filled {
560
+ .#{$component}-input {
561
+
562
+ }
563
+ }
564
+
565
+ &--outlined {
566
+ .#{$component}-input {
567
+
568
+ }
569
+ }
570
+
571
+
572
+ .#{$component}-label {
573
+ top: 24px;
574
+ }
575
+ }
576
+
577
+
578
+ // Support for multiline inputs
579
+ &-input[type="multiline"] {
580
+ min-height: 100px;
581
+ resize: vertical;
582
+
583
+ & ~ .#{$component}-leading-icon,
584
+ & ~ .#{$component}-trailing-icon {
585
+ top: 20px;
586
+ transform: none;
348
587
  }
349
588
  }
350
589
  }
package/demo/build.ts DELETED
@@ -1,349 +0,0 @@
1
- // src/demo/build.ts
2
- import { mkdir } from 'fs/promises'
3
- import { existsSync, watch } from 'fs'
4
- import { join, dirname } from 'path'
5
- import { fileURLToPath } from 'url'
6
- import * as sass from 'sass'
7
-
8
- const __dirname = dirname(fileURLToPath(import.meta.url))
9
- const isWatch = process.argv.includes('--watch')
10
- const isProduction = process.argv.includes('--production') || process.env.NODE_ENV === 'production'
11
-
12
- // Define consistent output paths
13
- const DIST_DIR = join(__dirname, 'dist')
14
- const STYLES_DIR = join(DIST_DIR, 'styles')
15
- const JS_OUTPUT = join(DIST_DIR, 'bundle.js')
16
- const CSS_OUTPUT = join(STYLES_DIR, 'main.css')
17
- const HTML_SOURCE = join(__dirname, 'index.html')
18
- const HTML_OUTPUT = join(DIST_DIR, 'index.html')
19
-
20
- // Log build mode
21
- console.log(`Building in ${isProduction ? 'PRODUCTION' : 'DEVELOPMENT'} mode`)
22
-
23
- const compileSass = async () => {
24
- try {
25
- const inputFile = join(__dirname, '../src/styles/main.scss')
26
- const outputFile = CSS_OUTPUT
27
-
28
- console.log('┌─────────────────────────────────────────')
29
- console.log('│ SASS Compilation')
30
- console.log('│ Mode:', isProduction ? 'PRODUCTION' : 'DEVELOPMENT')
31
- console.log('│ Input:', inputFile)
32
- console.log('│ Output:', outputFile)
33
- console.log('│ Minify:', isProduction ? 'Yes' : 'No')
34
- console.log('└─────────────────────────────────────────')
35
-
36
- const result = await sass.compileAsync(inputFile, {
37
- loadPaths: [
38
- join(__dirname, '../node_modules'),
39
- join(__dirname, '../src/styles'),
40
- join(__dirname, '..') // Add root directory to help resolve paths
41
- ],
42
- style: isProduction ? 'compressed' : 'expanded',
43
- sourceMap: !isProduction
44
- })
45
-
46
- await mkdir(dirname(outputFile), { recursive: true })
47
- await Bun.write(outputFile, result.css)
48
-
49
- if (result.sourceMap && !isProduction) {
50
- await Bun.write(`${outputFile}.map`, JSON.stringify(result.sourceMap))
51
- }
52
-
53
- console.log('✓ SASS compilation successful')
54
- console.log(` Size: ${(result.css.length / 1024).toFixed(2)} KB`)
55
- } catch (error) {
56
- console.error('❌ SASS compilation failed:', error)
57
- if (error.span) {
58
- console.error(` Error in ${error.span.url}:${error.span.start.line}:${error.span.start.column}`)
59
- }
60
- }
61
- }
62
-
63
- const buildApp = async () => {
64
- try {
65
- console.log('┌─────────────────────────────────────────')
66
- console.log('│ JavaScript Build')
67
- console.log('│ Mode:', isProduction ? 'PRODUCTION' : 'DEVELOPMENT')
68
- console.log('│ Minify:', isProduction ? 'Yes' : 'No')
69
- console.log('│ Sourcemaps:', isProduction ? 'No' : 'Yes (inline)')
70
- console.log('└─────────────────────────────────────────')
71
-
72
- const jsResult = await Bun.build({
73
- entrypoints: [join(__dirname, 'main.js')],
74
- outdir: DIST_DIR,
75
- minify: isProduction, // Only minify in production
76
- sourcemap: isProduction ? 'none' : 'inline', // No sourcemaps in production
77
- format: 'esm',
78
- target: 'browser',
79
- naming: {
80
- entry: 'bundle.js'
81
- },
82
- // Add tree shaking in production
83
- tree: isProduction ? true : undefined,
84
- // Define production/development environment
85
- define: {
86
- 'process.env.NODE_ENV': isProduction ? '"production"' : '"development"'
87
- }
88
- })
89
-
90
- if (!jsResult.success) {
91
- console.error('❌ JavaScript build failed')
92
- console.error(jsResult.logs)
93
- return false
94
- }
95
-
96
- const jsSize = (await Bun.file(JS_OUTPUT).size) / 1024
97
- console.log('✓ JavaScript build successful')
98
- console.log(` Size: ${jsSize.toFixed(2)} KB`)
99
-
100
- return true
101
- } catch (error) {
102
- console.error('❌ JavaScript build error:', error)
103
- return false
104
- }
105
- }
106
-
107
- const copyStaticFiles = async () => {
108
- try {
109
- console.log('┌─────────────────────────────────────────')
110
- console.log('│ Copying Static Files')
111
- console.log('└─────────────────────────────────────────')
112
-
113
- // Copy HTML template
114
- await Bun.write(HTML_OUTPUT, await Bun.file(HTML_SOURCE).text())
115
- console.log('✓ Copied HTML template')
116
-
117
- // Add other static files here if needed
118
- // For example, copy images, fonts, etc.
119
-
120
- return true
121
- } catch (error) {
122
- console.error('❌ Error copying static files:', error)
123
- return false
124
- }
125
- }
126
-
127
- // Update timestamp file to trigger live reload when needed
128
- const updateReloadTimestamp = async () => {
129
- if (!isProduction) {
130
- const reloadDir = join(DIST_DIR)
131
- const reloadFile = join(reloadDir, 'reload')
132
- await mkdir(reloadDir, { recursive: true })
133
- await Bun.write(reloadFile, Date.now().toString())
134
- console.log('🔄 Browser reload triggered')
135
- }
136
- }
137
-
138
- const setupWatchers = () => {
139
- if (isProduction) {
140
- console.log('Watch mode not available in production build')
141
- return { watchJsFiles: () => {}, watchScssFiles: () => {}, watchHtmlFiles: () => {} }
142
- }
143
-
144
- const jsWatchPaths = [
145
- join(__dirname, 'main.js'),
146
- join(__dirname, '../src') // Watch the entire src directory for changes
147
- ]
148
-
149
- const scssWatchPaths = [
150
- join(__dirname, '../src/styles')
151
- ]
152
-
153
- const htmlWatchPaths = [
154
- join(__dirname, 'index.html')
155
- ]
156
-
157
- const watchJsFiles = () => {
158
- // Use a debounce mechanism to prevent duplicate builds
159
- let buildTimeout = null
160
- const debouncedBuild = (filename) => {
161
- if (buildTimeout) {
162
- clearTimeout(buildTimeout)
163
- }
164
- buildTimeout = setTimeout(async () => {
165
- console.log('\n📁 JavaScript file changed:', filename)
166
- const success = await buildApp()
167
- if (success) await updateReloadTimestamp()
168
- buildTimeout = null
169
- }, 100) // 100ms debounce time
170
- }
171
-
172
- jsWatchPaths.forEach(path => {
173
- if (existsSync(path)) {
174
- watch(path, { recursive: true }, (_, filename) => {
175
- if (filename?.endsWith('.js') || filename?.endsWith('.ts')) {
176
- debouncedBuild(filename)
177
- }
178
- })
179
- } else {
180
- console.warn(`⚠️ Watch path does not exist: ${path}`)
181
- }
182
- })
183
- }
184
-
185
- const watchScssFiles = () => {
186
- // Use a debounce mechanism to prevent duplicate compilations
187
- let compileTimeout = null
188
- const debouncedCompile = (filename) => {
189
- if (compileTimeout) {
190
- clearTimeout(compileTimeout)
191
- }
192
- compileTimeout = setTimeout(async () => {
193
- console.log('\n📁 SCSS file changed:', filename)
194
- await compileSass()
195
- await updateReloadTimestamp()
196
- compileTimeout = null
197
- }, 100) // 100ms debounce time
198
- }
199
-
200
- scssWatchPaths.forEach(path => {
201
- if (existsSync(path)) {
202
- watch(path, { recursive: true }, (_, filename) => {
203
- if (filename?.endsWith('.scss')) {
204
- debouncedCompile(filename)
205
- }
206
- })
207
- } else {
208
- console.warn(`⚠️ Watch path does not exist: ${path}`)
209
- }
210
- })
211
- }
212
-
213
- const watchHtmlFiles = () => {
214
- // Watch HTML files and copy them when changed
215
- let copyTimeout = null
216
- const debouncedCopy = (filename) => {
217
- if (copyTimeout) {
218
- clearTimeout(copyTimeout)
219
- }
220
- copyTimeout = setTimeout(async () => {
221
- console.log('\n📁 HTML file changed:', filename)
222
- await copyStaticFiles()
223
- await updateReloadTimestamp()
224
- copyTimeout = null
225
- }, 100) // 100ms debounce time
226
- }
227
-
228
- htmlWatchPaths.forEach(path => {
229
- if (existsSync(path)) {
230
- watch(path, { recursive: false }, (_, filename) => {
231
- if (filename?.endsWith('.html')) {
232
- debouncedCopy(filename)
233
- }
234
- })
235
- } else {
236
- console.warn(`⚠️ Watch path does not exist: ${path}`)
237
- }
238
- })
239
- }
240
-
241
- return {
242
- watchJsFiles,
243
- watchScssFiles,
244
- watchHtmlFiles
245
- }
246
- }
247
-
248
- const verifyOutput = async () => {
249
- // Check if output files exist
250
- const jsExists = existsSync(JS_OUTPUT)
251
- const cssExists = existsSync(CSS_OUTPUT)
252
- const htmlExists = existsSync(HTML_OUTPUT)
253
-
254
- console.log('┌─────────────────────────────────────────')
255
- console.log('│ Build Verification')
256
- console.log('│ JavaScript:', jsExists ? '✓ OK' : '❌ Missing')
257
- console.log('│ CSS:', cssExists ? '✓ OK' : '❌ Missing')
258
- console.log('│ HTML:', htmlExists ? '✓ OK' : '❌ Missing')
259
- console.log('└─────────────────────────────────────────')
260
-
261
- // For production builds, check file sizes
262
- if (isProduction && jsExists && cssExists) {
263
- const jsStats = await Bun.file(JS_OUTPUT).size
264
- const cssStats = await Bun.file(CSS_OUTPUT).size
265
- const totalSize = jsStats + cssStats
266
-
267
- console.log('┌─────────────────────────────────────────')
268
- console.log('│ Production Build Stats')
269
- console.log('│ JavaScript:', (jsStats / 1024).toFixed(2), 'KB')
270
- console.log('│ CSS:', (cssStats / 1024).toFixed(2), 'KB')
271
- console.log('│ Total Size:', (totalSize / 1024).toFixed(2), 'KB')
272
- console.log('└─────────────────────────────────────────')
273
- }
274
-
275
- return jsExists && cssExists && htmlExists
276
- }
277
-
278
- const cleanDist = async () => {
279
- try {
280
- console.log('🧹 Cleaning dist directory...')
281
-
282
- // Recreate the directories
283
- await mkdir(DIST_DIR, { recursive: true })
284
- await mkdir(STYLES_DIR, { recursive: true })
285
-
286
- console.log('✓ Dist directory cleaned')
287
- } catch (error) {
288
- console.error('❌ Error cleaning dist directory:', error)
289
- }
290
- }
291
-
292
- const build = async () => {
293
- try {
294
- const startTime = Date.now()
295
-
296
- console.log('┌───────────────────────────────────────────────')
297
- console.log('│ 🚀 MTRL Demo Build Process')
298
- console.log('│ Mode:', isProduction ? '🏭 PRODUCTION' : '🔧 DEVELOPMENT')
299
- console.log('│ Watch:', isWatch ? '✓ Enabled' : '✗ Disabled')
300
- console.log('└───────────────────────────────────────────────')
301
- console.log('')
302
-
303
- // Clean dist directory
304
- await cleanDist()
305
-
306
- // Create output directories
307
- await mkdir(DIST_DIR, { recursive: true })
308
- await mkdir(STYLES_DIR, { recursive: true })
309
-
310
- // Build JavaScript
311
- await buildApp()
312
-
313
- // Compile SASS to CSS
314
- await compileSass()
315
-
316
- // Copy static files
317
- await copyStaticFiles()
318
-
319
- // Verify output
320
- await verifyOutput()
321
-
322
- // Update reload timestamp
323
- await updateReloadTimestamp()
324
-
325
- const buildTime = ((Date.now() - startTime) / 1000).toFixed(2)
326
-
327
- if (isWatch && !isProduction) {
328
- console.log('')
329
- console.log('┌───────────────────────────────────────────────')
330
- console.log('│ 👀 Watching for changes...')
331
- console.log('└───────────────────────────────────────────────')
332
-
333
- const { watchJsFiles, watchScssFiles, watchHtmlFiles } = setupWatchers()
334
- watchJsFiles()
335
- watchScssFiles()
336
- watchHtmlFiles()
337
- } else {
338
- console.log('')
339
- console.log('┌───────────────────────────────────────────────')
340
- console.log(`│ ✅ Build completed in ${buildTime}s`)
341
- console.log('└───────────────────────────────────────────────')
342
- }
343
- } catch (error) {
344
- console.error('❌ Build failed with error:', error)
345
- process.exit(1)
346
- }
347
- }
348
-
349
- build()