mtrl 0.2.5 → 0.2.6

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 (70) hide show
  1. package/package.json +1 -1
  2. package/src/components/badge/_styles.scss +9 -9
  3. package/src/components/button/_styles.scss +0 -56
  4. package/src/components/button/button.ts +0 -2
  5. package/src/components/button/constants.ts +0 -6
  6. package/src/components/button/index.ts +2 -2
  7. package/src/components/button/types.ts +1 -7
  8. package/src/components/card/_styles.scss +67 -25
  9. package/src/components/card/api.ts +54 -3
  10. package/src/components/card/card.ts +33 -2
  11. package/src/components/card/config.ts +143 -21
  12. package/src/components/card/constants.ts +20 -19
  13. package/src/components/card/content.ts +299 -2
  14. package/src/components/card/features.ts +155 -4
  15. package/src/components/card/index.ts +31 -9
  16. package/src/components/card/types.ts +138 -15
  17. package/src/components/chip/chip.ts +1 -9
  18. package/src/components/chip/constants.ts +0 -10
  19. package/src/components/chip/index.ts +1 -1
  20. package/src/components/chip/types.ts +1 -4
  21. package/src/components/progress/_styles.scss +0 -65
  22. package/src/components/progress/config.ts +1 -2
  23. package/src/components/progress/constants.ts +0 -14
  24. package/src/components/progress/index.ts +1 -1
  25. package/src/components/progress/progress.ts +1 -4
  26. package/src/components/progress/types.ts +1 -4
  27. package/src/components/radios/_styles.scss +0 -45
  28. package/src/components/radios/api.ts +85 -60
  29. package/src/components/radios/config.ts +1 -2
  30. package/src/components/radios/constants.ts +0 -9
  31. package/src/components/radios/index.ts +1 -1
  32. package/src/components/radios/radio.ts +34 -11
  33. package/src/components/radios/radios.ts +2 -1
  34. package/src/components/radios/types.ts +1 -7
  35. package/src/components/slider/_styles.scss +149 -155
  36. package/src/components/slider/accessibility.md +59 -0
  37. package/src/components/slider/config.ts +4 -6
  38. package/src/components/slider/features/disabled.ts +41 -16
  39. package/src/components/slider/features/interactions.ts +153 -18
  40. package/src/components/slider/features/keyboard.ts +127 -6
  41. package/src/components/slider/features/structure.ts +32 -5
  42. package/src/components/slider/features/ui.ts +18 -8
  43. package/src/components/tabs/_styles.scss +285 -155
  44. package/src/components/tabs/api.ts +178 -400
  45. package/src/components/tabs/config.ts +46 -52
  46. package/src/components/tabs/constants.ts +85 -8
  47. package/src/components/tabs/features.ts +401 -0
  48. package/src/components/tabs/index.ts +60 -3
  49. package/src/components/tabs/indicator.ts +225 -0
  50. package/src/components/tabs/responsive.ts +144 -0
  51. package/src/components/tabs/scroll-indicators.ts +149 -0
  52. package/src/components/tabs/state.ts +186 -0
  53. package/src/components/tabs/tab-api.ts +258 -0
  54. package/src/components/tabs/tab.ts +255 -0
  55. package/src/components/tabs/tabs.ts +50 -31
  56. package/src/components/tabs/types.ts +324 -128
  57. package/src/components/tabs/utils.ts +107 -0
  58. package/src/components/textfield/_styles.scss +0 -98
  59. package/src/components/textfield/config.ts +2 -3
  60. package/src/components/textfield/constants.ts +0 -14
  61. package/src/components/textfield/index.ts +2 -2
  62. package/src/components/textfield/textfield.ts +0 -2
  63. package/src/components/textfield/types.ts +1 -4
  64. package/src/core/compose/component.ts +1 -1
  65. package/src/core/compose/features/badge.ts +79 -0
  66. package/src/core/compose/features/index.ts +3 -1
  67. package/src/styles/abstract/_theme.scss +106 -2
  68. package/src/components/card/actions.ts +0 -48
  69. package/src/components/card/header.ts +0 -88
  70. package/src/components/card/media.ts +0 -52
