mtrl 0.2.4 → 0.2.5

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.
@@ -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,130 @@ 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
- }
161
+ /**
162
+ * Updates active track styles
163
+ */
164
+ const updateActiveTrack = () => {
165
+ if (!activeTrack || !track || !thumb) return;
212
166
 
213
- const trackLength = Math.max(0, adjustedHigherPercent - adjustedLowerPercent);
167
+ const dims = getTrackDimensions();
168
+ if (!dims) return;
214
169
 
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 {
170
+ const { thumbSize, trackSize, paddingPercent } = dims;
171
+ const edgeConstraint = (thumbSize / 2) / trackSize * 100;
172
+
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
+ let trackLength = Math.max(0, adjustedHigher - adjustedLower);
184
+ if (higherPercent - lowerPercent < paddingPercent * 2) {
185
+ trackLength = Math.max(0, higherPercent - lowerPercent);
186
+ }
187
+
222
188
  activeTrack.style.display = 'block';
223
189
  activeTrack.style.width = `${trackLength}%`;
224
- activeTrack.style.left = `${adjustedLowerPercent}%`;
190
+ activeTrack.style.left = `${adjustedLower}%`;
225
191
  activeTrack.style.height = '100%';
226
- }
227
- } else {
228
- // Single thumb slider
229
- const percent = getPercentage(state.value);
230
-
231
- // For single slider, adjust for left padding
232
- const adjustedWidth = Math.max(0, percent - paddingPercent);
233
-
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%';
240
192
  } else {
193
+ // Single thumb slider
194
+ const valuePercent = getPercentage(state.value);
195
+ const adjustedPercent = mapValueToVisualPercent(valuePercent, edgeConstraint);
196
+ const adjustedWidth = Math.max(0, adjustedPercent - paddingPercent);
197
+
241
198
  activeTrack.style.display = 'block';
242
199
  activeTrack.style.width = `${adjustedWidth}%`;
243
200
  activeTrack.style.left = '0';
244
201
  activeTrack.style.height = '100%';
245
202
  }
246
- }
247
- };
203
+ };
248
204
 
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);
205
+ /**
206
+ * Updates remaining track styles
207
+ */
208
+ const updateRemainingTrack = () => {
209
+ if (!remainingTrack || !track || !thumb) return;
288
210
 
289
- // Adjust for right padding
290
- const adjustedPercent = percent + paddingPercent;
291
- const adjustedWidth = Math.max(0, 100 - adjustedPercent);
211
+ const dims = getTrackDimensions();
212
+ if (!dims) return;
292
213
 
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
- };
214
+ const { thumbSize, trackSize, paddingPercent } = dims;
215
+ const edgeConstraint = (thumbSize / 2) / trackSize * 100;
216
+
217
+ // Find the highest thumb value
218
+ const highValue = config.range && state.secondValue !== null ?
219
+ Math.max(state.value, state.secondValue) : state.value;
220
+
221
+ const valuePercent = getPercentage(highValue);
222
+
223
+ // Map percentage to visual range
224
+ const adjustedPercent = mapValueToVisualPercent(valuePercent, edgeConstraint) + paddingPercent;
225
+ const remainingSize = Math.max(0, 100 - adjustedPercent);
226
+
227
+ remainingTrack.style.display = 'block';
228
+ remainingTrack.style.width = `${remainingSize}%`;
229
+ remainingTrack.style.left = `${adjustedPercent}%`;
230
+ remainingTrack.style.height = '100%';
231
+ };
307
232
 
