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