@@ -5,6 +5,9 @@
5
5
  @use '../../styles/abstract/mixins' as m;
6
6
  @use '../../styles/abstract/theme' as t;
7
7
 
8
+ // $transform: transform 0.2s ease, width 0.2s ease, height 0.2s ease;
9
+ $transform: none;
10
+
8
11
  $component: '#{base.$prefix}-slider';
9
12
 
10
13
  .#{$component} {
@@ -17,6 +20,11 @@ $component: '#{base.$prefix}-slider';
17
20
  user-select: none;
18
21
  touch-action: none;
19
22
 
23
+ // Container should not have a focus outline
24
+ &:focus {
25
+ outline: none;
26
+ }
27
+
20
28
  &-track {
21
29
  position: relative;
22
30
  width: 100%;
@@ -33,8 +41,127 @@ $component: '#{base.$prefix}-slider';
33
41
  cursor: not-allowed;
34
42
  }
35
43
  }
36
-
37
- // Start track (new element for range slider - beginning to first thumb)
44
+
45
+ // Thumb - updated with T-shape and proper focus styles
46
+ &-thumb {
47
+ position: absolute;
48
+ top: 50%;
49
+ transform: translate(-50%, -50%);
50
+ cursor: pointer;
51
+ z-index: 4;
52
+ padding: 4px 0;
53
+ transition: $transform;
54
+ width: 16px;
55
+ height: 50px;
56
+ border-radius: 16px;
57
+ background-color: rgba(red, .0);
58
+
59
+ // Focus styles for keyboard navigation
60
+ &:focus {
61
+ outline: 2px solid t.color('outline');
62
+ }
63
+
64
+ // Class added when focused via keyboard
65
+ &--focused {
66
+ // Additional styling when focused
67
+ }
68
+
69
+ // Create a T-shaped thumb using pseudo-elements
70
+ &::before {
71
+ pointer-events: none;
72
+ content: '';
73
+ position: absolute;
74
+ width: 4px;
75
+ height: 40px;
76
+ background-color: t.color('primary');
77
+ border-radius: 2px;
78
+ left: 50%;
79
+ top: 50%;
80
+ transform: translate(-50%, -50%);
81
+ transition: background-color 0.15s ease, width 0.15s ease;
82
+
83
+ .#{$component}--secondary & {
84
+ background-color: t.color('secondary');
85
+ }
86
+
87
+ .#{$component}--tertiary & {
88
+ background-color: t.color('tertiary');
89
+ }
90
+
91
+ .#{$component}--error & {
92
+ background-color: t.color('error');
93
+ }
94
+
95
+ .#{$component}--disabled & {
96
+ background-color: t.color('on-surface');
97
+ opacity: 0.38;
98
+ }
99
+ }
100
+
101
+ // Enable pointer events on the pseudo-element
102
+ &::before {
103
+ pointer-events: auto;
104
+ }
105
+
106
+ // Make thumb line slimmer during dragging (feedback)
107
+ .#{$component}--dragging & {
108
+ &::before {
109
+ width: 3px;
110
+ }
111
+ }
112
+
113
+ // Hover state
114
+ &:hover::before {
115
+ background-color: t.color('primary');
116
+
117
+ .#{$component}--secondary & {
118
+ background-color: t.color('secondary');
119
+ }
120
+
121
+ .#{$component}--tertiary & {
122
+ background-color: t.color('tertiary');
123
+ }
124
+
125
+ .#{$component}--error & {
126
+ background-color: t.color('error');
127
+ }
128
+
129
+ .#{$component}--disabled & {
130
+ background-color: t.color('on-surface');
131
+ opacity: 0.38;
132
+ }
133
+ }
134
+
135
+ // Active state
136
+ &:active::before {
137
+ background-color: t.color('primary');
138
+ width: 3px; // Shrink width when active for visual feedback
139
+
140
+ .#{$component}--secondary & {
141
+ background-color: t.color('secondary');
142
+ }
143
+
144
+ .#{$component}--tertiary & {
145
+ background-color: t.color('tertiary');
146
+ }
147
+
148
+ .#{$component}--error & {
149
+ background-color: t.color('error');
150
+ }
151
+ }
152
+
153
+ // Disabled state
154
+ .#{$component}--disabled & {
155
+ cursor: not-allowed;
156
+
157
+ &::before {
158
+ opacity: 0.38;
159
+ background-color: t.color('on-surface');
160
+ }
161
+ }
162
+ }
163
+
164
+ // Start track (for range slider)
38
165
  &-start-track {
39
166
  position: absolute;
40
167
  top: 0;
@@ -42,7 +169,7 @@ $component: '#{base.$prefix}-slider';
42
169
  height: 100%;
43
170
  background-color: t.color('primary');
44
171
  transform-origin: left center;
45
- // transition: transform 0.1s ease, width 0.1s ease, height 0.1s ease;
172
+ transition: $transform;
46
173
  border-radius: 2px;
47
174
  opacity: 0.24;
48
175
  z-index: 1;
@@ -71,7 +198,7 @@ $component: '#{base.$prefix}-slider';
71
198
  height: 100%;
72
199
  background-color: t.color('primary');
73
200
  transform-origin: left center;
74
- // transition: transform 0.1s ease, width 0.1s ease, height 0.1s ease, left 0.1s ease, bottom 0.1s ease;
201
+ transition: $transform;
75
202
  border-radius: 2px;
76
203
  z-index: 1;
77
204
 
@@ -88,7 +215,7 @@ $component: '#{base.$prefix}-slider';
88
215
  }