308
233
  /**
309
234
  * Updates thumb positions
@@ -336,56 +261,48 @@ const updateRemainingTrack = () => {
336
261
 
337
262
  // Format the values
338
263
  const formatter = config.valueFormatter || (value => value.toString());
339
- const formattedValue = formatter(state.value);
340
-
341
- // Update main value bubble
342
- valueBubble.textContent = formattedValue;
343
264
 
344
- // Update second value bubble if range slider
265
+ // Update main and second value bubble if needed
266
+ valueBubble.textContent = formatter(state.value);
345
267
  if (config.range && secondValueBubble && state.secondValue !== null) {
346
- const formattedSecondValue = formatter(state.secondValue);
347
- secondValueBubble.textContent = formattedSecondValue;
268
+ secondValueBubble.textContent = formatter(state.secondValue);
348
269
  }
349
270
  };
350
271
 
351
272
  /**
352
273
  * Shows or hides value bubble
353
- * @param bubbleElement Value bubble element
354
- * @param show Whether to show the bubble
355
274
  */
356
275
  const showValueBubble = (bubbleElement, show) => {
357
276
  if (!bubbleElement || !config.showValue) return;
358
277
 
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
- }
278
+ const visibleClass = `${state.component.getClass('slider-value')}--visible`;
279
+ bubbleElement.classList[show ? 'add' : 'remove'](visibleClass);
364
280
  };
365
281
 
366
282
  /**
367
- * Generates tick marks and labels
283
+ * Generates tick marks
368
284
  */
369
285
  const generateTicks = () => {
370
- // Remove existing ticks
371
- state.ticks.forEach(tick => {
372
- if (tick.parentNode) {
373
- tick.parentNode.removeChild(tick);
374
- }
375
- });
286
+ if (!ticksContainer) {
287
+ console.warn('Ticks container not found in component structure');
288
+ return;
289
+ }
376
290
 
377
- state.tickLabels.forEach(label => {
378
- if (label.parentNode) {
379
- label.parentNode.removeChild(label);
380
- }
381
- });
291
+ // Clear existing ticks
292
+ const sliderElement = state.component.element;
293
+ sliderElement.querySelectorAll(`.${state.component.getClass('slider-tick')}`)
294
+ .forEach(tick => tick.parentNode.removeChild(tick));
382
295
 
296
+ while (ticksContainer.firstChild) {
297
+ ticksContainer.removeChild(ticksContainer.firstChild);
298
+ }
299
+
300
+ // Reset ticks array
383
301
  state.ticks = [];
384
- state.tickLabels = [];
385
302
 
386
- if (!config.ticks && !config.tickLabels) return;
303
+ if (!config.ticks) return;
387
304
 
388
- // Generate new ticks
305
+ // Generate tick values
389
306
  const numSteps = Math.floor((state.max - state.min) / state.step);
390
307
  const tickValues = [];
391
308
 
@@ -401,58 +318,41 @@ const updateRemainingTrack = () => {
401
318
  tickValues.push(state.max);
402
319
  }
403
320
 
321
+ // CSS classes
322
+ const activeClass = `${state.component.getClass('slider-tick')}--active`;
323
+ const inactiveClass = `${state.component.getClass('slider-tick')}--inactive`;
324
+ const hiddenClass = `${state.component.getClass('slider-tick')}--hidden`;
325
+ const tickClass = state.component.getClass('slider-tick');
326
+
404
327
  // Create ticks
405
328
  tickValues.forEach(value => {
329
+ const percent = getPercentage(value);
330
+
331
+ // Create tick mark if enabled
406
332
  if (config.ticks) {
407
333
  const tick = document.createElement('div');
408
- tick.classList.add(state.component.getClass('slider-tick'));
334
+ tick.classList.add(tickClass);
409
335
 
410
336
  // 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
- }
337
+ tick.style.left = `${percent}%`;
338
+
339
+ // Check if this tick should be hidden (matches exactly a selected value)
340
+ const isExactlySelected = value === state.value ||
341
+ (config.range && state.secondValue !== null && value === state.secondValue);
417
342
 
418
- // Set active class if value is in active range
419
- if (config.range && state.secondValue !== null) {
343
+ if (isExactlySelected) {
344
+ tick.classList.add(hiddenClass);
345
+ } else if (config.range && state.secondValue !== null) {
420
346
  const lowerValue = Math.min(state.value, state.secondValue);
421
347
  const higherValue = Math.max(state.value, state.secondValue);
422
348
 
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)];
349
+ tick.classList.add(value >= lowerValue && value <= higherValue ? activeClass : inactiveClass);
449
350
  } else {
450
- const formatter = config.valueFormatter || (value => value.toString());
451
- label.textContent = formatter(value);
351
+ tick.classList.add(value <= state.value ? activeClass : inactiveClass);
452
352
  }
453
353
 
454
- state.component.element.appendChild(label);
455
- state.tickLabels.push(label);
354
+ ticksContainer.appendChild(tick);
355
+ state.ticks.push(tick);
456
356
  }
457
357
  });
