mtrl 0.2.4 → 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 (76) hide show
  1. package/package.json +6 -3
  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 +193 -281
  36. package/src/components/slider/accessibility.md +59 -0
  37. package/src/components/slider/api.ts +36 -101
  38. package/src/components/slider/config.ts +29 -78
  39. package/src/components/slider/constants.ts +12 -8
  40. package/src/components/slider/features/appearance.ts +1 -47
  41. package/src/components/slider/features/disabled.ts +41 -16
  42. package/src/components/slider/features/interactions.ts +166 -26
  43. package/src/components/slider/features/keyboard.ts +125 -6
  44. package/src/components/slider/features/structure.ts +182 -195
  45. package/src/components/slider/features/ui.ts +234 -303
  46. package/src/components/slider/index.ts +11 -1
  47. package/src/components/slider/slider.ts +1 -1
  48. package/src/components/slider/types.ts +10 -25
  49. package/src/components/tabs/_styles.scss +285 -155
  50. package/src/components/tabs/api.ts +178 -400
  51. package/src/components/tabs/config.ts +46 -52
  52. package/src/components/tabs/constants.ts +85 -8
  53. package/src/components/tabs/features.ts +401 -0
  54. package/src/components/tabs/index.ts +60 -3
  55. package/src/components/tabs/indicator.ts +225 -0
  56. package/src/components/tabs/responsive.ts +144 -0
  57. package/src/components/tabs/scroll-indicators.ts +149 -0
  58. package/src/components/tabs/state.ts +186 -0
  59. package/src/components/tabs/tab-api.ts +258 -0
  60. package/src/components/tabs/tab.ts +255 -0
  61. package/src/components/tabs/tabs.ts +50 -31
  62. package/src/components/tabs/types.ts +324 -128
  63. package/src/components/tabs/utils.ts +107 -0
  64. package/src/components/textfield/_styles.scss +0 -98
  65. package/src/components/textfield/config.ts +2 -3
  66. package/src/components/textfield/constants.ts +0 -14
  67. package/src/components/textfield/index.ts +2 -2
  68. package/src/components/textfield/textfield.ts +0 -2
  69. package/src/components/textfield/types.ts +1 -4
  70. package/src/core/compose/component.ts +1 -1
  71. package/src/core/compose/features/badge.ts +79 -0
  72. package/src/core/compose/features/index.ts +3 -1
  73. package/src/styles/abstract/_theme.scss +106 -2
  74. package/src/components/card/actions.ts +0 -48
  75. package/src/components/card/header.ts +0 -88
  76. package/src/components/card/media.ts +0 -52
@@ -1,251 +1,134 @@
1
- // src/components/slider/features/structure.ts - Fixed track initialization
2
- import { SLIDER_COLORS, SLIDER_SIZES, SLIDER_ORIENTATIONS } from '../constants';
1
+ // src/components/slider/features/structure.ts
2
+ import { SLIDER_COLORS, SLIDER_SIZES } from '../constants';
3
3
  import { SliderConfig } from '../types';
4
4
 
5
5
  /**
6
- * Creates the slider DOM structure following MD3 principles
6
+ * Creates the slider DOM structure following MD3 principles with improved accessibility
7
7
  * @param config Slider configuration
8
8
  * @returns Component enhancer with DOM structure
9
9
  */
