mtrl 0.2.2 → 0.2.4
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/.typedocignore +11 -0
- package/DOCS.md +153 -0
- package/index.ts +18 -3
- package/package.json +7 -2
- package/src/components/badge/_styles.scss +174 -0
- package/src/components/badge/api.ts +292 -0
- package/src/components/badge/badge.ts +52 -0
- package/src/components/badge/config.ts +68 -0
- package/src/components/badge/constants.ts +30 -0
- package/src/components/badge/features.ts +185 -0
- package/src/components/badge/index.ts +4 -0
- package/src/components/badge/types.ts +105 -0
- package/src/components/button/types.ts +174 -29
- package/src/components/carousel/_styles.scss +645 -0
- package/src/components/carousel/api.ts +147 -0
- package/src/components/carousel/carousel.ts +178 -0
- package/src/components/carousel/config.ts +91 -0
- package/src/components/carousel/constants.ts +95 -0
- package/src/components/carousel/features/drag.ts +388 -0
- package/src/components/carousel/features/index.ts +8 -0
- package/src/components/carousel/features/slides.ts +682 -0
- package/src/components/carousel/index.ts +38 -0
- package/src/components/carousel/types.ts +327 -0
- package/src/components/dialog/_styles.scss +213 -0
- package/src/components/dialog/api.ts +283 -0
- package/src/components/dialog/config.ts +113 -0
- package/src/components/dialog/constants.ts +32 -0
- package/src/components/dialog/dialog.ts +56 -0
- package/src/components/dialog/features.ts +713 -0
- package/src/components/dialog/index.ts +15 -0
- package/src/components/dialog/types.ts +221 -0
- package/src/components/progress/_styles.scss +13 -1
- package/src/components/progress/api.ts +2 -2
- package/src/components/progress/progress.ts +2 -2
- package/src/components/progress/types.ts +3 -0
- package/src/components/radios/_styles.scss +232 -0
- package/src/components/radios/api.ts +100 -0
- package/src/components/radios/config.ts +60 -0
- package/src/components/radios/constants.ts +28 -0
- package/src/components/radios/index.ts +4 -0
- package/src/components/radios/radio.ts +269 -0
- package/src/components/radios/radios.ts +42 -0
- package/src/components/radios/types.ts +232 -0
- package/src/components/sheet/_styles.scss +236 -0
- package/src/components/sheet/api.ts +96 -0
- package/src/components/sheet/config.ts +66 -0
- package/src/components/sheet/constants.ts +20 -0
- package/src/components/sheet/features/content.ts +51 -0
- package/src/components/sheet/features/gestures.ts +177 -0
- package/src/components/sheet/features/index.ts +6 -0
- package/src/components/sheet/features/position.ts +42 -0
- package/src/components/sheet/features/state.ts +116 -0
- package/src/components/sheet/features/title.ts +86 -0
- package/src/components/sheet/index.ts +4 -0
- package/src/components/sheet/sheet.ts +57 -0
- package/src/components/sheet/types.ts +266 -0
- package/src/components/slider/_styles.scss +518 -0
- package/src/components/slider/api.ts +336 -0
- package/src/components/slider/config.ts +145 -0
- package/src/components/slider/constants.ts +28 -0
- package/src/components/slider/features/appearance.ts +140 -0
- package/src/components/slider/features/disabled.ts +43 -0
- package/src/components/slider/features/events.ts +164 -0
- package/src/components/slider/features/index.ts +5 -0
- package/src/components/slider/features/interactions.ts +256 -0
- package/src/components/slider/features/keyboard.ts +114 -0
- package/src/components/slider/features/slider.ts +336 -0
- package/src/components/slider/features/structure.ts +264 -0
- package/src/components/slider/features/ui.ts +518 -0
- package/src/components/slider/index.ts +9 -0
- package/src/components/slider/slider.ts +58 -0
- package/src/components/slider/types.ts +166 -0
- package/src/components/tabs/_styles.scss +224 -0
- package/src/components/tabs/api.ts +443 -0
- package/src/components/tabs/config.ts +80 -0
- package/src/components/tabs/constants.ts +12 -0
- package/src/components/tabs/index.ts +4 -0
- package/src/components/tabs/tabs.ts +52 -0
- package/src/components/tabs/types.ts +247 -0
- package/src/components/textfield/_styles.scss +97 -4
- package/src/components/tooltip/_styles.scss +241 -0
- package/src/components/tooltip/api.ts +411 -0
- package/src/components/tooltip/config.ts +78 -0
- package/src/components/tooltip/constants.ts +27 -0
- package/src/components/tooltip/index.ts +4 -0
- package/src/components/tooltip/tooltip.ts +60 -0
- package/src/components/tooltip/types.ts +178 -0
- 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
- package/src/index.ts +9 -1
- package/src/styles/abstract/_variables.scss +24 -12
- package/tsconfig.json +22 -0
- package/typedoc.json +28 -0
- package/typedoc.simple.json +14 -0
|
@@ -0,0 +1,518 @@
|
|
|
1
|
+
// src/components/slider/features/ui.ts - Updated track update methods
|
|
2
|
+
import { SLIDER_ORIENTATIONS } from '../constants';
|
|
3
|
+
import { SliderConfig } from '../types';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Create UI update helpers for slider component with MD3 enhancements
|
|
7
|
+
*
|
|
8
|
+
* @param config Slider configuration
|
|
9
|
+
* @param state Slider state object
|
|
10
|
+
* @returns UI update helper methods
|
|
11
|
+
*/
|
|
12
|
+
export const createUiHelpers = (config: SliderConfig, state) => {
|
|
13
|
+
// Make sure state.component.structure exists
|
|
14
|
+
if (!state.component || !state.component.structure) {
|
|
15
|
+
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
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const {
|
|
35
|
+
track,
|
|
36
|
+
activeTrack,
|
|
37
|
+
startTrack,
|
|
38
|
+
remainingTrack,
|
|
39
|
+
thumb,
|
|
40
|
+
valueBubble,
|
|
41
|
+
secondThumb,
|
|
42
|
+
secondValueBubble
|
|
43
|
+
} = state.component.structure;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Calculates percentage position for a value
|
|
47
|
+
* @param value Value to convert to percentage
|
|
48
|
+
* @returns Percentage position (0-100)
|
|
49
|
+
*/
|
|
50
|
+
const getPercentage = (value) => {
|
|
51
|
+
const range = state.max - state.min;
|
|
52
|
+
if (range === 0) return 0;
|
|
53
|
+
return ((value - state.min) / range) * 100;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* 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
|
+
*/
|
|
62
|
+
const getValueFromPosition = (position, vertical = false) => {
|
|
63
|
+
if (!track) return state.min;
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
const trackRect = track.getBoundingClientRect();
|
|
67
|
+
const range = state.max - state.min;
|
|
68
|
+
|
|
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
|
+
}
|
|
84
|
+
} catch (error) {
|
|
85
|
+
console.warn('Error calculating value from position:', error);
|
|
86
|
+
return state.min;
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Rounds a value to the nearest step
|
|
92
|
+
* @param value Value to round
|
|
93
|
+
* @returns Rounded value
|
|
94
|
+
*/
|
|
95
|
+
const roundToStep = (value) => {
|
|
96
|
+
const step = state.step;
|
|
97
|
+
if (step <= 0) return value;
|
|
98
|
+
|
|
99
|
+
const steps = Math.round((value - state.min) / step);
|
|
100
|
+
return state.min + (steps * step);
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* 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
|
+
*/
|
|
110
|
+
const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
|
|
111
|
+
|
|
112
|
+
/**
|
|
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)
|
|
117
|
+
*/
|
|
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
|
+
}
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
|
|
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);
|
|
152
|
+
|
|
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;
|
|
157
|
+
|
|
158
|
+
// Calculate width with adjustment for thumb spacing - subtract 8px equivalent
|
|
159
|
+
const paddingAdjustment = 8; // 8px padding
|
|
160
|
+
const paddingPercent = (paddingAdjustment / trackSize) * 100;
|
|
161
|
+
|
|
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 {
|
|
170
|
+
startTrack.style.display = 'block';
|
|
171
|
+
startTrack.style.width = `${Math.max(0, lowerPercent - paddingPercent)}%`;
|
|
172
|
+
startTrack.style.left = '0';
|
|
173
|
+
startTrack.style.height = '100%';
|
|
174
|
+
}
|
|
175
|
+
} else {
|
|
176
|
+
// For single thumb slider, no start track needed
|
|
177
|
+
startTrack.style.display = 'none';
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
|
|
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);
|
|
214
|
+
|
|
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);
|
|
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
|
+
} else {
|
|
241
|
+
activeTrack.style.display = 'block';
|
|
242
|
+
activeTrack.style.width = `${adjustedWidth}%`;
|
|
243
|
+
activeTrack.style.left = '0';
|
|
244
|
+
activeTrack.style.height = '100%';
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
|
|
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);
|
|
288
|
+
|
|
289
|
+
// Adjust for right padding
|
|
290
|
+
const adjustedPercent = percent + paddingPercent;
|
|
291
|
+
const adjustedWidth = Math.max(0, 100 - adjustedPercent);
|
|
292
|
+
|
|
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
|
+
};
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Updates thumb positions
|
|
310
|
+
*/
|
|
311
|
+
const updateThumbPositions = () => {
|
|
312
|
+
if (!thumb) return;
|
|
313
|
+
|
|
314
|
+
// Update main thumb
|
|
315
|
+
const percent = getPercentage(state.value);
|
|
316
|
+
setThumbPosition(thumb, valueBubble, percent);
|
|
317
|
+
|
|
318
|
+
// Update second thumb if range slider
|
|
319
|
+
if (config.range && secondThumb && secondValueBubble && state.secondValue !== null) {
|
|
320
|
+
const secondPercent = getPercentage(state.secondValue);
|
|
321
|
+
setThumbPosition(secondThumb, secondValueBubble, secondPercent);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Update ARIA attributes
|
|
325
|
+
thumb.setAttribute('aria-valuenow', String(state.value));
|
|
326
|
+
if (config.range && secondThumb && state.secondValue !== null) {
|
|
327
|
+
secondThumb.setAttribute('aria-valuenow', String(state.secondValue));
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Updates value bubble content
|
|
333
|
+
*/
|
|
334
|
+
const updateValueBubbles = () => {
|
|
335
|
+
if (!valueBubble) return;
|
|
336
|
+
|
|
337
|
+
// Format the values
|
|
338
|
+
const formatter = config.valueFormatter || (value => value.toString());
|
|
339
|
+
const formattedValue = formatter(state.value);
|
|
340
|
+
|
|
341
|
+
// Update main value bubble
|
|
342
|
+
valueBubble.textContent = formattedValue;
|
|
343
|
+
|
|
344
|
+
// Update second value bubble if range slider
|
|
345
|
+
if (config.range && secondValueBubble && state.secondValue !== null) {
|
|
346
|
+
const formattedSecondValue = formatter(state.secondValue);
|
|
347
|
+
secondValueBubble.textContent = formattedSecondValue;
|
|
348
|
+
}
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Shows or hides value bubble
|
|
353
|
+
* @param bubbleElement Value bubble element
|
|
354
|
+
* @param show Whether to show the bubble
|
|
355
|
+
*/
|
|
356
|
+
const showValueBubble = (bubbleElement, show) => {
|
|
357
|
+
if (!bubbleElement || !config.showValue) return;
|
|
358
|
+
|
|
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
|
+
}
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Generates tick marks and labels
|
|
368
|
+
*/
|
|
369
|
+
const generateTicks = () => {
|
|
370
|
+
// Remove existing ticks
|
|
371
|
+
state.ticks.forEach(tick => {
|
|
372
|
+
if (tick.parentNode) {
|
|
373
|
+
tick.parentNode.removeChild(tick);
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
state.tickLabels.forEach(label => {
|
|
378
|
+
if (label.parentNode) {
|
|
379
|
+
label.parentNode.removeChild(label);
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
state.ticks = [];
|
|
384
|
+
state.tickLabels = [];
|
|
385
|
+
|
|
386
|
+
if (!config.ticks && !config.tickLabels) return;
|
|
387
|
+
|
|
388
|
+
// Generate new ticks
|
|
389
|
+
const numSteps = Math.floor((state.max - state.min) / state.step);
|
|
390
|
+
const tickValues = [];
|
|
391
|
+
|
|
392
|
+
for (let i = 0; i <= numSteps; i++) {
|
|
393
|
+
const value = state.min + (i * state.step);
|
|
394
|
+
if (value <= state.max) {
|
|
395
|
+
tickValues.push(value);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// Ensure max value is included
|
|
400
|
+
if (tickValues[tickValues.length - 1] !== state.max) {
|
|
401
|
+
tickValues.push(state.max);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// Create ticks
|
|
405
|
+
tickValues.forEach(value => {
|
|
406
|
+
if (config.ticks) {
|
|
407
|
+
const tick = document.createElement('div');
|
|
408
|
+
tick.classList.add(state.component.getClass('slider-tick'));
|
|
409
|
+
|
|
410
|
+
// 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
|
+
}
|
|
417
|
+
|
|
418
|
+
// Set active class if value is in active range
|
|
419
|
+
if (config.range && state.secondValue !== null) {
|
|
420
|
+
const lowerValue = Math.min(state.value, state.secondValue);
|
|
421
|
+
const higherValue = Math.max(state.value, state.secondValue);
|
|
422
|
+
|
|
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)];
|
|
449
|
+
} else {
|
|
450
|
+
const formatter = config.valueFormatter || (value => value.toString());
|
|
451
|
+
label.textContent = formatter(value);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
state.component.element.appendChild(label);
|
|
455
|
+
state.tickLabels.push(label);
|
|
456
|
+
}
|
|
457
|
+
});
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Updates active state of tick marks
|
|
462
|
+
*/
|
|
463
|
+
const updateTicks = () => {
|
|
464
|
+
// Update active ticks based on current value
|
|
465
|
+
state.ticks.forEach((tick, index) => {
|
|
466
|
+
const tickValue = state.min + (index * state.step);
|
|
467
|
+
|
|
468
|
+
if (config.range && state.secondValue !== null) {
|
|
469
|
+
// Range slider - ticks between the two values should be active
|
|
470
|
+
const lowerValue = Math.min(state.value, state.secondValue);
|
|
471
|
+
const higherValue = Math.max(state.value, state.secondValue);
|
|
472
|
+
|
|
473
|
+
if (tickValue >= lowerValue && tickValue <= higherValue) {
|
|
474
|
+
tick.classList.add(`${state.component.getClass('slider-tick')}--active`);
|
|
475
|
+
} else {
|
|
476
|
+
tick.classList.remove(`${state.component.getClass('slider-tick')}--active`);
|
|
477
|
+
}
|
|
478
|
+
} else {
|
|
479
|
+
// Single slider - ticks below or equal to value should be active
|
|
480
|
+
if (tickValue <= state.value) {
|
|
481
|
+
tick.classList.add(`${state.component.getClass('slider-tick')}--active`);
|
|
482
|
+
} else {
|
|
483
|
+
tick.classList.remove(`${state.component.getClass('slider-tick')}--active`);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
});
|
|
487
|
+
};
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Updates all UI elements
|
|
491
|
+
*/
|
|
492
|
+
const updateUi = () => {
|
|
493
|
+
updateThumbPositions();
|
|
494
|
+
updateStartTrack(); // Call BEFORE updateActiveTrack
|
|
495
|
+
updateActiveTrack();
|
|
496
|
+
updateRemainingTrack();
|
|
497
|
+
updateValueBubbles();
|
|
498
|
+
updateTicks();
|
|
499
|
+
};
|
|
500
|
+
|
|
501
|
+
// Return helper methods
|
|
502
|
+
return {
|
|
503
|
+
getPercentage,
|
|
504
|
+
getValueFromPosition,
|
|
505
|
+
roundToStep,
|
|
506
|
+
clamp,
|
|
507
|
+
setThumbPosition,
|
|
508
|
+
updateActiveTrack,
|
|
509
|
+
updateStartTrack,
|
|
510
|
+
updateRemainingTrack,
|
|
511
|
+
updateThumbPositions,
|
|
512
|
+
updateValueBubbles,
|
|
513
|
+
showValueBubble,
|
|
514
|
+
generateTicks,
|
|
515
|
+
updateTicks,
|
|
516
|
+
updateUi
|
|
517
|
+
};
|
|
518
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// src/components/slider/slider.ts
|
|
2
|
+
import { pipe } from '../../core/compose';
|
|
3
|
+
import { createBase, withElement } from '../../core/compose/component';
|
|
4
|
+
import { withEvents, withLifecycle } from '../../core/compose/features';
|
|
5
|
+
import {
|
|
6
|
+
withStructure,
|
|
7
|
+
withDisabled,
|
|
8
|
+
withAppearance,
|
|
9
|
+
withSlider
|
|
10
|
+
} from './features';
|
|
11
|
+
import { withAPI } from './api';
|
|
12
|
+
import { SliderConfig, SliderComponent } from './types';
|
|
13
|
+
import { createBaseConfig, getElementConfig, getApiConfig } from './config';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Creates a new Slider component
|
|
17
|
+
* @param {SliderConfig} config - Slider configuration object
|
|
18
|
+
* @returns {SliderComponent} Slider component instance
|
|
19
|
+
*/
|
|
20
|
+
const createSlider = (config: SliderConfig = {}): SliderComponent => {
|
|
21
|
+
const baseConfig = createBaseConfig(config);
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
// First create the component with all required features
|
|
25
|
+
const component = pipe(
|
|
26
|
+
createBase,
|
|
27
|
+
withEvents(),
|
|
28
|
+
withElement(getElementConfig(baseConfig)),
|
|
29
|
+
withStructure(baseConfig),
|
|
30
|
+
withDisabled(baseConfig),
|
|
31
|
+
withAppearance(baseConfig),
|
|
32
|
+
withSlider(baseConfig),
|
|
33
|
+
withLifecycle()
|
|
34
|
+
)(baseConfig);
|
|
35
|
+
|
|
36
|
+
// Then generate the API configuration using the component
|
|
37
|
+
const apiOptions = getApiConfig(component);
|
|
38
|
+
|
|
39
|
+
// Finally, apply the API layer with the generated options
|
|
40
|
+
const slider = withAPI(apiOptions)(component);
|
|
41
|
+
|
|
42
|
+
// Register event handlers from config
|
|
43
|
+
if (baseConfig.on && typeof slider.on === 'function') {
|
|
44
|
+
Object.entries(baseConfig.on).forEach(([event, handler]) => {
|
|
45
|
+
if (typeof handler === 'function') {
|
|
46
|
+
slider.on(event, handler);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return slider;
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.error('Slider creation error:', error);
|
|
54
|
+
throw new Error(`Failed to create slider: ${(error as Error).message}`);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export default createSlider;
|