mtrl 0.0.2 → 0.1.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/package.json +2 -2
- package/src/components/button/styles.scss +198 -161
- package/src/components/checkbox/checkbox.js +4 -3
- package/src/components/checkbox/styles.scss +105 -55
- package/src/components/container/styles.scss +65 -58
- package/src/components/list/styles.scss +240 -11
- package/src/components/menu/features/items-manager.js +5 -1
- package/src/components/menu/styles.scss +37 -30
- package/src/components/navigation/constants.js +19 -54
- package/src/components/navigation/styles.scss +406 -6
- package/src/components/snackbar/styles.scss +46 -17
- package/src/components/switch/styles.scss +104 -40
- package/src/components/switch/switch.js +1 -1
- package/src/components/textfield/styles.scss +351 -5
- package/src/core/build/_ripple.scss +79 -0
- package/src/core/compose/features/disabled.js +27 -7
- package/src/core/compose/features/input.js +9 -1
- package/src/core/compose/features/textinput.js +16 -20
- package/src/core/dom/create.js +0 -1
- package/src/styles/abstract/_mixins.scss +9 -7
- package/src/styles/abstract/_theme.scss +157 -0
- package/src/styles/abstract/_variables.scss +72 -6
- package/src/styles/base/_reset.scss +86 -0
- package/src/styles/base/_typography.scss +155 -0
- package/src/styles/main.scss +104 -57
- package/src/styles/themes/_base-theme.scss +2 -27
- package/src/styles/themes/_baseline.scss +64 -39
- package/src/styles/utilities/_color.scss +154 -0
- package/src/styles/utilities/_flexbox.scss +194 -0
- package/src/styles/utilities/_spacing.scss +139 -0
- package/src/styles/utilities/_typography.scss +178 -0
- package/src/styles/utilities/_visibility.scss +142 -0
- package/test/components/button.test.js +46 -34
- package/test/components/checkbox.test.js +238 -0
- package/test/components/list.test.js +105 -0
- package/test/components/menu.test.js +385 -0
- package/test/components/navigation.test.js +227 -0
- package/test/components/snackbar.test.js +234 -0
- package/test/components/switch.test.js +186 -0
- package/test/components/textfield.test.js +314 -0
- package/test/core/ripple.test.js +21 -120
- package/test/setup.js +152 -239
- package/src/components/list/styles/_list-item.scss +0 -142
- package/src/components/list/styles/_list.scss +0 -89
- package/src/components/list/styles/_variables.scss +0 -13
- package/src/components/navigation/styles/_bar.scss +0 -51
- package/src/components/navigation/styles/_base.scss +0 -129
- package/src/components/navigation/styles/_drawer.scss +0 -169
- package/src/components/navigation/styles/_rail.scss +0 -65
- package/src/components/textfield/styles/base.scss +0 -107
- package/src/components/textfield/styles/filled.scss +0 -58
- package/src/components/textfield/styles/outlined.scss +0 -66
|
@@ -1,6 +1,352 @@
|
|
|
1
1
|
// src/components/textfield/styles.scss
|
|
2
|
-
@use '
|
|
3
|
-
@use '../../styles/abstract/
|
|
4
|
-
@use 'styles/
|
|
5
|
-
@use 'styles/
|
|
6
|
-
@use 'styles/
|
|
2
|
+
@use '../../styles/abstract/base' as base;
|
|
3
|
+
@use '../../styles/abstract/variables' as v;
|
|
4
|
+
@use '../../styles/abstract/functions' as f;
|
|
5
|
+
@use '../../styles/abstract/mixins' as m;
|
|
6
|
+
@use '../../styles/abstract/theme' as t;
|
|
7
|
+
|
|
8
|
+
// Define the component once
|
|
9
|
+
$component: '#{base.$prefix}-textfield';
|
|
10
|
+
|
|
11
|
+
// ===== BASE STYLES =====
|
|
12
|
+
.#{$component} {
|
|
13
|
+
position: relative;
|
|
14
|
+
display: inline-flex;
|
|
15
|
+
flex-direction: column;
|
|
16
|
+
min-width: 210px;
|
|
17
|
+
|
|
18
|
+
// Size variants
|
|
19
|
+
&--small {
|
|
20
|
+
.#{$component}-input {
|
|
21
|
+
height: 48px;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
&--large {
|
|
26
|
+
.#{$component}-input {
|
|
27
|
+
height: 64px;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Label
|
|
32
|
+
&-label {
|
|
33
|
+
@include m.typography('body-large');
|
|
34
|
+
user-select: none;
|
|
35
|
+
position: absolute;
|
|
36
|
+
left: 16px;
|
|
37
|
+
top: 50%;
|
|
38
|
+
transform: translateY(-50%);
|
|
39
|
+
transform-origin: left top;
|
|
40
|
+
pointer-events: none;
|
|
41
|
+
border-radius: 2px;
|
|
42
|
+
color: t.color('on-surface-variant');
|
|
43
|
+
transition: transform v.motion('duration-short4') v.motion('easing-emphasized'),
|
|
44
|
+
color v.motion('duration-short2') v.motion('easing-standard');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Input element
|
|
48
|
+
&-input {
|
|
49
|
+
@include m.typography('body-large');
|
|
50
|
+
@include m.shape('extra-small');
|
|
51
|
+
padding: 13px 16px;
|
|
52
|
+
width: 100%;
|
|
53
|
+
color: t.color('on-surface');
|
|
54
|
+
border: 0;
|
|
55
|
+
appearance: none;
|
|
56
|
+
outline: none;
|
|
57
|
+
|
|
58
|
+
&::placeholder {
|
|
59
|
+
color: transparent;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Autofill styles
|
|
63
|
+
&:-webkit-autofill {
|
|
64
|
+
-webkit-text-fill-color: t.color('on-surface');
|
|
65
|
+
transition: background-color 5000s ease-in-out 0s; // Long transition to keep the background
|
|
66
|
+
|
|
67
|
+
& ~ .#{$component}-label {
|
|
68
|
+
transform: translateY(-95%) scale(0.75);
|
|
69
|
+
background-color: t.color('surface');
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Firefox autofill
|
|
74
|
+
&:autofill {
|
|
75
|
+
color: t.color('on-surface');
|
|
76
|
+
|
|
77
|
+
& ~ .#{$component}-label {
|
|
78
|
+
transform: translateY(-95%) scale(0.75);
|
|
79
|
+
background-color: t.color('surface');
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Error state
|
|
85
|
+
&--error {
|
|
86
|
+
border-color: t.color('error');
|
|
87
|
+
|
|
88
|
+
.#{$component}-label {
|
|
89
|
+
color: t.color('error');
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Disabled state
|
|
94
|
+
&-input:disabled {
|
|
95
|
+
opacity: 0.38;
|
|
96
|
+
border-color: t.color('on-surface');
|
|
97
|
+
background-color: t.alpha('on-surface', 0.04);
|
|
98
|
+
pointer-events: none;
|
|
99
|
+
|
|
100
|
+
& ~ .#{$component}-label {
|
|
101
|
+
color: t.color('on-surface');
|
|
102
|
+
opacity: 0.38;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Helper text
|
|
107
|
+
&-helper {
|
|
108
|
+
@include m.typography('body-small');
|
|
109
|
+
margin-top: 4px;
|
|
110
|
+
color: t.color('on-surface-variant');
|
|
111
|
+
|
|
112
|
+
&--error {
|
|
113
|
+
color: t.color('error');
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Required indicator
|
|
118
|
+
&-required {
|
|
119
|
+
color: t.color('error');
|
|
120
|
+
margin-left: 4px;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Accessibility
|
|
124
|
+
@include m.reduced-motion {
|
|
125
|
+
&-label {
|
|
126
|
+
transition: none;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// RTL support
|
|
131
|
+
@include m.rtl {
|
|
132
|
+
&-label {
|
|
133
|
+
left: auto;
|
|
134
|
+
right: 16px;
|
|
135
|
+
transform-origin: right top;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
&-required {
|
|
139
|
+
margin-left: 0;
|
|
140
|
+
margin-right: 4px;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ===== FILLED VARIANT =====
|
|
145
|
+
&--filled {
|
|
146
|
+
border-bottom: 1px solid t.color('outline');
|
|
147
|
+
|
|
148
|
+
.#{$component}-input {
|
|
149
|
+
background-color: t.color('surface-container-highest');
|
|
150
|
+
padding: 20px 16px 7px;
|
|
151
|
+
border-radius: f.get-shape('extra-small') f.get-shape('extra-small') 0 0;
|
|
152
|
+
@include m.motion-transition(background-color, border-color);
|
|
153
|
+
|
|
154
|
+
&:focus {
|
|
155
|
+
padding-bottom: 6px;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Autofill styles for filled variant
|
|
159
|
+
&:-webkit-autofill {
|
|
160
|
+
border-radius: f.get-shape('extra-small') f.get-shape('extra-small') 0 0;
|
|
161
|
+
|
|
162
|
+
& ~ .#{$component}-label {
|
|
163
|
+
transform: translateY(-95%) scale(0.75);
|
|
164
|
+
color: t.color('on-surface-variant');
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
&:autofill {
|
|
169
|
+
& ~ .#{$component}-label {
|
|
170
|
+
transform: translateY(-95%) scale(0.75);
|
|
171
|
+
color: t.color('on-surface-variant');
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Populated field (not empty) or focused field label position
|
|
177
|
+
&:not(.#{$component}--empty) .#{$component}-label,
|
|
178
|
+
&.#{$component}--focused .#{$component}-label {
|
|
179
|
+
transform: translateY(-95%) scale(0.75);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Focus state
|
|
183
|
+
&.#{$component}--focused {
|
|
184
|
+
border-bottom: 2px solid t.color('primary');
|
|
185
|
+
|
|
186
|
+
.#{$component}-label {
|
|
187
|
+
color: t.color('primary');
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
&:hover {
|
|
191
|
+
border-bottom: 2px solid t.color('primary');
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Hover state
|
|
196
|
+
&:hover {
|
|
197
|
+
border-bottom: 1px solid t.color('primary');
|
|
198
|
+
|
|
199
|
+
.#{$component}-label {
|
|
200
|
+
color: t.color('primary');
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Error state
|
|
205
|
+
&.#{$component}--error {
|
|
206
|
+
border-bottom: 2px solid t.color('error');
|
|
207
|
+
|
|
208
|
+
.#{$component}-label {
|
|
209
|
+
color: t.color('error');
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
&:hover {
|
|
213
|
+
border-bottom: 2px solid t.color('error');
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
&.#{$component}--focused {
|
|
217
|
+
border-bottom: 2px solid t.color('error');
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Disabled state
|
|
222
|
+
&.#{$component}--disabled {
|
|
223
|
+
border-bottom-color: t.alpha('on-surface', 0.38);
|
|
224
|
+
pointer-events: none;
|
|
225
|
+
|
|
226
|
+
.#{$component}-input {
|
|
227
|
+
background-color: t.alpha('on-surface', 0.04);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// RTL support
|
|
232
|
+
@include m.rtl {
|
|
233
|
+
.#{$component}-label {
|
|
234
|
+
left: auto;
|
|
235
|
+
right: 16px;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// ===== OUTLINED VARIANT =====
|
|
241
|
+
&--outlined {
|
|
242
|
+
border: 1px solid t.color('outline');
|
|
243
|
+
border-radius: f.get-shape('extra-small');
|
|
244
|
+
@include m.motion-transition(border-color);
|
|
245
|
+
|
|
246
|
+
.#{$component}-input {
|
|
247
|
+
background-color: transparent;
|
|
248
|
+
padding: 13px 16px 14px;
|
|
249
|
+
@include m.motion-transition(padding);
|
|
250
|
+
|
|
251
|
+
// Autofill styles for outlined variant
|
|
252
|
+
&:-webkit-autofill {
|
|
253
|
+
border-radius: f.get-shape('extra-small');
|
|
254
|
+
|
|
255
|
+
& ~ .#{$component}-label {
|
|
256
|
+
background-color: t.color('surface');
|
|
257
|
+
transform: translateY(-145%) scale(0.75);
|
|
258
|
+
left: 13px;
|
|
259
|
+
padding: 0 4px;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
&:autofill {
|
|
264
|
+
& ~ .#{$component}-label {
|
|
265
|
+
background-color: t.color('surface');
|
|
266
|
+
transform: translateY(-145%) scale(0.75);
|
|
267
|
+
left: 13px;
|
|
268
|
+
padding: 0 4px;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Populated field (not empty) or focused field label position
|
|
274
|
+
&:not(.#{$component}--empty) .#{$component}-label,
|
|
275
|
+
&.#{$component}--focused .#{$component}-label {
|
|
276
|
+
background-color: t.color('surface');
|
|
277
|
+
transform: translateY(-145%) scale(0.75);
|
|
278
|
+
left: 13px;
|
|
279
|
+
padding: 0 4px;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Focus state
|
|
283
|
+
&.#{$component}--focused {
|
|
284
|
+
border: 2px solid t.color('primary');
|
|
285
|
+
|
|
286
|
+
.#{$component}-label {
|
|
287
|
+
color: t.color('primary');
|
|
288
|
+
border-radius: 2px;
|
|
289
|
+
left: 12px;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
.#{$component}-input {
|
|
293
|
+
padding: 12px 15px 13px;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
&:hover {
|
|
297
|
+
border: 2px solid t.color('primary');
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Hover state
|
|
302
|
+
&:hover {
|
|
303
|
+
border: 1px solid t.color('primary');
|
|
304
|
+
|
|
305
|
+
.#{$component}-label {
|
|
306
|
+
color: t.color('primary');
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Error state
|
|
311
|
+
&.#{$component}--error {
|
|
312
|
+
border: 2px solid t.color('error');
|
|
313
|
+
|
|
314
|
+
.#{$component}-label {
|
|
315
|
+
color: t.color('error');
|
|
316
|
+
left: 12px;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
.#{$component}-input {
|
|
320
|
+
padding: 12px 15px 13px;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
&:hover,
|
|
324
|
+
&.#{$component}--focused {
|
|
325
|
+
border: 2px solid t.color('error');
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Disabled state
|
|
330
|
+
&.#{$component}--disabled {
|
|
331
|
+
border-color: t.alpha('on-surface', 0.38);
|
|
332
|
+
pointer-events: none;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// RTL support
|
|
336
|
+
@include m.rtl {
|
|
337
|
+
&:not(.#{$component}--empty) .#{$component}-label,
|
|
338
|
+
&.#{$component}--focused .#{$component}-label {
|
|
339
|
+
left: auto;
|
|
340
|
+
right: 13px;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
&.#{$component}--focused .#{$component}-label {
|
|
344
|
+
right: 12px;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
&.#{$component}--error .#{$component}-label {
|
|
348
|
+
right: 12px;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// src/components/ripple/_ripple.scss
|
|
2
|
+
@use '../../styles/abstract/base' as base;
|
|
3
|
+
@use '../../styles/abstract/variables' as v;
|
|
4
|
+
@use '../../styles/abstract/functions' as f;
|
|
5
|
+
@use '../../styles/abstract/mixins' as m;
|
|
6
|
+
@use '../../styles/abstract/theme' as t;
|
|
7
|
+
|
|
8
|
+
$component: '#{base.$prefix}-ripple';
|
|
9
|
+
|
|
10
|
+
.#{$component} {
|
|
11
|
+
// Ripple container
|
|
12
|
+
position: absolute;
|
|
13
|
+
top: 0;
|
|
14
|
+
left: 0;
|
|
15
|
+
right: 0;
|
|
16
|
+
bottom: 0;
|
|
17
|
+
overflow: hidden;
|
|
18
|
+
border-radius: inherit;
|
|
19
|
+
pointer-events: none;
|
|
20
|
+
z-index: 0;
|
|
21
|
+
|
|
22
|
+
// Ripple element
|
|
23
|
+
&-wave {
|
|
24
|
+
position: absolute;
|
|
25
|
+
border-radius: 50%;
|
|
26
|
+
background-color: currentColor;
|
|
27
|
+
transform: scale(0);
|
|
28
|
+
opacity: 0;
|
|
29
|
+
pointer-events: none;
|
|
30
|
+
will-change: transform, opacity;
|
|
31
|
+
|
|
32
|
+
// Animation
|
|
33
|
+
transition-property: transform, opacity;
|
|
34
|
+
transition-duration: v.motion('duration-short4');
|
|
35
|
+
transition-timing-function: v.motion('easing-standard');
|
|
36
|
+
|
|
37
|
+
// Active ripple
|
|
38
|
+
&.active {
|
|
39
|
+
transform: scale(1);
|
|
40
|
+
opacity: v.state('hover-state-layer-opacity');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
&.fade-out {
|
|
44
|
+
opacity: 0;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Standalone utility for adding ripple to any element
|
|
50
|
+
[data-ripple] {
|
|
51
|
+
position: relative;
|
|
52
|
+
overflow: hidden;
|
|
53
|
+
|
|
54
|
+
&::after {
|
|
55
|
+
content: '';
|
|
56
|
+
position: absolute;
|
|
57
|
+
top: 0;
|
|
58
|
+
left: 0;
|
|
59
|
+
right: 0;
|
|
60
|
+
bottom: 0;
|
|
61
|
+
z-index: 0;
|
|
62
|
+
pointer-events: none;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Handle ripple color based on data attribute
|
|
66
|
+
&[data-ripple="light"]::after {
|
|
67
|
+
background-color: rgba(255, 255, 255, 0.3);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
&[data-ripple="dark"]::after {
|
|
71
|
+
background-color: rgba(0, 0, 0, 0.1);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Make content appear above ripple
|
|
75
|
+
> * {
|
|
76
|
+
position: relative;
|
|
77
|
+
z-index: 1;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// src/core/compose/features/disabled.js
|
|
1
2
|
|
|
2
3
|
/**
|
|
3
4
|
* Adds disabled state management to a component
|
|
@@ -5,22 +6,37 @@
|
|
|
5
6
|
* @returns {Function} Component enhancer
|
|
6
7
|
*/
|
|
7
8
|
export const withDisabled = (config = {}) => (component) => {
|
|
9
|
+
// Get the disabled class based on component name
|
|
10
|
+
const disabledClass = `${component.getClass(component.componentName)}--disabled`
|
|
11
|
+
|
|
8
12
|
// Directly implement disabled functionality
|
|
9
13
|
const disabled = {
|
|
10
14
|
enable () {
|
|
11
|
-
component.element.
|
|
12
|
-
component.
|
|
15
|
+
component.element.classList.remove(disabledClass)
|
|
16
|
+
if (component.input) {
|
|
17
|
+
component.input.disabled = false
|
|
18
|
+
component.input.removeAttribute('disabled')
|
|
19
|
+
} else {
|
|
20
|
+
component.element.disabled = false
|
|
21
|
+
component.element.removeAttribute('disabled')
|
|
22
|
+
}
|
|
13
23
|
return this
|
|
14
24
|
},
|
|
15
25
|
|
|
16
26
|
disable () {
|
|
17
|
-
component.element.
|
|
18
|
-
component.
|
|
27
|
+
component.element.classList.add(disabledClass)
|
|
28
|
+
if (component.input) {
|
|
29
|
+
component.input.disabled = true
|
|
30
|
+
component.input.setAttribute('disabled', 'true')
|
|
31
|
+
} else {
|
|
32
|
+
component.element.disabled = true
|
|
33
|
+
component.element.setAttribute('disabled', 'true')
|
|
34
|
+
}
|
|
19
35
|
return this
|
|
20
36
|
},
|
|
21
37
|
|
|
22
38
|
toggle () {
|
|
23
|
-
if (
|
|
39
|
+
if (this.isDisabled()) {
|
|
24
40
|
this.enable()
|
|
25
41
|
} else {
|
|
26
42
|
this.disable()
|
|
@@ -29,12 +45,16 @@ export const withDisabled = (config = {}) => (component) => {
|
|
|
29
45
|
},
|
|
30
46
|
|
|
31
47
|
isDisabled () {
|
|
32
|
-
return component.
|
|
48
|
+
return component.input ? component.input.disabled : component.element.disabled
|
|
33
49
|
}
|
|
34
50
|
}
|
|
35
51
|
|
|
52
|
+
// Initialize disabled state if configured
|
|
36
53
|
if (config.disabled) {
|
|
37
|
-
|
|
54
|
+
// Use requestAnimationFrame to ensure DOM is ready
|
|
55
|
+
requestAnimationFrame(() => {
|
|
56
|
+
disabled.disable()
|
|
57
|
+
})
|
|
38
58
|
}
|
|
39
59
|
|
|
40
60
|
return {
|
|
@@ -42,7 +42,15 @@ export const withInput = (config = {}) => (component) => {
|
|
|
42
42
|
|
|
43
43
|
Object.entries(attributes).forEach(([key, value]) => {
|
|
44
44
|
if (value !== null && value !== undefined) {
|
|
45
|
-
|
|
45
|
+
if (key === 'disabled' && value === true) {
|
|
46
|
+
input.disabled = true
|
|
47
|
+
input.setAttribute('disabled', 'true')
|
|
48
|
+
// Note: We don't add the class here because that's handled by withDisabled
|
|
49
|
+
} else if (value === true) {
|
|
50
|
+
input.setAttribute(key, key)
|
|
51
|
+
} else {
|
|
52
|
+
input.setAttribute(key, value)
|
|
53
|
+
}
|
|
46
54
|
}
|
|
47
55
|
})
|
|
48
56
|
|
|
@@ -35,30 +35,28 @@ export const withTextInput = (config = {}) => (component) => {
|
|
|
35
35
|
return isEmpty
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
// Detect
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
// Detect autofill using input events instead of animation
|
|
39
|
+
// This is more compatible with our testing environment
|
|
40
|
+
const handleAutofill = () => {
|
|
41
|
+
// Check for webkit autofill background
|
|
42
|
+
const isAutofilled =
|
|
43
|
+
input.matches(':-webkit-autofill') ||
|
|
44
|
+
// For Firefox and other browsers
|
|
45
|
+
(window.getComputedStyle(input).backgroundColor === 'rgb(250, 255, 189)' ||
|
|
46
|
+
window.getComputedStyle(input).backgroundColor === 'rgb(232, 240, 254)')
|
|
47
|
+
|
|
48
|
+
if (isAutofilled) {
|
|
42
49
|
component.element.classList.remove(`${component.getClass('textfield')}--empty`)
|
|
43
50
|
component.emit('input', { value: input.value, isEmpty: false, isAutofilled: true })
|
|
44
51
|
}
|
|
45
52
|
}
|
|
46
53
|
|
|
47
|
-
// Add required animation for autocomplete detection
|
|
48
|
-
const style = document.createElement('style')
|
|
49
|
-
style.textContent = `
|
|
50
|
-
@keyframes onAutoFillStart { from {} to {} }
|
|
51
|
-
.${component.getClass('textfield')}-input:-webkit-autofill {
|
|
52
|
-
animation-name: onAutoFillStart;
|
|
53
|
-
animation-duration: 1ms;
|
|
54
|
-
}
|
|
55
|
-
`
|
|
56
|
-
document.head.appendChild(style)
|
|
57
|
-
|
|
58
54
|
// Event listeners
|
|
59
55
|
input.addEventListener('focus', () => {
|
|
60
56
|
component.element.classList.add(`${component.getClass('textfield')}--focused`)
|
|
61
57
|
component.emit('focus', { isEmpty: updateInputState() })
|
|
58
|
+
// Also check for autofill on focus
|
|
59
|
+
setTimeout(handleAutofill, 100)
|
|
62
60
|
})
|
|
63
61
|
|
|
64
62
|
input.addEventListener('blur', () => {
|
|
@@ -74,8 +72,6 @@ export const withTextInput = (config = {}) => (component) => {
|
|
|
74
72
|
})
|
|
75
73
|
})
|
|
76
74
|
|
|
77
|
-
input.addEventListener('animationstart', handleAutocomplete)
|
|
78
|
-
|
|
79
75
|
// Initial state
|
|
80
76
|
updateInputState()
|
|
81
77
|
|
|
@@ -85,10 +81,10 @@ export const withTextInput = (config = {}) => (component) => {
|
|
|
85
81
|
if (component.lifecycle) {
|
|
86
82
|
const originalDestroy = component.lifecycle.destroy
|
|
87
83
|
component.lifecycle.destroy = () => {
|
|
88
|
-
input.removeEventListener('animationstart', handleAutocomplete)
|
|
89
|
-
style.remove()
|
|
90
84
|
input.remove()
|
|
91
|
-
originalDestroy
|
|
85
|
+
if (originalDestroy) {
|
|
86
|
+
originalDestroy.call(component.lifecycle)
|
|
87
|
+
}
|
|
92
88
|
}
|
|
93
89
|
}
|
|
94
90
|
|
package/src/core/dom/create.js
CHANGED
|
@@ -232,8 +232,8 @@ $icons: (
|
|
|
232
232
|
}
|
|
233
233
|
}
|
|
234
234
|
|
|
235
|
-
// Touch Target
|
|
236
235
|
@mixin touch-target($size: 48px) {
|
|
236
|
+
// Position first to avoid the deprecation warning
|
|
237
237
|
position: relative;
|
|
238
238
|
|
|
239
239
|
&::after {
|
|
@@ -244,6 +244,14 @@ $icons: (
|
|
|
244
244
|
height: $size;
|
|
245
245
|
transform: translate(-50%, -50%);
|
|
246
246
|
}
|
|
247
|
+
|
|
248
|
+
@media (prefers-reduced-motion: reduce) {
|
|
249
|
+
@content;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
@media (forced-colors: active) {
|
|
253
|
+
@content;
|
|
254
|
+
}
|
|
247
255
|
}
|
|
248
256
|
|
|
249
257
|
// Scrollbars
|
|
@@ -322,12 +330,6 @@ $icons: (
|
|
|
322
330
|
}
|
|
323
331
|
}
|
|
324
332
|
|
|
325
|
-
@mixin flex-center {
|
|
326
|
-
display: flex;
|
|
327
|
-
align-items: center;
|
|
328
|
-
justify-content: center;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
333
|
// Print
|
|
332
334
|
@mixin print {
|
|
333
335
|
@media print {
|