10
10
  export const withStructure = (config: SliderConfig) => component => {
11
- // Create track element
12
- const track = document.createElement('div');
13
- track.classList.add(component.getClass('slider-track'));
14
-
15
- // Calculate initial percentages based on values
11
+ // Set default values
16
12
  const min = config.min || 0;
17
13
  const max = config.max || 100;
18
14
  const range = max - min;
19
-
20
- // Set default values
21
15
  const value = config.value !== undefined ? config.value : min;
22
16
  const secondValue = config.secondValue !== undefined ? config.secondValue : null;
17
+ const isRangeSlider = config.range && secondValue !== null;
18
+ const isDisabled = config.disabled === true;
23
19
 
24
- // Calculate percentages
20
+ // Helper function to calculate percentage
25
21
  const getPercentage = (val) => ((val - min) / range) * 100;
26
22
  const valuePercent = getPercentage(value);
27
23
 
28
- // Create remaining track element (fills entire width initially)
29
- const remainingTrack = document.createElement('div');
30
- remainingTrack.classList.add(component.getClass('slider-remaining-track'));
24
+ // Create track element and segments
25
+ const track = createElement('slider-track');
26
+ const remainingTrack = createElement('slider-remaining-track');
27
+ const startTrack = createElement('slider-start-track');
28
+ const activeTrack = createElement('slider-active-track');
31
29
 
32
- // Create start track element (for range slider)
33
- const startTrack = document.createElement('div');
34
- startTrack.classList.add(component.getClass('slider-start-track'));
30
+ // Create ticks container
31
+ const ticksContainer = createElement('slider-ticks-container');
35
32
 
36
- // Create active track element (filled part)
37
- const activeTrack = document.createElement('div');
38
- activeTrack.classList.add(component.getClass('slider-active-track'));
39
-
40
- // Calculate padding adjustment (8px equivalent as percentage)
41
- // We'll do a rough estimate initially, then recalculate once rendered
42
- const paddingAdjustment = 8; // 8px padding
43
- const estimatedTrackSize = 300; // A reasonable guess at track width
44
- const paddingPercent = (paddingAdjustment / estimatedTrackSize) * 100;
33
+ // Create dots for track ends
34
+ const startDot = createElement('slider-dot');
35
+ startDot.classList.add(component.getClass('slider-dot--start'));
45
36
 
46
- // Set initial dimensions for all track segments
47
- if (config.range && secondValue !== null) {
48
- // Range slider
49
- const lowerValue = Math.min(value, secondValue);
50
- const higherValue = Math.max(value, secondValue);
51
- const lowerPercent = getPercentage(lowerValue);
52
- const higherPercent = getPercentage(higherValue);
53
-
54
- // Adjust positions and width to account for spacing
55
- let adjustedLowerPercent = lowerPercent + paddingPercent;
56
- let adjustedHigherPercent = higherPercent - paddingPercent;
57
-
58
- if (adjustedHigherPercent <= adjustedLowerPercent) {
59
- adjustedLowerPercent = (lowerPercent + higherPercent) / 2 - 1;
60
- adjustedHigherPercent = (lowerPercent + higherPercent) / 2 + 1;
61
- }
62
-
63
- // Calculate track segment sizes
64
- const startWidth = Math.max(0, lowerPercent - paddingPercent);
65
- const activeWidth = Math.max(0, adjustedHigherPercent - adjustedLowerPercent);
66
- const remainingWidth = Math.max(0, 100 - higherPercent - paddingPercent);
67
-
68
- if (config.orientation === SLIDER_ORIENTATIONS.VERTICAL) {
69
- // Vertical orientation
70
- startTrack.style.display = 'block';
71
- startTrack.style.height = `${startWidth}%`;
72
- startTrack.style.bottom = '0';
73
- startTrack.style.width = '100%';
74
-
75
- activeTrack.style.display = 'block';
76
- activeTrack.style.height = `${activeWidth}%`;
77
- activeTrack.style.bottom = `${adjustedLowerPercent}%`;
78
- activeTrack.style.width = '100%';
79
-
80
- remainingTrack.style.display = 'block';
81
- remainingTrack.style.height = `${remainingWidth}%`;
82
- remainingTrack.style.bottom = `${higherPercent + paddingPercent}%`;
83
- remainingTrack.style.width = '100%';
84
- } else {
85
- // Horizontal orientation
86
- startTrack.style.display = 'block';
87
- startTrack.style.width = `${startWidth}%`;
88
- startTrack.style.left = '0';
89
- startTrack.style.height = '100%';
90
-
91
- activeTrack.style.display = 'block';
92
- activeTrack.style.width = `${activeWidth}%`;
93
- activeTrack.style.left = `${adjustedLowerPercent}%`;
94
- activeTrack.style.height = '100%';
95
-
96
- remainingTrack.style.display = 'block';
97
- remainingTrack.style.width = `${remainingWidth}%`;
98
- remainingTrack.style.left = `${higherPercent + paddingPercent}%`;
99
- remainingTrack.style.height = '100%';
100
- }
101
- } else {
102
- // Single thumb slider
103
- const adjustedWidth = Math.max(0, valuePercent - paddingPercent);
104
- const remainingWidth = Math.max(0, 100 - valuePercent - paddingPercent);
105
-
106
- if (config.orientation === SLIDER_ORIENTATIONS.VERTICAL) {
107
- // Vertical orientation
108
- startTrack.style.display = 'none';
109
-
110
- activeTrack.style.display = 'block';
111
- activeTrack.style.height = `${adjustedWidth}%`;
112
- activeTrack.style.bottom = '0';
113
- activeTrack.style.width = '100%';
114
-
115
- remainingTrack.style.display = 'block';
116
- remainingTrack.style.height = `${remainingWidth}%`;
117
- remainingTrack.style.bottom = `${valuePercent + paddingPercent}%`;
118
- remainingTrack.style.width = '100%';
119
- } else {
120
- // Horizontal orientation
121
- startTrack.style.display = 'none';
122
-
123
- activeTrack.style.display = 'block';
124
- activeTrack.style.width = `${adjustedWidth}%`;
125
- activeTrack.style.left = '0';
126
- activeTrack.style.height = '100%';
127
-
128
- remainingTrack.style.display = 'block';
129
- remainingTrack.style.width = `${remainingWidth}%`;
130
- remainingTrack.style.left = `${valuePercent + paddingPercent}%`;
131
- remainingTrack.style.height = '100%';
132
- }
133
- }
37
+ const endDot = createElement('slider-dot');
38
+ endDot.classList.add(component.getClass('slider-dot--end'));
134
39
 
135
- // Add tracks to container
136
- track.appendChild(remainingTrack);
137
- track.appendChild(startTrack);
138
- track.appendChild(activeTrack);
40
+ // Create value bubble and format the value
41
+ const formatter = config.valueFormatter || (val => val.toString());
42
+ const valueBubble = createElement('slider-value');
43
+ valueBubble.textContent = formatter(value);
139
44
 
140
- // Create thumb element
141
- const thumb = document.createElement('div');
142
- thumb.classList.add(component.getClass('slider-thumb'));
143
- thumb.setAttribute('tabindex', '0');
45
+ // Create thumb element with improved accessibility attributes
46
+ const thumb = createElement('slider-thumb');
144
47
  thumb.setAttribute('role', 'slider');
145
-
146
- // Set initial thumb position
147
- if (config.orientation === SLIDER_ORIENTATIONS.VERTICAL) {
148
- thumb.style.bottom = `${valuePercent}%`;
149
- thumb.style.left = '50%';
150
- thumb.style.top = 'auto';
151
- } else {
152
- thumb.style.left = `${valuePercent}%`;
48
+ thumb.setAttribute('aria-valuemin', String(min));
49
+ thumb.setAttribute('aria-valuemax', String(max));
50
+ thumb.setAttribute('aria-valuenow', String(value));
51
+ thumb.setAttribute('aria-orientation', 'horizontal');
52
+
53
+ // Set tabindex based on disabled state
54
+ thumb.setAttribute('tabindex', isDisabled ? '-1' : '0');
55
+ if (isDisabled) {
56
+ thumb.setAttribute('aria-disabled', 'true');
153
57
  }
154
58
 
155
- // Create dots for the track ends
156
- const startDot = document.createElement('div');
157
- startDot.classList.add(component.getClass('slider-dot'));
158
- startDot.classList.add(component.getClass('slider-dot--start'));
159
-
160
- const endDot = document.createElement('div');
161
- endDot.classList.add(component.getClass('slider-dot'));
162
- endDot.classList.add(component.getClass('slider-dot--end'));
163
-
164
- // Create value bubble element
165
- const valueBubble = document.createElement('div');
166
- valueBubble.classList.add(component.getClass('slider-value'));
59
+ // Set initial thumb position
60
+ thumb.style.left = `${valuePercent}%`;
167
61
 
168
- // Format value and set initial bubble text
169
- const formatter = config.valueFormatter || (val => val.toString());
170
- valueBubble.textContent = formatter(value);
62
+ // Calculate padding adjustment (8px equivalent as percentage)
63
+ const paddingAdjustment = 8; // 8px padding
64
+ const estimatedTrackSize = 300; // A reasonable guess at track width
65
+ const paddingPercent = (paddingAdjustment / estimatedTrackSize) * 100;
171
66
 
172
- // For range slider: Create second thumb and value bubble
67
+ // Create second thumb and value bubble for range slider
173
68
  let secondThumb = null;
174
69
  let secondValueBubble = null;
175
70
 
176
- if (config.range && secondValue !== null) {
177
- // Create second thumb
178
- secondThumb = document.createElement('div');
179
- secondThumb.classList.add(component.getClass('slider-thumb'));
180
- secondThumb.setAttribute('tabindex', '0');
71
+ if (isRangeSlider) {
72
+ secondThumb = createElement('slider-thumb');
181
73
  secondThumb.setAttribute('role', 'slider');
74
+ secondThumb.setAttribute('aria-valuemin', String(min));
75
+ secondThumb.setAttribute('aria-valuemax', String(max));
76
+ secondThumb.setAttribute('aria-valuenow', String(secondValue));
77
+ secondThumb.setAttribute('aria-orientation', 'horizontal');
182
78
 
183
- // Set initial second thumb position
184
- const secondPercent = getPercentage(secondValue);
185
- if (config.orientation === SLIDER_ORIENTATIONS.VERTICAL) {
186
- secondThumb.style.bottom = `${secondPercent}%`;
187
- secondThumb.style.left = '50%';
188
- secondThumb.style.top = 'auto';
189
- } else {
190
- secondThumb.style.left = `${secondPercent}%`;
79
+ // Set tabindex based on disabled state
80
+ secondThumb.setAttribute('tabindex', isDisabled ? '-1' : '0');
81
+ if (isDisabled) {
82
+ secondThumb.setAttribute('aria-disabled', 'true');
191
83
  }
192
84
 
193
- // Create second value bubble
194
- secondValueBubble = document.createElement('div');
195
- secondValueBubble.classList.add(component.getClass('slider-value'));
85
+ const secondPercent = getPercentage(secondValue);
86
+ secondThumb.style.left = `${secondPercent}%`;
87
+
88
+ secondValueBubble = createElement('slider-value');
196
89
  secondValueBubble.textContent = formatter(secondValue);
197
90
  }
198
91
 
92
+ // Set initial track segment dimensions
93
+ setupInitialTrackSegments();
94
+
95
+ // Add tracks to container
96
+ track.appendChild(remainingTrack);
97
+ track.appendChild(startTrack);
98
+ track.appendChild(activeTrack);
99
+
199
100
  // Add elements to the slider
200
101
  component.element.classList.add(component.getClass('slider'));
102
+
103
+ // Accessibility enhancement: Container is not focusable
104
+ component.element.setAttribute('tabindex', '-1');
105
+
106
+ // Set container aria attributes
107
+ component.element.setAttribute('role', 'none');
108
+ component.element.setAttribute('aria-disabled', isDisabled ? 'true' : 'false');
109
+
201
110
  component.element.appendChild(track);
111
+ component.element.appendChild(ticksContainer); // Add ticks container
202
112
  component.element.appendChild(startDot);
203
113
  component.element.appendChild(endDot);
204
114
  component.element.appendChild(thumb);
205
115
  component.element.appendChild(valueBubble);
206
116
 
207
- if (config.range && secondThumb && secondValueBubble) {
117
+ if (isRangeSlider && secondThumb && secondValueBubble) {
208
118
  component.element.classList.add(`${component.getClass('slider')}--range`);
209
119
  component.element.appendChild(secondThumb);
210
120
  component.element.appendChild(secondValueBubble);
211
121
  }
212
122
 
213
- // Apply size class
214
- const size = config.size || SLIDER_SIZES.MEDIUM;
215
- if (size !== SLIDER_SIZES.MEDIUM) {
216
- component.element.classList.add(`${component.getClass('slider')}--${size}`);
217
- }
218
-
219
- // Apply color class
220
- const color = config.color || SLIDER_COLORS.PRIMARY;
221
- if (color !== SLIDER_COLORS.PRIMARY) {
222
- component.element.classList.add(`${component.getClass('slider')}--${color}`);
223
- }
224
-
225
- // Apply orientation class
226
- const orientation = config.orientation || SLIDER_ORIENTATIONS.HORIZONTAL;
227
- if (orientation === SLIDER_ORIENTATIONS.VERTICAL) {
228
- component.element.classList.add(`${component.getClass('slider')}--vertical`);
229
- }
230
-
231
- // Apply discrete class if step is specified
232
- if (config.step !== undefined && config.step > 0) {
233
- component.element.classList.add(`${component.getClass('slider')}--discrete`);
234
- }
123
+ // Apply styling classes
124
+ applyStyleClasses();
235
125
 
236
- // Apply disabled class if needed
237
- if (config.disabled) {
238
- component.element.classList.add(`${component.getClass('slider')}--disabled`);
239
- }
240
-
241
- // Ensure proper initialization after DOM is attached by scheduling a UI update
126
+ // Schedule UI update after DOM is attached
242
127
  setTimeout(() => {
243
- if (component.slider && typeof component.slider.updateUi === 'function') {
244
- component.slider.updateUi();
245
- }
128
+ component.slider?.updateUi?.();
246
129
  }, 0);
247
130
 
248
- // Store elements in component
131
+ // Return enhanced component with structure
249
132
  return {
250
133
  ...component,
251
134
  structure: {
@@ -253,6 +136,7 @@ export const withStructure = (config: SliderConfig) => component => {
253
136
  activeTrack,
254
137
  startTrack,
255
138
  remainingTrack,
139
+ ticksContainer,
256
140
  thumb,
257
141
  valueBubble,
258
142
  secondThumb,
@@ -261,4 +145,107 @@ export const withStructure = (config: SliderConfig) => component => {
261
145
  endDot
262
146
  }
263
147
  };
148
+
149
+ /**
150
+ * Creates DOM element with slider class
151
+ * @param className Base class name
152
+ * @returns DOM element
153
+ */
154
+ function createElement(className) {
155
+ const element = document.createElement('div');
156
+ element.classList.add(component.getClass(className));
157
+ return element;
158
+ }
159
+
160
+ /**
161
+ * Sets up initial track segment positions and dimensions
162
+ */
163
+ function setupInitialTrackSegments() {
164
+ if (isRangeSlider) {
165
+ // Range slider with two thumbs
166
+ const lowerValue = Math.min(value, secondValue);
167
+ const higherValue = Math.max(value, secondValue);
168
+ const lowerPercent = getPercentage(lowerValue);
169
+ const higherPercent = getPercentage(higherValue);
170
+
171
+ // Adjust positions to account for spacing
172
+ let adjustedLowerPercent = lowerPercent + paddingPercent;
173
+ let adjustedHigherPercent = higherPercent - paddingPercent;
174
+
175
+ // Handle case when thumbs are very close
176
+ if (adjustedHigherPercent <= adjustedLowerPercent) {
177
+ adjustedLowerPercent = (lowerPercent + higherPercent) / 2 - 1;
178
+ adjustedHigherPercent = (lowerPercent + higherPercent) / 2 + 1;
179
+ }
180
+
181
+ // Calculate segment sizes
182
+ const startWidth = Math.max(0, lowerPercent - paddingPercent);
183
+ const activeWidth = Math.max(0, adjustedHigherPercent - adjustedLowerPercent);
184
+ const remainingWidth = Math.max(0, 100 - higherPercent - paddingPercent);
185
+
186
+ // Set styles
187
+ startTrack.style.display = 'block';
188
+ activeTrack.style.display = 'block';
189
+ remainingTrack.style.display = 'block';
190
+
191
+ // Horizontal orientation
192
+ setTrackStyles(startTrack, startWidth, 0);
193
+ setTrackStyles(activeTrack, activeWidth, adjustedLowerPercent);
194
+ setTrackStyles(remainingTrack, remainingWidth, higherPercent + paddingPercent);
195
+ } else {
196
+ // Single thumb slider
197
+ const adjustedWidth = Math.max(0, valuePercent - paddingPercent);
198
+ const remainingWidth = Math.max(0, 100 - valuePercent - paddingPercent);
199
+
200
+ // Hide start track for single thumb
201
+ startTrack.style.display = 'none';
202
+ activeTrack.style.display = 'block';
203
+ remainingTrack.style.display = 'block';
204
+
205
+ // Horizontal orientation
206
+ setTrackStyles(activeTrack, adjustedWidth, 0);
207
+ setTrackStyles(remainingTrack, remainingWidth, valuePercent + paddingPercent);
208
+ }
209
+ }
210
+
211
+ /**
212
+ * Sets styles for track segments
213
+ * @param element Track segment element
214
+ * @param width Width as percentage
215
+ * @param left Left position as percentage
216
+ */
217
+ function setTrackStyles(element, width, left) {
218
+ element.style.width = `${width}%`;
219
+ element.style.left = `${left}%`;
220
+ element.style.height = '100%';
221
+ }
222
+
223
+ /**
224
+ * Applies style classes based on configuration
225
+ */
226
+ function applyStyleClasses() {
227
+ const baseClass = component.getClass('slider');
228
+
229
+ // Apply size class
230
+ const size = config.size || SLIDER_SIZES.MEDIUM;
231
+ if (size !== SLIDER_SIZES.MEDIUM) {
232
+ component.element.classList.add(`${baseClass}--${size}`);
233
+ }
234
+
235
+ // Apply color class
236
+ const color = config.color || SLIDER_COLORS.PRIMARY;
237
+ if (color !== SLIDER_COLORS.PRIMARY) {
238
+ component.element.classList.add(`${baseClass}--${color}`);
239
+ }
240
+
241
+ // Apply discrete class if step is specified
242
+ if (config.step !== undefined && config.step > 0) {
243
+ component.element.classList.add(`${baseClass}--discrete`);
244
+ }
245
+
246
+ // Apply disabled class if needed
247
+ if (isDisabled) {
248
+ component.element.classList.add(`${baseClass}--disabled`);
249
+ }
250
+ }
264
251
  };