458
358
  };
@@ -461,26 +361,47 @@ const updateRemainingTrack = () => {
461
361
  * Updates active state of tick marks
462
362
  */
463
363
  const updateTicks = () => {
364
+ if (!state.ticks || state.ticks.length === 0) return;
365
+
366
+ const activeClass = `${state.component.getClass('slider-tick')}--active`;
367
+ const inactiveClass = `${state.component.getClass('slider-tick')}--inactive`;
368
+ const hiddenClass = `${state.component.getClass('slider-tick')}--hidden`;
369
+
464
370
  // Update active ticks based on current value
465
371
  state.ticks.forEach((tick, index) => {
372
+ // Calculate the value for this tick based on its index
466
373
  const tickValue = state.min + (index * state.step);
467
374
 
468
- if (config.range && state.secondValue !== null) {
469
- // Range slider - ticks between the two values should be active
375
+ // First, reset visibility
376
+ tick.classList.remove(hiddenClass);
377
+
378
+ // Check if this tick should be hidden (matches exactly a selected value)
379
+ const isExactlySelected = tickValue === state.value ||
380
+ (config.range && state.secondValue !== null && tickValue === state.secondValue);
381
+
382
+ if (isExactlySelected) {
383
+ // Hide this tick as it exactly matches a selected value
384
+ tick.classList.add(hiddenClass);
385
+ } else if (config.range && state.secondValue !== null) {
386
+ // Range slider - ticks between values should be active
470
387
  const lowerValue = Math.min(state.value, state.secondValue);
471
388
  const higherValue = Math.max(state.value, state.secondValue);
472
389
 
473
390
  if (tickValue >= lowerValue && tickValue <= higherValue) {
474
- tick.classList.add(`${state.component.getClass('slider-tick')}--active`);
391
+ tick.classList.add(activeClass);
392
+ tick.classList.remove(inactiveClass);
475
393
  } else {
476
- tick.classList.remove(`${state.component.getClass('slider-tick')}--active`);
394
+ tick.classList.remove(activeClass);
395
+ tick.classList.add(inactiveClass);
477
396
  }
478
397
  } else {
479
- // Single slider - ticks below or equal to value should be active
398
+ // Single slider - ticks below value should be active
480
399
  if (tickValue <= state.value) {
481
- tick.classList.add(`${state.component.getClass('slider-tick')}--active`);
400
+ tick.classList.add(activeClass);
401
+ tick.classList.remove(inactiveClass);
482
402
  } else {
483
- tick.classList.remove(`${state.component.getClass('slider-tick')}--active`);
403
+ tick.classList.remove(activeClass);
404
+ tick.classList.add(inactiveClass);
484
405
  }
485
406
  }
486
407
  });
@@ -491,7 +412,7 @@ const updateRemainingTrack = () => {
491
412
  */
492
413
  const updateUi = () => {
493
414
  updateThumbPositions();
494
- updateStartTrack(); // Call BEFORE updateActiveTrack
415
+ updateStartTrack();
495
416
  updateActiveTrack();
496
417
  updateRemainingTrack();
497
418
  updateValueBubbles();