89
216
  }
90
217
 
91
- // Remaining track (unfilled part) - new for MD3
218
+ // Remaining track (unfilled part)
92
219
  &-remaining-track {
93
220
  opacity: .24;
94
221
  position: absolute;
@@ -96,7 +223,7 @@ $component: '#{base.$prefix}-slider';
96
223
  height: 100%;
97
224
  background-color: t.color('primary');
98
225
  transform-origin: left center;
99
- // transition: transform 0.1s ease, width 0.1s ease, height 0.1s ease, left 0.1s ease, bottom 0.1s ease;
226
+ transition: $transform;
100
227
  border-radius: 2px;
101
228
  width: 100%;
102
229
  z-index: 0;
@@ -118,7 +245,7 @@ $component: '#{base.$prefix}-slider';
118
245
  }
119
246
  }
120
247
 
121
- // Dot elements for track ends (separate from thumbs)
248
+ // Dot elements for track ends (visual anchors for accessibility)
122
249
  &-dot {
123
250
  pointer-events: none;
124
251
  position: absolute;
@@ -132,7 +259,6 @@ $component: '#{base.$prefix}-slider';
132
259
  // Start dot
133
260
  &--start {
134
261
  left: 6px;
135
- // Color variants
136
262
  background-color: t.color('on-primary');
137
263
 
138
264
  .#{$component}--secondary & {
@@ -147,7 +273,6 @@ $component: '#{base.$prefix}-slider';
147
273
  // End dot
148
274
  &--end {
149
275
  right: 6px;
150
- // Color variants
151
276
  background-color: t.color('primary');
152
277
 
153
278
  .#{$component}--secondary & {
@@ -162,136 +287,10 @@ $component: '#{base.$prefix}-slider';
162
287
  .#{$component}--disabled & {
163
288
  opacity: 0.38;
164
289
  }
165
-
166
290
  }
167
291
 
168
- // Thumb - updated to MD3 style with T-shape
169
- &-thumb {
170
- position: absolute;
171
- top: 50%;
172
- transform: translate(-50%, -50%);
173
- cursor: pointer;
174
- z-index: 4;
175
- // transition: left 0.1s ease, bottom 0.1s ease;
176
- width: 16px;
177
- height: 100%;
178
-
179
- // Create a T-shaped thumb using pseudo-elements
180
- &::before {
181
- pointer-events: none;
182
- content: '';
183
- position: absolute;
184
- width: 4px;
185
- height: 40px;
186
- background-color: t.color('primary');
187
- border-radius: 2px;
188
- left: 50%;
189
- top: 50%;
190
- transform: translate(-50%, -50%);
191
- transition: background-color 0.15s ease, width 0.15s ease;
192
-
193
- .#{$component}--secondary & {
194
- background-color: t.color('secondary');
195
- }
196
-
197
- .#{$component}--tertiary & {
198
- background-color: t.color('tertiary');
199
- }
200
-
201
- .#{$component}--error & {
202
- background-color: t.color('error');
203
- }
204
-
205
- .#{$component}--disabled & {
206
- background-color: t.color('on-surface');
207
- opacity: 0.38;
208
- }
209
- }
210
-
211
- // Enable pointer events on the pseudo-elements
212
- &::before, &::after {
213
- pointer-events: auto;
214
- }
215
-
216
- // Make thumb line slimmer during dragging
217
- .#{$component}--dragging & {
218
- &::before {
219
- width: 3px;
220
- }
221
- }
222
-
223
- // Hover state
224
- &:hover::before, &:hover::after {
225
- background-color: t.color('primary');
226
-
227
- .#{$component}--secondary & {
228
- background-color: t.color('secondary');
229
- }
230
-
231
- .#{$component}--tertiary & {
232
- background-color: t.color('tertiary');
233
- }
234
-
235
- .#{$component}--error & {
236
- background-color: t.color('error');
237
- }
238
-
239
- .#{$component}--disabled & {
240
- background-color: t.color('on-surface');
241
- opacity: 0.38;
242
- }
243
- }
244
-
245
- // Focus state
246
- &:focus {
247
- outline: none;
248
- }
249
-
250
- &:focus::before, &:focus::after {
251
- background-color: t.color('primary');
252
-
253
- .#{$component}--secondary & {
254
- background-color: t.color('secondary');
255
- }
256
-
257
- .#{$component}--tertiary & {
258
- background-color: t.color('tertiary');
259
- }
260
-
261
- .#{$component}--error & {
262
- background-color: t.color('error');
263
- }
264
- }
265
-
266
- // Active state
267
- &:active::before, &:active::after {
268
- background-color: t.color('primary');
269
-
270
- .#{$component}--secondary & {
271
- background-color: t.color('secondary');
272
- }
273
-
274
- .#{$component}--tertiary & {
275
- background-color: t.color('tertiary');
276
- }
277
-
278
- .#{$component}--error & {
279
- background-color: t.color('error');
280
- }
281
- }
282
-
283
- // Disabled state
284
- .#{$component}--disabled & {
285
- cursor: not-allowed;
286
-
287
- &::before, &::after {
288
- opacity: 0.38;
289
- background-color: t.color('on-surface');
290
- }
291
- }
292
- }
293
292
 
