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,5 +1,4 @@
1
- // src/components/slider/features/ui.ts - Updated track update methods
2
- import { SLIDER_ORIENTATIONS } from '../constants';
1
+ // src/components/slider/features/ui.ts
3
2
  import { SliderConfig } from '../types';
4
3
 
5
4
  /**
@@ -10,77 +9,83 @@ import { SliderConfig } from '../types';
10
9
  * @returns UI update helper methods
11
10
  */
12
11
  export const createUiHelpers = (config: SliderConfig, state) => {
13
- // Make sure state.component.structure exists
14
- if (!state.component || !state.component.structure) {
12
+ // Return empty implementations if component structure is missing
13
+ if (!state.component?.structure) {
15
14
  console.error('Cannot create UI helpers: component structure is missing');
16
- return {
17
- getPercentage: () => 0,
18
- getValueFromPosition: () => 0,
19
- roundToStep: (value) => value,
20
- clamp: (value, min, max) => Math.min(Math.max(value, min), max),
21
- setThumbPosition: () => {},
22
- updateActiveTrack: () => {},
23
- updateStartTrack: () => {},
24
- updateRemainingTrack: () => {},
25
- updateThumbPositions: () => {},
26
- updateValueBubbles: () => {},
27
- showValueBubble: () => {},
28
- generateTicks: () => {},
29
- updateTicks: () => {},
30
- updateUi: () => {}
31
- };
15
+ return Object.fromEntries(['getPercentage', 'getValueFromPosition', 'roundToStep',
16
+ 'clamp', 'setThumbPosition', 'updateActiveTrack', 'updateStartTrack',
17
+ 'updateRemainingTrack', 'updateThumbPositions', 'updateValueBubbles',
18
+ 'showValueBubble', 'generateTicks', 'updateTicks', 'updateUi']
19
+ .map(method => [method, method === 'clamp' ? (v, min, max) => Math.min(Math.max(v, min), max) :
20
+ method === 'roundToStep' ? v => v :
21
+ method === 'getPercentage' || method === 'getValueFromPosition' ? () => 0 :
22
+ () => {}]));
32
23
  }
33
24
 
34
25
  const {
35
- track,
36
- activeTrack,
37
- startTrack,
38
- remainingTrack,
39
- thumb,
40
- valueBubble,
41
- secondThumb,
42
- secondValueBubble
26
+ track, activeTrack, startTrack, remainingTrack, thumb,
27
+ valueBubble, secondThumb, secondValueBubble, ticksContainer
43
28
  } = state.component.structure;
44
29
 
45
30
  /**
46
31
  * Calculates percentage position for a value
47
- * @param value Value to convert to percentage
48
- * @returns Percentage position (0-100)
49
32
  */
50
33
  const getPercentage = (value) => {
51
34
  const range = state.max - state.min;
52
- if (range === 0) return 0;
53
- return ((value - state.min) / range) * 100;
35
+ return range === 0 ? 0 : ((value - state.min) / range) * 100;
36
+ };
37
+
38
+ /**
39
+ * Gets track dimensions and constraints for positioning calculations
40
+ */
41
+ const getTrackDimensions = () => {
42
+ if (!track || !thumb) return null;
43
+
44
+ const thumbRect = thumb.getBoundingClientRect();
45
+ const trackRect = track.getBoundingClientRect();
46
+ const thumbSize = thumbRect.width || 20;
47
+ const trackSize = trackRect.width;
48
+
49
+ const edgeConstraint = (thumbSize / 2) / trackSize * 100;
50
+ const paddingPercent = (8 / trackSize) * 100; // 8px padding
51
+
52
+ return { thumbSize, trackSize, edgeConstraint, paddingPercent };
53
+ };
54
+
55
+ /**
56
+ * Map value percentage to visual position with edge constraints
57
+ */
58
+ const mapValueToVisualPercent = (valuePercent, edgeConstraint) => {
59
+ const minEdge = edgeConstraint;
60
+ const maxEdge = 100 - edgeConstraint;
61
+ const visualRange = maxEdge - minEdge;
62
+
63
+ if (valuePercent <= 0) return minEdge;
64
+ if (valuePercent >= 100) return maxEdge;
65
+ return minEdge + (valuePercent / 100) * visualRange;
54
66
  };
55
67
 
56
68
  /**
57
69
  * Gets slider value from a position on the track
58
- * @param position Screen coordinate (clientX/clientY)
59
- * @param vertical Whether slider is vertical
60
- * @returns Calculated value
61
70
  */
62
- const getValueFromPosition = (position, vertical = false) => {
71
+ const getValueFromPosition = (position) => {
63
72
  if (!track) return state.min;
64
73
 
65
74
  try {
66
75
  const trackRect = track.getBoundingClientRect();
67
76
  const range = state.max - state.min;
68
77
 
69
- if (vertical) {
70
- const trackHeight = trackRect.height;
71
- // For vertical sliders, 0% is at the bottom, 100% at the top
72
- const percentageFromBottom = 1 - ((position - trackRect.top) / trackHeight);
73
- // Clamp percentage between 0 and 1
74
- const clampedPercentage = Math.max(0, Math.min(1, percentageFromBottom));
75
- return state.min + clampedPercentage * range;
76
- } else {
77
- const trackWidth = trackRect.width;
78
- // For horizontal sliders, 0% is at the left, 100% at the right
79
- const percentageFromLeft = (position - trackRect.left) / trackWidth;
80
- // Clamp percentage between 0 and 1
81
- const clampedPercentage = Math.max(0, Math.min(1, percentageFromLeft));
82
- return state.min + clampedPercentage * range;
83
- }
78
+ const thumbRect = thumb.getBoundingClientRect();
79
+ const thumbWidth = thumbRect.width || 20;
80
+
81
+ const leftEdge = trackRect.left + (thumbWidth / 2);
82
+ const rightEdge = trackRect.right - (thumbWidth / 2);
83
+ const effectiveWidth = rightEdge - leftEdge;
84
+
85
+ const adjustedPosition = Math.max(leftEdge, Math.min(rightEdge, position));
86
+ const percentageFromLeft = (adjustedPosition - leftEdge) / effectiveWidth;
87
+
88
+ return state.min + percentageFromLeft * range;
84
89
  } catch (error) {
85
90
  console.warn('Error calculating value from position:', error);
86
91
  return state.min;
@@ -89,8 +94,6 @@ export const createUiHelpers = (config: SliderConfig, state) => {
89
94
 
90
95
  /**
91
96
  * Rounds a value to the nearest step
92
- * @param value Value to round
93
- * @returns Rounded value
94
97
  */
95
98
  const roundToStep = (value) => {
96
99
  const step = state.step;
@@ -102,208 +105,140 @@ export const createUiHelpers = (config: SliderConfig, state) => {
102
105
 
103
106
  /**
104
107
  * Clamps a value between min and max
105
- * @param value Value to clamp
106
- * @param min Minimum allowed value
107
- * @param max Maximum allowed value
108
- * @returns Clamped value
109
108
  */
110
109
  const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
111
110
 
112
111
  /**
113
- * Sets thumb position based on a value percentage
114
- * @param thumbElement Thumb element to position
115
- * @param valueBubbleElement Value bubble element to position
116
- * @param valuePercent Percentage position (0-100)
112
+ * Sets thumb position based on a value percentage with proper edge mapping
117
113
  */
118
- const setThumbPosition = (thumbElement, valueBubbleElement, valuePercent) => {
119
- if (!thumbElement) return;
120
-
121
- if (config.orientation === SLIDER_ORIENTATIONS.VERTICAL) {
122
- // Fix for vertical slider - position from bottom using absolute positioning
123
- thumbElement.style.bottom = `${valuePercent}%`;
124
- thumbElement.style.left = '50%'; // Keep it centered horizontally
125
- thumbElement.style.top = 'auto'; // Clear top property to prevent conflicts
126
-
127
- if (valueBubbleElement) {
128
- valueBubbleElement.style.bottom = `${valuePercent}%`;
129
- valueBubbleElement.style.top = 'auto';
130
- }
131
- } else {
132
- thumbElement.style.left = `${valuePercent}%`;
133
- if (valueBubbleElement) {
134
- valueBubbleElement.style.left = `${valuePercent}%`;
135
- }
114
+ const setThumbPosition = (thumbElement, bubbleElement, valuePercent) => {
115
+ if (!thumbElement || !track) return;
116
+
117
+ const dims = getTrackDimensions();
118
+ if (!dims) return;
119
+
120
+ const { thumbSize, trackSize } = dims;
121
+ const edgeConstraint = (thumbSize / 2) / trackSize * 100;
122
+ const adjustedPercent = mapValueToVisualPercent(valuePercent, edgeConstraint);
123
+
124
+ thumbElement.style.left = `${adjustedPercent}%`;
125
+ thumbElement.style.transform = 'translate(-50%, -50%)';
126
+
127
+ if (bubbleElement) {
128
+ bubbleElement.style.left = `${adjustedPercent}%`;
129
+ bubbleElement.style.transform = 'translateX(-50%)';
136
130
  }
137
131
  };
138
-
139
-
140
132
 
141
- /**
142
- * Updates start track styles (before the first thumb for range slider)
143
- * This method now includes padding adjustments in the calculation
144
- */
145
- const updateStartTrack = () => {
146
- if (!startTrack) return;
147
-
148
- if (config.range && state.secondValue !== null) {
149
- // For range slider, calculate the track from start to first thumb
150
- const lowerValue = Math.min(state.value, state.secondValue);
151
- const lowerPercent = getPercentage(lowerValue);
133
+ /**
134
+ * Updates start track styles
135
+ */
136
+ const updateStartTrack = () => {
137
+ if (!startTrack || !track || !thumb) return;
152
138
 
153
- // Get track width for pixel calculations
154
- const trackRect = track.getBoundingClientRect();
155
- const isVertical = config.orientation === SLIDER_ORIENTATIONS.VERTICAL;
156
- const trackSize = isVertical ? trackRect.height : trackRect.width;
139
+ const dims = getTrackDimensions();
140
+ if (!dims) return;
157
141
 
158
- // Calculate width with adjustment for thumb spacing - subtract 8px equivalent
159
- const paddingAdjustment = 8; // 8px padding
160
- const paddingPercent = (paddingAdjustment / trackSize) * 100;
142
+ const { thumbSize, trackSize, paddingPercent } = dims;
161
143
 
162
- if (isVertical) {
163
- startTrack.style.display = 'block';
164
- startTrack.style.height = `${Math.max(0, lowerPercent - paddingPercent)}%`;
165
- startTrack.style.bottom = '0';
166
- startTrack.style.top = 'auto';
167
- startTrack.style.width = '100%';
168
- startTrack.style.left = '0';
169
- } else {
144
+ if (config.range && state.secondValue !== null) {
145
+ const lowerValue = Math.min(state.value, state.secondValue);
146
+ const lowerPercent = getPercentage(lowerValue);
147
+
148
+ const edgeConstraint = (thumbSize / 2) / trackSize * 100;
149
+ const adjustedPercent = mapValueToVisualPercent(lowerPercent, edgeConstraint);
150
+ const finalPercent = Math.max(0, adjustedPercent - paddingPercent);
151
+
170
152
  startTrack.style.display = 'block';
171
- startTrack.style.width = `${Math.max(0, lowerPercent - paddingPercent)}%`;
153
+ startTrack.style.width = `${finalPercent}%`;
172
154
  startTrack.style.left = '0';
173
155
  startTrack.style.height = '100%';
156
+ } else {
157
+ startTrack.style.display = 'none';
174
158
  }
175
- } else {
176
- // For single thumb slider, no start track needed
177
- startTrack.style.display = 'none';
178
- }
179
- };
159
+ };
180
160
 
181
- /**
182
- * Updates active track styles - with padding adjustments
183
- */
184
- const updateActiveTrack = () => {
185
- if (!activeTrack) return;
186
-
187
- // Get track width for pixel calculations
188
- const trackRect = track.getBoundingClientRect();
189
- const isVertical = config.orientation === SLIDER_ORIENTATIONS.VERTICAL;
190
- const trackSize = isVertical ? trackRect.height : trackRect.width;
191
-
192
- // Calculate padding adjustment
193
- const paddingAdjustment = 8; // 8px padding
194
- const paddingPercent = (paddingAdjustment / trackSize) * 100;
195
-
196
- if (config.range && state.secondValue !== null) {
197
- // Range slider (two thumbs)
198
- const lowerValue = Math.min(state.value, state.secondValue);
199
- const higherValue = Math.max(state.value, state.secondValue);
200
- const lowerPercent = getPercentage(lowerValue);
201
- const higherPercent = getPercentage(higherValue);
202
-
203
- // Adjust positions and width to account for spacing
204
- let adjustedLowerPercent = lowerPercent;
205
- let adjustedHigherPercent = higherPercent;
206
-
207
- if (higherPercent - lowerPercent > paddingPercent * 2) {
208
- // If there's enough space, add padding to both sides
209
- adjustedLowerPercent = lowerPercent + paddingPercent;
210
- adjustedHigherPercent = higherPercent - paddingPercent;
211
- }
212
-
213
- const trackLength = Math.max(0, adjustedHigherPercent - adjustedLowerPercent);
161
+ /**
162
+ * Updates active track styles
163
+ */
164
+ const updateActiveTrack = () => {
165
+ if (!activeTrack || !track || !thumb) return;
214
166
 
215
- if (isVertical) {
216
- activeTrack.style.display = 'block';
217
- activeTrack.style.height = `${trackLength}%`;
218
- activeTrack.style.bottom = `${adjustedLowerPercent}%`;
219
- activeTrack.style.top = 'auto';
220
- activeTrack.style.width = '100%';
221
- } else {
222
- activeTrack.style.display = 'block';
223
- activeTrack.style.width = `${trackLength}%`;
224
- activeTrack.style.left = `${adjustedLowerPercent}%`;
225
- activeTrack.style.height = '100%';
226
- }
227
- } else {
228
- // Single thumb slider
229
- const percent = getPercentage(state.value);
167
+ const dims = getTrackDimensions();
168
+ if (!dims) return;
230
169
 
231
- // For single slider, adjust for left padding
232
- const adjustedWidth = Math.max(0, percent - paddingPercent);
170
+ const { thumbSize, trackSize, paddingPercent } = dims;
171
+ const edgeConstraint = (thumbSize / 2) / trackSize * 100;
233
172
 
234
- if (isVertical) {
235
- activeTrack.style.display = 'block';
236
- activeTrack.style.height = `${adjustedWidth}%`;
237
- activeTrack.style.bottom = '0';
238
- activeTrack.style.top = 'auto';
239
- activeTrack.style.width = '100%';
173
+ if (config.range && state.secondValue !== null) {
174
+ // Range slider (two thumbs)
175
+ const lowerValue = Math.min(state.value, state.secondValue);
176
+ const higherValue = Math.max(state.value, state.secondValue);
177
+ const lowerPercent = getPercentage(lowerValue);
178
+ const higherPercent = getPercentage(higherValue);
179
+
180
+ const adjustedLower = mapValueToVisualPercent(lowerPercent, edgeConstraint) + paddingPercent;
181
+ const adjustedHigher = mapValueToVisualPercent(higherPercent, edgeConstraint) - paddingPercent;
182
+
183
+ // Calculate the actual percentage difference between thumbs
184
+ const valueDiffPercent = Math.abs(higherPercent - lowerPercent);
185
+
186
+ // Define a threshold below which we'll hide the active track
187
+ // This threshold is based on the thumb width plus some margin
188
+ const hideThreshold = (thumbSize / trackSize) * 100;
189
+
190
+ if (valueDiffPercent <= hideThreshold) {
191
+ // Thumbs are too close together, hide the active track
192
+ activeTrack.style.display = 'none';
193
+ } else {
194
+ // Normal display of active track
195
+ let trackLength = Math.max(0, adjustedHigher - adjustedLower);
196
+
197
+ activeTrack.style.display = 'block';
198
+ activeTrack.style.width = `${trackLength}%`;
199
+ activeTrack.style.left = `${adjustedLower}%`;
200
+ activeTrack.style.height = '100%';
201
+ }
240
202
  } else {
203
+ // Single thumb slider
204
+ const valuePercent = getPercentage(state.value);
205
+ const adjustedPercent = mapValueToVisualPercent(valuePercent, edgeConstraint);
206
+ const adjustedWidth = Math.max(0, adjustedPercent - paddingPercent);
207
+
241
208
  activeTrack.style.display = 'block';
242
209
  activeTrack.style.width = `${adjustedWidth}%`;
243
210
  activeTrack.style.left = '0';
244
211
  activeTrack.style.height = '100%';
245
212
  }
246
- }
247
- };
213
+ };
248
214
 
249
- /**
250
- * Updates remaining track styles - with padding adjustments
251
- */
252
- const updateRemainingTrack = () => {
253
- if (!remainingTrack) return;
254
-
255
- // Get track width for pixel calculations
256
- const trackRect = track.getBoundingClientRect();
257
- const isVertical = config.orientation === SLIDER_ORIENTATIONS.VERTICAL;
258
- const trackSize = isVertical ? trackRect.height : trackRect.width;
259
-
260
- // Calculate padding adjustment
261
- const paddingAdjustment = 8; // 8px padding
262
- const paddingPercent = (paddingAdjustment / trackSize) * 100;
263
-
264
- if (config.range && state.secondValue !== null) {
265
- // Range slider (two thumbs)
266
- const higherValue = Math.max(state.value, state.secondValue);
267
- const higherPercent = getPercentage(higherValue);
268
-
269
- // Adjust for right padding
270
- const adjustedPercent = higherPercent + paddingPercent;
271
- const adjustedWidth = Math.max(0, 100 - adjustedPercent);
272
-
273
- if (isVertical) {
274
- remainingTrack.style.display = 'block';
275
- remainingTrack.style.height = `${adjustedWidth}%`;
276
- remainingTrack.style.bottom = `${adjustedPercent}%`;
277
- remainingTrack.style.top = 'auto';
278
- remainingTrack.style.width = '100%';
279
- } else {
280
- remainingTrack.style.display = 'block';
281
- remainingTrack.style.width = `${adjustedWidth}%`;
282
- remainingTrack.style.left = `${adjustedPercent}%`;
283
- remainingTrack.style.height = '100%';
284
- }
285
- } else {
286
- // Single thumb slider
287
- const percent = getPercentage(state.value);
215
+ /**
216
+ * Updates remaining track styles
217
+ */
218
+ const updateRemainingTrack = () => {
219
+ if (!remainingTrack || !track || !thumb) return;
288
220
 
289
- // Adjust for right padding
290
- const adjustedPercent = percent + paddingPercent;
291
- const adjustedWidth = Math.max(0, 100 - adjustedPercent);
221
+ const dims = getTrackDimensions();
222
+ if (!dims) return;
292
223
 
293
- if (isVertical) {
294
- remainingTrack.style.display = 'block';
295
- remainingTrack.style.height = `${adjustedWidth}%`;
296
- remainingTrack.style.bottom = `${adjustedPercent}%`;
297
- remainingTrack.style.top = 'auto';
298
- remainingTrack.style.width = '100%';
299
- } else {
300
- remainingTrack.style.display = 'block';
301
- remainingTrack.style.width = `${adjustedWidth}%`;
302
- remainingTrack.style.left = `${adjustedPercent}%`;
303
- remainingTrack.style.height = '100%';
304
- }
305
- }
306
- };
224
+ const { thumbSize, trackSize, paddingPercent } = dims;
225
+ const edgeConstraint = (thumbSize / 2) / trackSize * 100;
226
+
227
+ // Find the highest thumb value
228
+ const highValue = config.range && state.secondValue !== null ?
229
+ Math.max(state.value, state.secondValue) : state.value;
230
+
231
+ const valuePercent = getPercentage(highValue);
232
+
233
+ // Map percentage to visual range
234
+ const adjustedPercent = mapValueToVisualPercent(valuePercent, edgeConstraint) + paddingPercent;
235
+ const remainingSize = Math.max(0, 100 - adjustedPercent);
236
+
237
+ remainingTrack.style.display = 'block';
238
+ remainingTrack.style.width = `${remainingSize}%`;
239
+ remainingTrack.style.left = `${adjustedPercent}%`;
240
+ remainingTrack.style.height = '100%';
241
+ };
307
242
 
308
243
  /**
309
244
  * Updates thumb positions
@@ -336,56 +271,48 @@ const updateRemainingTrack = () => {
336
271
 
337
272
  // Format the values
338
273
  const formatter = config.valueFormatter || (value => value.toString());
339
- const formattedValue = formatter(state.value);
340
-
341
- // Update main value bubble
342
- valueBubble.textContent = formattedValue;
343
274
 
344
- // Update second value bubble if range slider
275
+ // Update main and second value bubble if needed
276
+ valueBubble.textContent = formatter(state.value);
345
277
  if (config.range && secondValueBubble && state.secondValue !== null) {
346
- const formattedSecondValue = formatter(state.secondValue);
347
- secondValueBubble.textContent = formattedSecondValue;
278
+ secondValueBubble.textContent = formatter(state.secondValue);
348
279
  }
349
280
  };
350
281
 
351
282
  /**
352
283
  * Shows or hides value bubble
353
- * @param bubbleElement Value bubble element
354
- * @param show Whether to show the bubble
355
284
  */
356
285
  const showValueBubble = (bubbleElement, show) => {
357
286
  if (!bubbleElement || !config.showValue) return;
358
287
 
359
- if (show) {
360
- bubbleElement.classList.add(`${state.component.getClass('slider-value')}--visible`);
361
- } else {
362
- bubbleElement.classList.remove(`${state.component.getClass('slider-value')}--visible`);
363
- }
288
+ const visibleClass = `${state.component.getClass('slider-value')}--visible`;
289
+ bubbleElement.classList[show ? 'add' : 'remove'](visibleClass);
364
290
  };
365
291
 
366
292
  /**
367
- * Generates tick marks and labels
293
+ * Generates tick marks
368
294
  */
369
295
  const generateTicks = () => {
370
- // Remove existing ticks
371
- state.ticks.forEach(tick => {
372
- if (tick.parentNode) {
373
- tick.parentNode.removeChild(tick);
374
- }
375
- });
296
+ if (!ticksContainer) {
297
+ console.warn('Ticks container not found in component structure');
298
+ return;
299
+ }
376
300
 
377
- state.tickLabels.forEach(label => {
378
- if (label.parentNode) {
379
- label.parentNode.removeChild(label);
380
- }
381
- });
301
+ // Clear existing ticks
302
+ const sliderElement = state.component.element;
303
+ sliderElement.querySelectorAll(`.${state.component.getClass('slider-tick')}`)
304
+ .forEach(tick => tick.parentNode.removeChild(tick));
382
305
 
306
+ while (ticksContainer.firstChild) {
307
+ ticksContainer.removeChild(ticksContainer.firstChild);
308
+ }
309
+
310
+ // Reset ticks array
383
311
  state.ticks = [];
384
- state.tickLabels = [];
385
312
 
386
- if (!config.ticks && !config.tickLabels) return;
313
+ if (!config.ticks) return;
387
314
 
388
- // Generate new ticks
315
+ // Generate tick values
389
316
  const numSteps = Math.floor((state.max - state.min) / state.step);
390
317
  const tickValues = [];
391
318
 
@@ -401,58 +328,41 @@ const updateRemainingTrack = () => {
401
328
  tickValues.push(state.max);
402
329
  }
403
330
 
331
+ // CSS classes
332
+ const activeClass = `${state.component.getClass('slider-tick')}--active`;
333
+ const inactiveClass = `${state.component.getClass('slider-tick')}--inactive`;
334
+ const hiddenClass = `${state.component.getClass('slider-tick')}--hidden`;
335
+ const tickClass = state.component.getClass('slider-tick');
336
+
404
337
  // Create ticks
405
338
  tickValues.forEach(value => {
339
+ const percent = getPercentage(value);
340
+
341
+ // Create tick mark if enabled
406
342
  if (config.ticks) {
407
343
  const tick = document.createElement('div');
408
- tick.classList.add(state.component.getClass('slider-tick'));
344
+ tick.classList.add(tickClass);
409
345
 
410
346
  // Position tick
411
- const percent = getPercentage(value);
412
- if (config.orientation === SLIDER_ORIENTATIONS.VERTICAL) {
413
- tick.style.bottom = `${percent}%`;
414
- } else {
415
- tick.style.left = `${percent}%`;
416
- }
347
+ tick.style.left = `${percent}%`;
348
+
349
+ // Check if this tick should be hidden (matches exactly a selected value)
350
+ const isExactlySelected = value === state.value ||
351
+ (config.range && state.secondValue !== null && value === state.secondValue);
417
352
 
418
- // Set active class if value is in active range
419
- if (config.range && state.secondValue !== null) {
353
+ if (isExactlySelected) {
354
+ tick.classList.add(hiddenClass);
355
+ } else if (config.range && state.secondValue !== null) {
420
356
  const lowerValue = Math.min(state.value, state.secondValue);
421
357
  const higherValue = Math.max(state.value, state.secondValue);
422
358
 
423
- if (value >= lowerValue && value <= higherValue) {
424
- tick.classList.add(`${state.component.getClass('slider-tick')}--active`);
425
- }
426
- } else if (value <= state.value) {
427
- tick.classList.add(`${state.component.getClass('slider-tick')}--active`);
428
- }
429
-
430
- state.component.element.appendChild(tick);
431
- state.ticks.push(tick);
432
- }
433
-
434
- if (config.tickLabels) {
435
- const label = document.createElement('div');
436
- label.classList.add(state.component.getClass('slider-label'));
437
-
438
- // Position label
439
- const percent = getPercentage(value);
440
- if (config.orientation === SLIDER_ORIENTATIONS.VERTICAL) {
441
- label.style.bottom = `${percent}%`;
442
- } else {
443
- label.style.left = `${percent}%`;
444
- }
445
-
446
- // Set label text
447
- if (Array.isArray(config.tickLabels) && config.tickLabels[tickValues.indexOf(value)]) {
448
- label.textContent = config.tickLabels[tickValues.indexOf(value)];
359
+ tick.classList.add(value >= lowerValue && value <= higherValue ? activeClass : inactiveClass);
449
360
  } else {
450
- const formatter = config.valueFormatter || (value => value.toString());
451
- label.textContent = formatter(value);
361
+ tick.classList.add(value <= state.value ? activeClass : inactiveClass);
452
362
  }
453
363
 
454
- state.component.element.appendChild(label);
455
- state.tickLabels.push(label);
364
+ ticksContainer.appendChild(tick);
365
+ state.ticks.push(tick);
456
366
  }
457
367
  });
458
368
  };
@@ -461,26 +371,47 @@ const updateRemainingTrack = () => {
461
371
  * Updates active state of tick marks
462
372
  */
463
373
  const updateTicks = () => {
374
+ if (!state.ticks || state.ticks.length === 0) return;
375
+
376
+ const activeClass = `${state.component.getClass('slider-tick')}--active`;
377
+ const inactiveClass = `${state.component.getClass('slider-tick')}--inactive`;
378
+ const hiddenClass = `${state.component.getClass('slider-tick')}--hidden`;
379
+
464
380
  // Update active ticks based on current value
465
381
  state.ticks.forEach((tick, index) => {
382
+ // Calculate the value for this tick based on its index
466
383
  const tickValue = state.min + (index * state.step);
467
384
 
468
- if (config.range && state.secondValue !== null) {
469
- // Range slider - ticks between the two values should be active
385
+ // First, reset visibility
386
+ tick.classList.remove(hiddenClass);
387
+
388
+ // Check if this tick should be hidden (matches exactly a selected value)
389
+ const isExactlySelected = tickValue === state.value ||
390
+ (config.range && state.secondValue !== null && tickValue === state.secondValue);
391
+
392
+ if (isExactlySelected) {
393
+ // Hide this tick as it exactly matches a selected value
394
+ tick.classList.add(hiddenClass);
395
+ } else if (config.range && state.secondValue !== null) {
396
+ // Range slider - ticks between values should be active
470
397
  const lowerValue = Math.min(state.value, state.secondValue);
471
398
  const higherValue = Math.max(state.value, state.secondValue);
472
399
 
473
400
  if (tickValue >= lowerValue && tickValue <= higherValue) {
474
- tick.classList.add(`${state.component.getClass('slider-tick')}--active`);
401
+ tick.classList.add(activeClass);
402
+ tick.classList.remove(inactiveClass);
475
403
  } else {
476
- tick.classList.remove(`${state.component.getClass('slider-tick')}--active`);
404
+ tick.classList.remove(activeClass);
405
+ tick.classList.add(inactiveClass);
477
406
  }
478
407
  } else {
479
- // Single slider - ticks below or equal to value should be active
408
+ // Single slider - ticks below value should be active
480
409
  if (tickValue <= state.value) {
481
- tick.classList.add(`${state.component.getClass('slider-tick')}--active`);
410
+ tick.classList.add(activeClass);
411
+ tick.classList.remove(inactiveClass);
482
412
  } else {
483
- tick.classList.remove(`${state.component.getClass('slider-tick')}--active`);
413
+ tick.classList.remove(activeClass);
414
+ tick.classList.add(inactiveClass);
484
415
  }
485
416
  }
486
417
  });
@@ -491,7 +422,7 @@ const updateRemainingTrack = () => {
491
422
  */
492
423
  const updateUi = () => {
493
424
  updateThumbPositions();
494
- updateStartTrack(); // Call BEFORE updateActiveTrack
425
+ updateStartTrack();
495
426
  updateActiveTrack();
496
427
  updateRemainingTrack();
497
428
  updateValueBubbles();