mtrl 0.2.3 → 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.
- package/package.json +6 -3
- package/src/components/slider/_styles.scss +72 -154
- package/src/components/slider/api.ts +36 -101
- package/src/components/slider/config.ts +26 -73
- package/src/components/slider/constants.ts +12 -8
- package/src/components/slider/features/appearance.ts +1 -47
- package/src/components/slider/features/interactions.ts +14 -9
- package/src/components/slider/features/keyboard.ts +0 -2
- package/src/components/slider/features/structure.ts +151 -191
- package/src/components/slider/features/ui.ts +222 -301
- package/src/components/slider/index.ts +11 -1
- package/src/components/slider/slider.ts +1 -1
- package/src/components/slider/types.ts +10 -25
- package/src/core/build/_ripple.scss +79 -0
- package/src/core/build/constants.ts +48 -0
- package/src/core/build/icon.ts +137 -0
- package/src/core/build/ripple.ts +216 -0
- package/src/core/build/text.ts +91 -0
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
// src/components/slider/features/ui.ts
|
|
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
|
-
//
|
|
14
|
-
if (!state.component
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
clamp
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
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
|
-
|
|
53
|
-
|
|
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
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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,
|
|
119
|
-
if (!thumbElement) return;
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
-
|
|
154
|
-
|
|
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
|
-
|
|
159
|
-
const paddingAdjustment = 8; // 8px padding
|
|
160
|
-
const paddingPercent = (paddingAdjustment / trackSize) * 100;
|
|
142
|
+
const { thumbSize, trackSize, paddingPercent } = dims;
|
|
161
143
|
|
|
162
|
-
if (
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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 = `${
|
|
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
|
-
}
|
|
176
|
-
// For single thumb slider, no start track needed
|
|
177
|
-
startTrack.style.display = 'none';
|
|
178
|
-
}
|
|
179
|
-
};
|
|
159
|
+
};
|
|
180
160
|
|
|
181
|
-
/**
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
const updateActiveTrack = () => {
|
|
185
|
-
|
|
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
|
|
167
|
+
const dims = getTrackDimensions();
|
|
168
|
+
if (!dims) return;
|
|
214
169
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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 = `${
|
|
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
|
-
|
|
251
|
-
|
|
252
|
-
const updateRemainingTrack = () => {
|
|
253
|
-
|
|
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
|
-
|
|
290
|
-
|
|
291
|
-
const adjustedWidth = Math.max(0, 100 - adjustedPercent);
|
|
211
|
+
const dims = getTrackDimensions();
|
|
212
|
+
if (!dims) return;
|
|
292
213
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
360
|
-
|
|
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
|
|
283
|
+
* Generates tick marks
|
|
368
284
|
*/
|
|
369
285
|
const generateTicks = () => {
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
}
|
|
375
|
-
});
|
|
286
|
+
if (!ticksContainer) {
|
|
287
|
+
console.warn('Ticks container not found in component structure');
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
376
290
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
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
|
|
303
|
+
if (!config.ticks) return;
|
|
387
304
|
|
|
388
|
-
// Generate
|
|
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(
|
|
334
|
+
tick.classList.add(tickClass);
|
|
409
335
|
|
|
410
336
|
// Position tick
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
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
|
-
|
|
419
|
-
|
|
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
|
-
|
|
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
|
-
|
|
451
|
-
label.textContent = formatter(value);
|
|
351
|
+
tick.classList.add(value <= state.value ? activeClass : inactiveClass);
|
|
452
352
|
}
|
|
453
353
|
|
|
454
|
-
|
|
455
|
-
state.
|
|
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
|
-
|
|
469
|
-
|
|
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(
|
|
391
|
+
tick.classList.add(activeClass);
|
|
392
|
+
tick.classList.remove(inactiveClass);
|
|
475
393
|
} else {
|
|
476
|
-
tick.classList.remove(
|
|
394
|
+
tick.classList.remove(activeClass);
|
|
395
|
+
tick.classList.add(inactiveClass);
|
|
477
396
|
}
|
|
478
397
|
} else {
|
|
479
|
-
// Single slider - ticks below
|
|
398
|
+
// Single slider - ticks below value should be active
|
|
480
399
|
if (tickValue <= state.value) {
|
|
481
|
-
tick.classList.add(
|
|
400
|
+
tick.classList.add(activeClass);
|
|
401
|
+
tick.classList.remove(inactiveClass);
|
|
482
402
|
} else {
|
|
483
|
-
tick.classList.remove(
|
|
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();
|
|
415
|
+
updateStartTrack();
|
|
495
416
|
updateActiveTrack();
|
|
496
417
|
updateRemainingTrack();
|
|
497
418
|
updateValueBubbles();
|