294
- // Container for ticks in MD3 style
293
+ // Container for ticks
295
294
  &-ticks-container {
296
295
  position: absolute;
297
296
  width: 100%;
@@ -320,7 +319,6 @@ $component: '#{base.$prefix}-slider';
320
319
 
321
320
  // Active tick (filled)
322
321
  &--active {
323
- // background-color: white;
324
322
  background-color: t.color('on-primary');
325
323
 
326
324
  .#{$component}--secondary & {
@@ -351,7 +349,7 @@ $component: '#{base.$prefix}-slider';
351
349
  }
352
350
  }
353
351
 
354
- // Range slider styles (for two thumbs)
352
+ // Range slider styles
355
353
  &--range {
356
354
  .#{$component}-thumb {
357
355
  &:nth-of-type(1) {
@@ -365,24 +363,24 @@ $component: '#{base.$prefix}-slider';
365
363
  }
366
364
  }
367
365
 
368
- // Value bubble (shows current value when dragging)
366
+ // Value bubble - shows current value during interaction
369
367
  &-value {
370
368
  position: absolute;
371
369
  top: -40px;
372
370
  left: 0;
373
371
  background-color: t.color('on-surface');
374
372
  color: t.color('surface-container-highest');
375
- padding: 6px 10px;
373
+ padding: 6px 12px;
376
374
  border-radius: 21px;
377
- font-size: 12px;
378
- font-weight: 500;
375
+ font-size: 14px;
376
+ font-weight: 600;
379
377
  transform: translateX(-50%);
380
378
  visibility: hidden;
381
379
  opacity: 0;
382
380
  transition: opacity 0.2s ease, visibility 0s linear 0.2s;
383
381
  pointer-events: none;
384
382
 
385
- // Show value when dragging
383
+ // Show value when interacting
386
384
  &--visible {
387
385
  visibility: visible;
388
386
  opacity: 1;
@@ -406,11 +404,6 @@ $component: '#{base.$prefix}-slider';
406
404
  &::before {
407
405
  height: 34px;
408
406
  }
409
-
410
- &::after {
411
- width: 14px;
412
- height: 14px;
413
- }
414
407
  }
415
408
  }
