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.
- package/index.ts +2 -0
- package/package.json +1 -1
- package/src/components/navigation/api.ts +131 -96
- package/src/components/navigation/features/controller.ts +273 -0
- package/src/components/navigation/features/items.ts +133 -64
- package/src/components/navigation/navigation.ts +17 -2
- package/src/components/navigation/system-types.ts +124 -0
- package/src/components/navigation/system.ts +776 -0
- package/src/components/slider/config.ts +20 -2
- package/src/components/slider/features/controller.ts +761 -0
- package/src/components/slider/features/handlers.ts +18 -15
- package/src/components/slider/features/index.ts +3 -2
- package/src/components/slider/features/range.ts +104 -0
- package/src/components/slider/slider.ts +34 -14
- package/src/components/slider/structure.ts +152 -0
- package/src/components/textfield/api.ts +53 -0
- package/src/components/textfield/features.ts +322 -0
- package/src/components/textfield/textfield.ts +8 -0
- package/src/components/textfield/types.ts +12 -3
- package/src/components/timepicker/clockdial.ts +1 -4
- package/src/core/compose/features/textinput.ts +15 -2
- package/src/core/composition/features/dom.ts +33 -0
- package/src/core/composition/features/icon.ts +131 -0
- package/src/core/composition/features/index.ts +11 -0
- package/src/core/composition/features/label.ts +156 -0
- package/src/core/composition/features/structure.ts +22 -0
- package/src/core/composition/index.ts +26 -0
- package/src/core/index.ts +1 -1
- package/src/core/structure.ts +288 -0
- package/src/index.ts +1 -0
- package/src/styles/components/_navigation-mobile.scss +244 -0
- package/src/styles/components/_navigation-system.scss +151 -0
- package/src/styles/components/_textfield.scss +250 -11
- package/demo/build.ts +0 -349
- package/demo/index.html +0 -110
- package/demo/main.js +0 -448
- package/demo/styles.css +0 -239
- package/server.ts +0 -86
- package/src/components/slider/features/slider.ts +0 -318
- package/src/components/slider/features/structure.ts +0 -181
- package/src/components/slider/features/ui.ts +0 -388
- 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.
|
|
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:
|
|
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.
|
|
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()
|