416
409
 
@@ -421,16 +414,17 @@ $component: '#{base.$prefix}-slider';
421
414
  &::before {
422
415
  height: 48px;
423
416
  }
424
-
425
- &::after {
426
- width: 18px;
427
- height: 18px;
428
- }
429
417
  }
430
418
  }
431
419
 
432
- // For dragging state
420
+ // For dragging state - visual feedback
433
421
  &--dragging {
434
- // Additional styles when dragging will be applied to thumb::before
422
+ // No transitions during dragging for responsive feel
423
+ .#{$component}-start-track,
424
+ .#{$component}-active-track,
425
+ .#{$component}-remaining-track,
426
+ .#{$component}-thumb {
427
+ transition: none;
428
+ }
435
429
  }
436
430
  }
@@ -0,0 +1,59 @@
1
+ # Slider Accessibility Enhancements
2
+
3
+ ## Overview
4
+
5
+ Based on the provided accessibility requirements, the slider component has been enhanced to provide better keyboard navigation, visual feedback, and overall accessibility. The changes focus on ensuring that the slider meets Material Design accessibility standards and provides appropriate feedback based on input type.
6
+
7
+ ## Key Enhancements
8
+
9
+ ### Focus and Keyboard Navigation
10
+
11
+ - **Direct Thumb Focus**: The initial focus now lands directly on the thumb (not the container)
12
+ - **Visual Feedback**: Added a clear outline on focus to provide visual cues for keyboard users
13
+ - **Arrow Key Navigation**:
14
+ - Left/Right arrows change the value by one step
15
+ - Up/Down arrows also change the value (for consistency with other controls)
16
+ - Home/End keys jump to minimum/maximum values
17
+ - Page Up/Down for larger step increments
18
+ - Shift + arrows for faster navigation (10x step size)
19
+
20
+ ### Visual Feedback During Interaction
21
+
22
+ - **Thumb Shrinking**: The thumb width shrinks slightly during interaction to provide feedback
23
+ - **Value Display**:
24
+ - Value appears during interaction (touch, drag, mouse click, keyboard navigation)
25
+ - Value remains visible briefly after interaction ends (1.5 seconds)
26
+ - Value position updates to follow the thumb
27
+
28
+ ### Visual Anchors for Contrast
29
+
30
+ - **Track End Indicators**: Added dot elements at both ends of the track
31
+ - **Color Contrast**: Ensured sufficient contrast for all elements
32
+ - **Disabled State**: Properly indicated visually and via ARIA attributes
33
+
34
+ ## Implementation Details
35
+
36
+ 1. **Keyboard Handling**:
37
+ - Enhanced keyboard navigation with proper step calculations
38
+ - Added support for modifier keys (Shift)
39
+ - Set appropriate ARIA attributes for screen readers
40
+
41
+ 2. **Interaction Feedback**:
42
+ - Modified CSS to shrink thumb width during active states
43
+ - Enhanced value bubble display timing
44
+ - Improved touch and mouse event handling
45
+
46
+ 3. **Focus Management**:
47
+ - Set clear focus styles that work cross-browser
48
+ - Applied focus directly to interactive thumb elements
49
+ - Ensured focus outline is visible against various backgrounds
50
+
51
+ ## Keyboard Navigation Map
52
+
53
+ | Keys | Actions |
54
+ |------|---------|
55
+ | Tab | Moves focus to the slider handle |
56
+ | Arrows | Increase and decrease the value by one step |
57
+ | Shift + Arrows | Increase and decrease by 10x step size |
58
+ | Home / End | Set to minimum or maximum value |
59
+ | Page Up / Down | Increase/decrease by larger increments |
@@ -41,13 +41,11 @@ export const getElementConfig = (config: SliderConfig) => {
41
41
  return createElementConfig(config, {
42
42
  tag: 'div',
43
43
  attrs: {
44
- 'role': 'slider',
45
- 'tabindex': config.disabled ? -1 : 0,
44
+ // Accessibility improvement: Container is not focusable; only thumbs are
45
+ 'tabindex': '-1',
46
46
  'aria-disabled': config.disabled === true ? true : false,
47
- 'aria-valuemin': config.min,
48
- 'aria-valuemax': config.max,
49
- 'aria-valuenow': config.value,
50
- 'aria-orientation': 'horizontal'
47
+ // ARIA attributes will be set directly on thumbs instead
48
+ 'role': 'none',
51
49
  },
52
50
  className: config.class
53
51
  });
@@ -10,29 +10,54 @@ export const withDisabled = (config: SliderConfig) => component => {
10
10
  // Initial disabled state
11
11
  const isDisabled = config.disabled === true;
12
12
 
13
+ // Apply initial disabled state if needed
14
+ if (isDisabled && component.structure) {
15
+ setTimeout(() => {
16
+ disableComponent();
17
+ }, 0);
18
+ }
19
+
20
+ function disableComponent() {
21
+ component.element.classList.add(`${component.getClass('slider')}--disabled`);
22
+ component.element.setAttribute('aria-disabled', 'true');
23
+
24
+ // Ensure thumbs cannot receive focus when disabled
25
+ if (component.structure.thumb) {
26
+ component.structure.thumb.tabIndex = -1;
27
+ component.structure.thumb.setAttribute('aria-disabled', 'true');
28
+ }
29
+
30
+ if (config.range && component.structure.secondThumb) {
31
+ component.structure.secondThumb.tabIndex = -1;
32
+ component.structure.secondThumb.setAttribute('aria-disabled', 'true');
33
+ }
34
+ }
35
+
36
+ function enableComponent() {
37
+ component.element.classList.remove(`${component.getClass('slider')}--disabled`);
38
+ component.element.setAttribute('aria-disabled', 'false');
39
+
40
+ // Re-enable focus on thumbs
41
+ if (component.structure.thumb) {
42
+ component.structure.thumb.tabIndex = 0;
43
+ component.structure.thumb.setAttribute('aria-disabled', 'false');
44
+ }
45
+
46
+ if (config.range && component.structure.secondThumb) {
47
+ component.structure.secondThumb.tabIndex = 0;
48
+ component.structure.secondThumb.setAttribute('aria-disabled', 'false');
49
+ }
50
+ }
51
+
13
52
  return {
14
53
  ...component,
15
54
  disabled: {
16
55
  enable() {
17
- component.element.classList.remove(`${component.getClass('slider')}--disabled`);
18
- component.element.setAttribute('aria-disabled', 'false');
19
- component.element.tabIndex = 0;
20
- component.structure.thumb.tabIndex = 0;
21
-
22
- if (config.range && component.structure.secondThumb) {
23
- component.structure.secondThumb.tabIndex = 0;
24
- }
56
+ enableComponent();
25
57
  },
26
58
 
27
59
  disable() {
28
- component.element.classList.add(`${component.getClass('slider')}--disabled`);
29
- component.element.setAttribute('aria-disabled', 'true');
30
- component.element.tabIndex = -1;
31
- component.structure.thumb.tabIndex = -1;
32
-
33
- if (config.range && component.structure.secondThumb) {
34
- component.structure.secondThumb.tabIndex = -1;
35
- }
60
+ disableComponent();
36
61
  },
37
62
 
38
63
  isDisabled() {