mtrl 0.3.1 → 0.3.2
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/.env +15 -0
- package/CONTRIBUTING.md +8 -8
- package/DOCS.md +3 -3
- package/README.md +43 -20
- package/TESTING.md +128 -18
- package/dist/index.js +14865 -0
- package/git-user-stats.js +545 -0
- package/index.ts +9 -67
- package/package.json +8 -3
- package/src/components/badge/api.ts +15 -1
- package/src/components/badge/badge.ts +43 -4
- package/src/components/badge/config.ts +40 -8
- package/src/components/badge/index.ts +64 -3
- package/src/components/badge/types.ts +175 -33
- package/src/components/button/api.ts +63 -1
- package/src/components/button/button.ts +39 -3
- package/src/components/button/config.ts +21 -4
- package/src/components/button/index.ts +26 -1
- package/src/components/button/types.ts +7 -1
- package/src/components/card/api.ts +78 -9
- package/src/components/card/card.ts +58 -3
- package/src/components/card/config.ts +41 -11
- package/src/components/card/features.ts +39 -12
- package/src/components/card/index.ts +84 -19
- package/src/components/card/types.ts +218 -29
- package/src/components/carousel/carousel.ts +92 -28
- package/src/components/carousel/constants.ts +107 -21
- package/src/components/carousel/index.ts +31 -13
- package/src/components/checkbox/checkbox.ts +83 -16
- package/src/components/checkbox/index.ts +43 -1
- package/src/components/checkbox/types.ts +219 -32
- package/src/components/chips/api.ts +194 -0
- package/src/components/{chip → chips/chip}/api.ts +42 -2
- package/src/components/chips/chip/chip.ts +131 -0
- package/src/components/{chip → chips/chip}/config.ts +3 -3
- package/src/components/chips/chip/index.ts +3 -0
- package/src/components/chips/chips.md +481 -0
- package/src/components/chips/chips.ts +75 -0
- package/src/components/chips/config.ts +109 -0
- package/src/components/chips/constants.ts +61 -0
- package/src/components/chips/features/chip-items.ts +33 -0
- package/src/components/chips/features/container.ts +77 -0
- package/src/components/chips/features/controller.ts +448 -0
- package/src/components/chips/features/index.ts +5 -0
- package/src/components/chips/features/label.ts +108 -0
- package/src/components/chips/index.ts +11 -0
- package/src/components/chips/schema.ts +61 -0
- package/src/components/{chip → chips}/types.ts +203 -92
- package/src/components/dialog/dialog.ts +99 -16
- package/src/components/dialog/index.ts +97 -1
- package/src/components/dialog/types.ts +375 -69
- package/src/components/divider/config.ts +90 -6
- package/src/components/divider/divider.ts +32 -2
- package/src/components/divider/features.ts +26 -0
- package/src/components/divider/index.ts +30 -0
- package/src/components/divider/types.ts +86 -9
- package/src/components/extended-fab/api.ts +53 -1
- package/src/components/extended-fab/config.ts +29 -1
- package/src/components/extended-fab/extended-fab.ts +28 -0
- package/src/components/extended-fab/index.ts +36 -0
- package/src/components/extended-fab/types.ts +458 -13
- package/src/components/fab/api.ts +42 -2
- package/src/components/fab/config.ts +29 -1
- package/src/components/fab/fab.ts +16 -2
- package/src/components/fab/index.ts +35 -0
- package/src/components/fab/types.ts +374 -10
- package/src/components/list/api.ts +12 -2
- package/src/components/list/config.ts +21 -0
- package/src/components/list/features.ts +6 -0
- package/src/components/list/index.ts +56 -1
- package/src/components/list/list-item.ts +46 -2
- package/src/components/list/list.ts +73 -2
- package/src/components/list/types.ts +172 -0
- package/src/components/list/utils.ts +26 -2
- package/src/components/menu/api.ts +217 -20
- package/src/components/menu/config.ts +27 -0
- package/src/components/menu/features/visibility.ts +55 -6
- package/src/components/menu/index.ts +64 -0
- package/src/components/menu/menu-item.ts +46 -3
- package/src/components/menu/menu.ts +77 -1
- package/src/components/menu/types.ts +404 -39
- package/src/components/sheet/config.ts +1 -2
- package/src/components/sheet/features/gestures.ts +1 -1
- package/src/components/sheet/features/position.ts +1 -2
- package/src/components/sheet/features/state.ts +1 -1
- package/src/components/sheet/index.ts +10 -2
- package/src/components/sheet/sheet.ts +1 -2
- package/src/components/sheet/types.ts +29 -1
- package/src/components/slider/api.ts +1 -1
- package/src/components/slider/config.ts +1 -1
- package/src/components/slider/features/controller.ts +1 -1
- package/src/components/slider/features/handlers.ts +1 -1
- package/src/components/slider/features/states.ts +1 -1
- package/src/components/slider/index.ts +12 -5
- package/src/components/slider/schema.ts +1 -1
- package/src/components/slider/types.ts +31 -0
- package/src/components/tabs/tab-api.ts +1 -1
- package/src/components/tabs/types.ts +1 -1
- package/src/components/tooltip/api.ts +6 -2
- package/src/components/tooltip/config.ts +9 -28
- package/src/components/tooltip/index.ts +10 -1
- package/src/components/tooltip/types.ts +38 -3
- package/src/index.ts +129 -31
- package/src/styles/abstract/_mixins.scss +23 -9
- package/src/styles/abstract/_variables.scss +14 -4
- package/src/styles/components/_card.scss +1 -1
- package/src/styles/components/_chip.scss +323 -113
- package/src/styles/components/_tabs.scss +1 -1
- package/CLAUDE.md +0 -33
- package/src/components/checkbox/constants.ts +0 -37
- package/src/components/chip/chip-set.ts +0 -225
- package/src/components/chip/chip.ts +0 -118
- package/src/components/chip/constants.ts +0 -28
- package/src/components/chip/index.ts +0 -12
- package/src/components/list/constants.ts +0 -116
- package/src/components/sheet/constants.ts +0 -20
- package/src/components/slider/constants.ts +0 -32
- package/src/components/tooltip/constants.ts +0 -27
- package/test/components/badge.test.ts +0 -545
- package/test/components/bottom-app-bar.test.ts +0 -303
- package/test/components/button.test.ts +0 -233
- package/test/components/card.test.ts +0 -560
- package/test/components/carousel.test.ts +0 -951
- package/test/components/checkbox.test.ts +0 -462
- package/test/components/chip.test.ts +0 -692
- package/test/components/datepicker.test.ts +0 -1124
- package/test/components/dialog.test.ts +0 -990
- package/test/components/divider.test.ts +0 -412
- package/test/components/extended-fab.test.ts +0 -672
- package/test/components/fab.test.ts +0 -561
- package/test/components/list.test.ts +0 -365
- package/test/components/menu.test.ts +0 -718
- package/test/components/navigation.test.ts +0 -186
- package/test/components/progress.test.ts +0 -567
- package/test/components/radios.test.ts +0 -699
- package/test/components/search.test.ts +0 -1135
- package/test/components/segmented-button.test.ts +0 -732
- package/test/components/sheet.test.ts +0 -641
- package/test/components/slider.test.ts +0 -1220
- package/test/components/snackbar.test.ts +0 -461
- package/test/components/switch.test.ts +0 -452
- package/test/components/tabs.test.ts +0 -1369
- package/test/components/textfield.test.ts +0 -400
- package/test/components/timepicker.test.ts +0 -592
- package/test/components/tooltip.test.ts +0 -630
- package/test/components/top-app-bar.test.ts +0 -566
- package/test/core/dom.attributes.test.ts +0 -148
- package/test/core/dom.classes.test.ts +0 -152
- package/test/core/dom.events.test.ts +0 -243
- package/test/core/emitter.test.ts +0 -141
- package/test/core/ripple.test.ts +0 -99
- package/test/core/state.store.test.ts +0 -189
- package/test/core/utils.normalize.test.ts +0 -61
- package/test/core/utils.object.test.ts +0 -120
- package/test/setup.js +0 -371
- package/test/setup.ts +0 -451
- package/tsconfig.json +0 -22
- package/typedoc.json +0 -28
- package/typedoc.simple.json +0 -14
|
@@ -1,1220 +0,0 @@
|
|
|
1
|
-
// test/components/slider.test.ts
|
|
2
|
-
import { describe, test, expect, beforeAll, afterAll } from 'bun:test';
|
|
3
|
-
import { JSDOM } from 'jsdom';
|
|
4
|
-
import {
|
|
5
|
-
type SliderComponent,
|
|
6
|
-
type SliderConfig,
|
|
7
|
-
type SliderColor,
|
|
8
|
-
type SliderSize,
|
|
9
|
-
type SliderEventType,
|
|
10
|
-
type SliderEvent
|
|
11
|
-
} from '../../src/components/slider/types';
|
|
12
|
-
|
|
13
|
-
// Setup jsdom environment
|
|
14
|
-
let dom: JSDOM;
|
|
15
|
-
let window: Window;
|
|
16
|
-
let document: Document;
|
|
17
|
-
let originalGlobalDocument: any;
|
|
18
|
-
let originalGlobalWindow: any;
|
|
19
|
-
|
|
20
|
-
beforeAll(() => {
|
|
21
|
-
// Create a new JSDOM instance
|
|
22
|
-
dom = new JSDOM('<!DOCTYPE html><html><body></body></html>', {
|
|
23
|
-
url: 'http://localhost/',
|
|
24
|
-
pretendToBeVisual: true
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
// Get window and document from jsdom
|
|
28
|
-
window = dom.window;
|
|
29
|
-
document = window.document;
|
|
30
|
-
|
|
31
|
-
// Store original globals
|
|
32
|
-
originalGlobalDocument = global.document;
|
|
33
|
-
originalGlobalWindow = global.window;
|
|
34
|
-
|
|
35
|
-
// Set globals to use jsdom
|
|
36
|
-
global.document = document;
|
|
37
|
-
global.window = window;
|
|
38
|
-
global.Element = window.Element;
|
|
39
|
-
global.HTMLElement = window.HTMLElement;
|
|
40
|
-
global.HTMLButtonElement = window.HTMLButtonElement;
|
|
41
|
-
global.Event = window.Event;
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
afterAll(() => {
|
|
45
|
-
// Restore original globals
|
|
46
|
-
global.document = originalGlobalDocument;
|
|
47
|
-
global.window = originalGlobalWindow;
|
|
48
|
-
|
|
49
|
-
// Clean up jsdom
|
|
50
|
-
window.close();
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
// Constants for slider colors
|
|
54
|
-
const SLIDER_COLORS = {
|
|
55
|
-
PRIMARY: 'primary',
|
|
56
|
-
SECONDARY: 'secondary',
|
|
57
|
-
TERTIARY: 'tertiary',
|
|
58
|
-
ERROR: 'error'
|
|
59
|
-
} as const;
|
|
60
|
-
|
|
61
|
-
// Constants for slider sizes
|
|
62
|
-
const SLIDER_SIZES = {
|
|
63
|
-
SMALL: 'small',
|
|
64
|
-
MEDIUM: 'medium',
|
|
65
|
-
LARGE: 'large'
|
|
66
|
-
} as const;
|
|
67
|
-
|
|
68
|
-
// Constants for slider events
|
|
69
|
-
const SLIDER_EVENTS = {
|
|
70
|
-
CHANGE: 'change',
|
|
71
|
-
INPUT: 'input',
|
|
72
|
-
FOCUS: 'focus',
|
|
73
|
-
BLUR: 'blur',
|
|
74
|
-
START: 'start',
|
|
75
|
-
END: 'end'
|
|
76
|
-
} as const;
|
|
77
|
-
|
|
78
|
-
// Mock slider implementation
|
|
79
|
-
const createMockSlider = (config: SliderConfig = {}): SliderComponent => {
|
|
80
|
-
// Create main container element
|
|
81
|
-
const element = document.createElement('div');
|
|
82
|
-
element.className = 'mtrl-slider';
|
|
83
|
-
|
|
84
|
-
// Default settings
|
|
85
|
-
const settings = {
|
|
86
|
-
min: config.min !== undefined ? config.min : 0,
|
|
87
|
-
max: config.max !== undefined ? config.max : 100,
|
|
88
|
-
value: config.value !== undefined ? config.value : 0,
|
|
89
|
-
secondValue: config.secondValue,
|
|
90
|
-
step: config.step !== undefined ? config.step : 1,
|
|
91
|
-
disabled: config.disabled || false,
|
|
92
|
-
color: config.color || SLIDER_COLORS.PRIMARY,
|
|
93
|
-
size: config.size || SLIDER_SIZES.MEDIUM,
|
|
94
|
-
ticks: config.ticks || false,
|
|
95
|
-
valueFormatter: config.valueFormatter || ((value: number) => value.toString()),
|
|
96
|
-
showValue: config.showValue || false,
|
|
97
|
-
snapToSteps: config.snapToSteps !== undefined ? config.snapToSteps : false,
|
|
98
|
-
range: config.range || false,
|
|
99
|
-
label: config.label || '',
|
|
100
|
-
labelPosition: config.labelPosition || 'start',
|
|
101
|
-
icon: config.icon || '',
|
|
102
|
-
iconPosition: config.iconPosition || 'start'
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
// Apply color class
|
|
106
|
-
element.classList.add(`mtrl-slider--${settings.color}`);
|
|
107
|
-
|
|
108
|
-
// Apply size class
|
|
109
|
-
element.classList.add(`mtrl-slider--${settings.size}`);
|
|
110
|
-
|
|
111
|
-
// Apply disabled state
|
|
112
|
-
if (settings.disabled) {
|
|
113
|
-
element.classList.add('mtrl-slider--disabled');
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Apply ticks class
|
|
117
|
-
if (settings.ticks) {
|
|
118
|
-
element.classList.add('mtrl-slider--ticks');
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// Apply range class
|
|
122
|
-
if (settings.range) {
|
|
123
|
-
element.classList.add('mtrl-slider--range');
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Apply label position class
|
|
127
|
-
if (settings.label) {
|
|
128
|
-
element.classList.add(`mtrl-slider--label-${settings.labelPosition}`);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// Apply additional classes
|
|
132
|
-
if (config.class) {
|
|
133
|
-
const classes = config.class.split(' ');
|
|
134
|
-
classes.forEach(className => element.classList.add(className));
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// Create label if provided
|
|
138
|
-
let labelElement: HTMLElement | null = null;
|
|
139
|
-
if (settings.label) {
|
|
140
|
-
labelElement = document.createElement('label');
|
|
141
|
-
labelElement.className = 'mtrl-slider__label';
|
|
142
|
-
labelElement.textContent = settings.label;
|
|
143
|
-
element.appendChild(labelElement);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// Create icon if provided
|
|
147
|
-
let iconElement: HTMLElement | null = null;
|
|
148
|
-
if (settings.icon) {
|
|
149
|
-
iconElement = document.createElement('span');
|
|
150
|
-
iconElement.className = `mtrl-slider__icon mtrl-slider__icon--${settings.iconPosition}`;
|
|
151
|
-
iconElement.innerHTML = settings.icon;
|
|
152
|
-
element.appendChild(iconElement);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// Create slider track
|
|
156
|
-
const track = document.createElement('div');
|
|
157
|
-
track.className = 'mtrl-slider__track';
|
|
158
|
-
|
|
159
|
-
// Create slider fill (progress)
|
|
160
|
-
const fill = document.createElement('div');
|
|
161
|
-
fill.className = 'mtrl-slider__fill';
|
|
162
|
-
track.appendChild(fill);
|
|
163
|
-
|
|
164
|
-
// Create ticks if enabled
|
|
165
|
-
if (settings.ticks) {
|
|
166
|
-
const ticksContainer = document.createElement('div');
|
|
167
|
-
ticksContainer.className = 'mtrl-slider__ticks';
|
|
168
|
-
|
|
169
|
-
// Calculate number of ticks based on step
|
|
170
|
-
const numTicks = Math.floor((settings.max - settings.min) / settings.step) + 1;
|
|
171
|
-
for (let i = 0; i < numTicks; i++) {
|
|
172
|
-
const tick = document.createElement('div');
|
|
173
|
-
tick.className = 'mtrl-slider__tick';
|
|
174
|
-
ticksContainer.appendChild(tick);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
track.appendChild(ticksContainer);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// Create thumb (handle)
|
|
181
|
-
const thumb = document.createElement('div');
|
|
182
|
-
thumb.className = 'mtrl-slider__thumb';
|
|
183
|
-
|
|
184
|
-
// Create second thumb for range slider
|
|
185
|
-
let secondThumb: HTMLElement | null = null;
|
|
186
|
-
if (settings.range) {
|
|
187
|
-
secondThumb = document.createElement('div');
|
|
188
|
-
secondThumb.className = 'mtrl-slider__thumb mtrl-slider__thumb--second';
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// Create value display if enabled
|
|
192
|
-
let valueElement: HTMLElement | null = null;
|
|
193
|
-
if (settings.showValue) {
|
|
194
|
-
valueElement = document.createElement('div');
|
|
195
|
-
valueElement.className = 'mtrl-slider__value';
|
|
196
|
-
valueElement.textContent = settings.valueFormatter(settings.value);
|
|
197
|
-
thumb.appendChild(valueElement);
|
|
198
|
-
|
|
199
|
-
if (settings.range && secondThumb) {
|
|
200
|
-
const secondValueElement = document.createElement('div');
|
|
201
|
-
secondValueElement.className = 'mtrl-slider__value';
|
|
202
|
-
secondValueElement.textContent = settings.valueFormatter(settings.secondValue || settings.min);
|
|
203
|
-
secondThumb.appendChild(secondValueElement);
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// Add thumbs to track
|
|
208
|
-
track.appendChild(thumb);
|
|
209
|
-
if (settings.range && secondThumb) {
|
|
210
|
-
track.appendChild(secondThumb);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// Add track to container
|
|
214
|
-
element.appendChild(track);
|
|
215
|
-
|
|
216
|
-
// Update UI based on current values
|
|
217
|
-
const updateSliderUI = () => {
|
|
218
|
-
const range = settings.max - settings.min;
|
|
219
|
-
|
|
220
|
-
// Calculate percentage for first thumb
|
|
221
|
-
const percent = ((settings.value - settings.min) / range) * 100;
|
|
222
|
-
|
|
223
|
-
// Set fill width and thumb position
|
|
224
|
-
if (settings.range && settings.secondValue !== undefined) {
|
|
225
|
-
// For range slider, fill is between the two thumbs
|
|
226
|
-
const secondPercent = ((settings.secondValue - settings.min) / range) * 100;
|
|
227
|
-
const startPercent = Math.min(percent, secondPercent);
|
|
228
|
-
const endPercent = Math.max(percent, secondPercent);
|
|
229
|
-
|
|
230
|
-
fill.style.left = `${startPercent}%`;
|
|
231
|
-
fill.style.width = `${endPercent - startPercent}%`;
|
|
232
|
-
|
|
233
|
-
thumb.style.left = `${percent}%`;
|
|
234
|
-
if (secondThumb) {
|
|
235
|
-
secondThumb.style.left = `${secondPercent}%`;
|
|
236
|
-
}
|
|
237
|
-
} else {
|
|
238
|
-
// For regular slider, fill starts from beginning
|
|
239
|
-
fill.style.width = `${percent}%`;
|
|
240
|
-
thumb.style.left = `${percent}%`;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// Update value display if present
|
|
244
|
-
if (valueElement) {
|
|
245
|
-
valueElement.textContent = settings.valueFormatter(settings.value);
|
|
246
|
-
|
|
247
|
-
if (settings.range && secondThumb && settings.secondValue !== undefined) {
|
|
248
|
-
const secondValueEl = secondThumb.querySelector('.mtrl-slider__value');
|
|
249
|
-
if (secondValueEl) {
|
|
250
|
-
secondValueEl.textContent = settings.valueFormatter(settings.secondValue);
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
};
|
|
255
|
-
|
|
256
|
-
// Initialize slider UI
|
|
257
|
-
updateSliderUI();
|
|
258
|
-
|
|
259
|
-
// Track event handlers
|
|
260
|
-
const eventHandlers: Record<string, Function[]> = {};
|
|
261
|
-
|
|
262
|
-
// Emit an event
|
|
263
|
-
const emit = (event: SliderEventType, originalEvent?: Event | null) => {
|
|
264
|
-
let defaultPrevented = false;
|
|
265
|
-
|
|
266
|
-
const eventData: SliderEvent = {
|
|
267
|
-
slider,
|
|
268
|
-
value: settings.value,
|
|
269
|
-
secondValue: settings.range ? settings.secondValue || null : null,
|
|
270
|
-
originalEvent: originalEvent || null,
|
|
271
|
-
preventDefault: () => {
|
|
272
|
-
defaultPrevented = true;
|
|
273
|
-
},
|
|
274
|
-
defaultPrevented
|
|
275
|
-
};
|
|
276
|
-
|
|
277
|
-
// Call handlers from config.on
|
|
278
|
-
if (config.on && config.on[event]) {
|
|
279
|
-
config.on[event]!(eventData);
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
// Call registered event handlers
|
|
283
|
-
if (eventHandlers[event]) {
|
|
284
|
-
eventHandlers[event].forEach(handler => handler(eventData));
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
return defaultPrevented;
|
|
288
|
-
};
|
|
289
|
-
|
|
290
|
-
// Create the slider component
|
|
291
|
-
const slider: SliderComponent = {
|
|
292
|
-
element,
|
|
293
|
-
|
|
294
|
-
setValue: (value: number, triggerEvent: boolean = false) => {
|
|
295
|
-
// Ensure value is within min/max bounds
|
|
296
|
-
value = Math.max(settings.min, Math.min(settings.max, value));
|
|
297
|
-
|
|
298
|
-
// Snap to nearest step if enabled
|
|
299
|
-
if (settings.snapToSteps && settings.step > 0) {
|
|
300
|
-
value = Math.round((value - settings.min) / settings.step) * settings.step + settings.min;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
// Update value
|
|
304
|
-
settings.value = value;
|
|
305
|
-
|
|
306
|
-
// Update UI
|
|
307
|
-
updateSliderUI();
|
|
308
|
-
|
|
309
|
-
// Emit events if requested
|
|
310
|
-
if (triggerEvent) {
|
|
311
|
-
emit(SLIDER_EVENTS.INPUT);
|
|
312
|
-
emit(SLIDER_EVENTS.CHANGE);
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
return slider;
|
|
316
|
-
},
|
|
317
|
-
|
|
318
|
-
getValue: () => settings.value,
|
|
319
|
-
|
|
320
|
-
setSecondValue: (value: number, triggerEvent: boolean = false) => {
|
|
321
|
-
if (!settings.range) return slider;
|
|
322
|
-
|
|
323
|
-
// Ensure value is within min/max bounds
|
|
324
|
-
value = Math.max(settings.min, Math.min(settings.max, value));
|
|
325
|
-
|
|
326
|
-
// Snap to nearest step if enabled
|
|
327
|
-
if (settings.snapToSteps && settings.step > 0) {
|
|
328
|
-
value = Math.round((value - settings.min) / settings.step) * settings.step + settings.min;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
// Update value
|
|
332
|
-
settings.secondValue = value;
|
|
333
|
-
|
|
334
|
-
// Update UI
|
|
335
|
-
updateSliderUI();
|
|
336
|
-
|
|
337
|
-
// Emit events if requested
|
|
338
|
-
if (triggerEvent) {
|
|
339
|
-
emit(SLIDER_EVENTS.INPUT);
|
|
340
|
-
emit(SLIDER_EVENTS.CHANGE);
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
return slider;
|
|
344
|
-
},
|
|
345
|
-
|
|
346
|
-
getSecondValue: () => {
|
|
347
|
-
return settings.range ? settings.secondValue || null : null;
|
|
348
|
-
},
|
|
349
|
-
|
|
350
|
-
setMin: (min: number) => {
|
|
351
|
-
settings.min = min;
|
|
352
|
-
|
|
353
|
-
// Adjust values if they're now outside bounds
|
|
354
|
-
if (settings.value < min) {
|
|
355
|
-
settings.value = min;
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
if (settings.range && settings.secondValue !== undefined && settings.secondValue < min) {
|
|
359
|
-
settings.secondValue = min;
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
// Update ticks if enabled
|
|
363
|
-
if (settings.ticks) {
|
|
364
|
-
const ticksContainer = element.querySelector('.mtrl-slider__ticks');
|
|
365
|
-
if (ticksContainer) {
|
|
366
|
-
ticksContainer.innerHTML = '';
|
|
367
|
-
|
|
368
|
-
const numTicks = Math.floor((settings.max - settings.min) / settings.step) + 1;
|
|
369
|
-
for (let i = 0; i < numTicks; i++) {
|
|
370
|
-
const tick = document.createElement('div');
|
|
371
|
-
tick.className = 'mtrl-slider__tick';
|
|
372
|
-
ticksContainer.appendChild(tick);
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
// Update UI
|
|
378
|
-
updateSliderUI();
|
|
379
|
-
|
|
380
|
-
return slider;
|
|
381
|
-
},
|
|
382
|
-
|
|
383
|
-
getMin: () => settings.min,
|
|
384
|
-
|
|
385
|
-
setMax: (max: number) => {
|
|
386
|
-
settings.max = max;
|
|
387
|
-
|
|
388
|
-
// Adjust values if they're now outside bounds
|
|
389
|
-
if (settings.value > max) {
|
|
390
|
-
settings.value = max;
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
if (settings.range && settings.secondValue !== undefined && settings.secondValue > max) {
|
|
394
|
-
settings.secondValue = max;
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
// Update ticks if enabled
|
|
398
|
-
if (settings.ticks) {
|
|
399
|
-
const ticksContainer = element.querySelector('.mtrl-slider__ticks');
|
|
400
|
-
if (ticksContainer) {
|
|
401
|
-
ticksContainer.innerHTML = '';
|
|
402
|
-
|
|
403
|
-
const numTicks = Math.floor((settings.max - settings.min) / settings.step) + 1;
|
|
404
|
-
for (let i = 0; i < numTicks; i++) {
|
|
405
|
-
const tick = document.createElement('div');
|
|
406
|
-
tick.className = 'mtrl-slider__tick';
|
|
407
|
-
ticksContainer.appendChild(tick);
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
// Update UI
|
|
413
|
-
updateSliderUI();
|
|
414
|
-
|
|
415
|
-
return slider;
|
|
416
|
-
},
|
|
417
|
-
|
|
418
|
-
getMax: () => settings.max,
|
|
419
|
-
|
|
420
|
-
setStep: (step: number) => {
|
|
421
|
-
settings.step = step;
|
|
422
|
-
|
|
423
|
-
// Update ticks if enabled
|
|
424
|
-
if (settings.ticks) {
|
|
425
|
-
const ticksContainer = element.querySelector('.mtrl-slider__ticks');
|
|
426
|
-
if (ticksContainer) {
|
|
427
|
-
ticksContainer.innerHTML = '';
|
|
428
|
-
|
|
429
|
-
const numTicks = Math.floor((settings.max - settings.min) / settings.step) + 1;
|
|
430
|
-
for (let i = 0; i < numTicks; i++) {
|
|
431
|
-
const tick = document.createElement('div');
|
|
432
|
-
tick.className = 'mtrl-slider__tick';
|
|
433
|
-
ticksContainer.appendChild(tick);
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
// Snap current values to new step if enabled
|
|
439
|
-
if (settings.snapToSteps) {
|
|
440
|
-
settings.value = Math.round((settings.value - settings.min) / step) * step + settings.min;
|
|
441
|
-
|
|
442
|
-
if (settings.range && settings.secondValue !== undefined) {
|
|
443
|
-
settings.secondValue = Math.round((settings.secondValue - settings.min) / step) * step + settings.min;
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
// Update UI
|
|
447
|
-
updateSliderUI();
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
return slider;
|
|
451
|
-
},
|
|
452
|
-
|
|
453
|
-
getStep: () => settings.step,
|
|
454
|
-
|
|
455
|
-
enable: () => {
|
|
456
|
-
settings.disabled = false;
|
|
457
|
-
element.classList.remove('mtrl-slider--disabled');
|
|
458
|
-
return slider;
|
|
459
|
-
},
|
|
460
|
-
|
|
461
|
-
disable: () => {
|
|
462
|
-
settings.disabled = true;
|
|
463
|
-
element.classList.add('mtrl-slider--disabled');
|
|
464
|
-
return slider;
|
|
465
|
-
},
|
|
466
|
-
|
|
467
|
-
isDisabled: () => settings.disabled,
|
|
468
|
-
|
|
469
|
-
setColor: (color: SliderColor) => {
|
|
470
|
-
// Remove existing color class
|
|
471
|
-
Object.values(SLIDER_COLORS).forEach(c => {
|
|
472
|
-
element.classList.remove(`mtrl-slider--${c}`);
|
|
473
|
-
});
|
|
474
|
-
|
|
475
|
-
// Add new color class
|
|
476
|
-
element.classList.add(`mtrl-slider--${color}`);
|
|
477
|
-
settings.color = color;
|
|
478
|
-
|
|
479
|
-
return slider;
|
|
480
|
-
},
|
|
481
|
-
|
|
482
|
-
getColor: () => settings.color,
|
|
483
|
-
|
|
484
|
-
setSize: (size: SliderSize) => {
|
|
485
|
-
// Remove existing size class
|
|
486
|
-
Object.values(SLIDER_SIZES).forEach(s => {
|
|
487
|
-
element.classList.remove(`mtrl-slider--${s}`);
|
|
488
|
-
});
|
|
489
|
-
|
|
490
|
-
// Add new size class
|
|
491
|
-
element.classList.add(`mtrl-slider--${size}`);
|
|
492
|
-
settings.size = size;
|
|
493
|
-
|
|
494
|
-
return slider;
|
|
495
|
-
},
|
|
496
|
-
|
|
497
|
-
getSize: () => settings.size,
|
|
498
|
-
|
|
499
|
-
showTicks: (show: boolean) => {
|
|
500
|
-
settings.ticks = show;
|
|
501
|
-
|
|
502
|
-
if (show) {
|
|
503
|
-
element.classList.add('mtrl-slider--ticks');
|
|
504
|
-
|
|
505
|
-
// Create ticks if they don't exist
|
|
506
|
-
let ticksContainer = element.querySelector('.mtrl-slider__ticks');
|
|
507
|
-
if (!ticksContainer) {
|
|
508
|
-
ticksContainer = document.createElement('div');
|
|
509
|
-
ticksContainer.className = 'mtrl-slider__ticks';
|
|
510
|
-
|
|
511
|
-
const numTicks = Math.floor((settings.max - settings.min) / settings.step) + 1;
|
|
512
|
-
for (let i = 0; i < numTicks; i++) {
|
|
513
|
-
const tick = document.createElement('div');
|
|
514
|
-
tick.className = 'mtrl-slider__tick';
|
|
515
|
-
ticksContainer.appendChild(tick);
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
const track = element.querySelector('.mtrl-slider__track');
|
|
519
|
-
if (track) {
|
|
520
|
-
track.appendChild(ticksContainer);
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
} else {
|
|
524
|
-
element.classList.remove('mtrl-slider--ticks');
|
|
525
|
-
|
|
526
|
-
// Remove ticks if they exist
|
|
527
|
-
const ticksContainer = element.querySelector('.mtrl-slider__ticks');
|
|
528
|
-
if (ticksContainer) {
|
|
529
|
-
ticksContainer.remove();
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
return slider;
|
|
534
|
-
},
|
|
535
|
-
|
|
536
|
-
showCurrentValue: (show: boolean) => {
|
|
537
|
-
settings.showValue = show;
|
|
538
|
-
|
|
539
|
-
if (show) {
|
|
540
|
-
// Create value display for main thumb if it doesn't exist
|
|
541
|
-
let valueEl = thumb.querySelector('.mtrl-slider__value');
|
|
542
|
-
if (!valueEl) {
|
|
543
|
-
valueEl = document.createElement('div');
|
|
544
|
-
valueEl.className = 'mtrl-slider__value';
|
|
545
|
-
valueEl.textContent = settings.valueFormatter(settings.value);
|
|
546
|
-
thumb.appendChild(valueEl);
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
// Create value display for second thumb if range slider
|
|
550
|
-
if (settings.range && secondThumb) {
|
|
551
|
-
let secondValueEl = secondThumb.querySelector('.mtrl-slider__value');
|
|
552
|
-
if (!secondValueEl) {
|
|
553
|
-
secondValueEl = document.createElement('div');
|
|
554
|
-
secondValueEl.className = 'mtrl-slider__value';
|
|
555
|
-
secondValueEl.textContent = settings.valueFormatter(settings.secondValue || settings.min);
|
|
556
|
-
secondThumb.appendChild(secondValueEl);
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
} else {
|
|
560
|
-
// Remove value display from main thumb
|
|
561
|
-
const valueEl = thumb.querySelector('.mtrl-slider__value');
|
|
562
|
-
if (valueEl) {
|
|
563
|
-
valueEl.remove();
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
// Remove value display from second thumb
|
|
567
|
-
if (settings.range && secondThumb) {
|
|
568
|
-
const secondValueEl = secondThumb.querySelector('.mtrl-slider__value');
|
|
569
|
-
if (secondValueEl) {
|
|
570
|
-
secondValueEl.remove();
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
return slider;
|
|
576
|
-
},
|
|
577
|
-
|
|
578
|
-
setLabel: (text: string) => {
|
|
579
|
-
settings.label = text;
|
|
580
|
-
|
|
581
|
-
if (text) {
|
|
582
|
-
if (!labelElement) {
|
|
583
|
-
labelElement = document.createElement('label');
|
|
584
|
-
labelElement.className = 'mtrl-slider__label';
|
|
585
|
-
element.insertBefore(labelElement, element.firstChild);
|
|
586
|
-
|
|
587
|
-
// Apply label position class
|
|
588
|
-
element.classList.add(`mtrl-slider--label-${settings.labelPosition}`);
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
labelElement.textContent = text;
|
|
592
|
-
} else if (labelElement) {
|
|
593
|
-
labelElement.remove();
|
|
594
|
-
labelElement = null;
|
|
595
|
-
|
|
596
|
-
// Remove label position class
|
|
597
|
-
element.classList.remove(`mtrl-slider--label-${settings.labelPosition}`);
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
return slider;
|
|
601
|
-
},
|
|
602
|
-
|
|
603
|
-
getLabel: () => settings.label,
|
|
604
|
-
|
|
605
|
-
setIcon: (iconHtml: string) => {
|
|
606
|
-
settings.icon = iconHtml;
|
|
607
|
-
|
|
608
|
-
if (iconHtml) {
|
|
609
|
-
if (!iconElement) {
|
|
610
|
-
iconElement = document.createElement('span');
|
|
611
|
-
iconElement.className = `mtrl-slider__icon mtrl-slider__icon--${settings.iconPosition}`;
|
|
612
|
-
|
|
613
|
-
if (settings.iconPosition === 'start') {
|
|
614
|
-
element.insertBefore(iconElement, element.firstChild);
|
|
615
|
-
} else {
|
|
616
|
-
element.appendChild(iconElement);
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
iconElement.innerHTML = iconHtml;
|
|
621
|
-
} else if (iconElement) {
|
|
622
|
-
iconElement.remove();
|
|
623
|
-
iconElement = null;
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
return slider;
|
|
627
|
-
},
|
|
628
|
-
|
|
629
|
-
getIcon: () => settings.icon,
|
|
630
|
-
|
|
631
|
-
on: (event: SliderEventType, handler: (event: SliderEvent) => void) => {
|
|
632
|
-
if (!eventHandlers[event]) {
|
|
633
|
-
eventHandlers[event] = [];
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
eventHandlers[event].push(handler);
|
|
637
|
-
return slider;
|
|
638
|
-
},
|
|
639
|
-
|
|
640
|
-
off: (event: SliderEventType, handler: (event: SliderEvent) => void) => {
|
|
641
|
-
if (eventHandlers[event]) {
|
|
642
|
-
eventHandlers[event] = eventHandlers[event].filter(h => h !== handler);
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
return slider;
|
|
646
|
-
},
|
|
647
|
-
|
|
648
|
-
destroy: () => {
|
|
649
|
-
// Remove element from DOM if it has a parent
|
|
650
|
-
if (element.parentNode) {
|
|
651
|
-
element.parentNode.removeChild(element);
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
// Clear event handlers
|
|
655
|
-
for (const event in eventHandlers) {
|
|
656
|
-
eventHandlers[event] = [];
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
};
|
|
660
|
-
|
|
661
|
-
return slider;
|
|
662
|
-
};
|
|
663
|
-
|
|
664
|
-
describe('Slider Component', () => {
|
|
665
|
-
test('should create a slider element', () => {
|
|
666
|
-
const slider = createMockSlider();
|
|
667
|
-
|
|
668
|
-
expect(slider.element).toBeDefined();
|
|
669
|
-
expect(slider.element.tagName).toBe('DIV');
|
|
670
|
-
expect(slider.element.className).toContain('mtrl-slider');
|
|
671
|
-
|
|
672
|
-
const track = slider.element.querySelector('.mtrl-slider__track');
|
|
673
|
-
expect(track).toBeDefined();
|
|
674
|
-
|
|
675
|
-
const fill = slider.element.querySelector('.mtrl-slider__fill');
|
|
676
|
-
expect(fill).toBeDefined();
|
|
677
|
-
|
|
678
|
-
const thumb = slider.element.querySelector('.mtrl-slider__thumb');
|
|
679
|
-
expect(thumb).toBeDefined();
|
|
680
|
-
});
|
|
681
|
-
|
|
682
|
-
test('should apply primary color by default', () => {
|
|
683
|
-
const slider = createMockSlider();
|
|
684
|
-
expect(slider.element.className).toContain('mtrl-slider--primary');
|
|
685
|
-
});
|
|
686
|
-
|
|
687
|
-
test('should apply different colors', () => {
|
|
688
|
-
const colors: SliderColor[] = [
|
|
689
|
-
SLIDER_COLORS.PRIMARY,
|
|
690
|
-
SLIDER_COLORS.SECONDARY,
|
|
691
|
-
SLIDER_COLORS.TERTIARY,
|
|
692
|
-
SLIDER_COLORS.ERROR
|
|
693
|
-
];
|
|
694
|
-
|
|
695
|
-
colors.forEach(color => {
|
|
696
|
-
const slider = createMockSlider({ color });
|
|
697
|
-
expect(slider.element.className).toContain(`mtrl-slider--${color}`);
|
|
698
|
-
});
|
|
699
|
-
});
|
|
700
|
-
|
|
701
|
-
test('should apply medium size by default', () => {
|
|
702
|
-
const slider = createMockSlider();
|
|
703
|
-
expect(slider.element.className).toContain('mtrl-slider--medium');
|
|
704
|
-
});
|
|
705
|
-
|
|
706
|
-
test('should apply different sizes', () => {
|
|
707
|
-
const sizes: SliderSize[] = [
|
|
708
|
-
SLIDER_SIZES.SMALL,
|
|
709
|
-
SLIDER_SIZES.MEDIUM,
|
|
710
|
-
SLIDER_SIZES.LARGE
|
|
711
|
-
];
|
|
712
|
-
|
|
713
|
-
sizes.forEach(size => {
|
|
714
|
-
const slider = createMockSlider({ size });
|
|
715
|
-
expect(slider.element.className).toContain(`mtrl-slider--${size}`);
|
|
716
|
-
});
|
|
717
|
-
});
|
|
718
|
-
|
|
719
|
-
test('should set initial value', () => {
|
|
720
|
-
const slider = createMockSlider({
|
|
721
|
-
min: 0,
|
|
722
|
-
max: 100,
|
|
723
|
-
value: 50
|
|
724
|
-
});
|
|
725
|
-
|
|
726
|
-
expect(slider.getValue()).toBe(50);
|
|
727
|
-
|
|
728
|
-
const fill = slider.element.querySelector('.mtrl-slider__fill');
|
|
729
|
-
expect(fill?.style.width).toBe('50%');
|
|
730
|
-
|
|
731
|
-
const thumb = slider.element.querySelector('.mtrl-slider__thumb');
|
|
732
|
-
expect(thumb?.style.left).toBe('50%');
|
|
733
|
-
});
|
|
734
|
-
|
|
735
|
-
test('should support range slider with two thumbs', () => {
|
|
736
|
-
const slider = createMockSlider({
|
|
737
|
-
range: true,
|
|
738
|
-
value: 25,
|
|
739
|
-
secondValue: 75
|
|
740
|
-
});
|
|
741
|
-
|
|
742
|
-
expect(slider.element.className).toContain('mtrl-slider--range');
|
|
743
|
-
expect(slider.getValue()).toBe(25);
|
|
744
|
-
expect(slider.getSecondValue()).toBe(75);
|
|
745
|
-
|
|
746
|
-
const thumbs = slider.element.querySelectorAll('.mtrl-slider__thumb');
|
|
747
|
-
expect(thumbs.length).toBe(2);
|
|
748
|
-
expect(thumbs[0].style.left).toBe('25%');
|
|
749
|
-
expect(thumbs[1].style.left).toBe('75%');
|
|
750
|
-
|
|
751
|
-
const fill = slider.element.querySelector('.mtrl-slider__fill');
|
|
752
|
-
expect(fill?.style.left).toBe('25%');
|
|
753
|
-
expect(fill?.style.width).toBe('50%');
|
|
754
|
-
});
|
|
755
|
-
|
|
756
|
-
test('should display tick marks when configured', () => {
|
|
757
|
-
const slider = createMockSlider({
|
|
758
|
-
min: 0,
|
|
759
|
-
max: 10,
|
|
760
|
-
step: 2,
|
|
761
|
-
ticks: true
|
|
762
|
-
});
|
|
763
|
-
|
|
764
|
-
expect(slider.element.className).toContain('mtrl-slider--ticks');
|
|
765
|
-
|
|
766
|
-
const ticksContainer = slider.element.querySelector('.mtrl-slider__ticks');
|
|
767
|
-
expect(ticksContainer).toBeDefined();
|
|
768
|
-
|
|
769
|
-
const ticks = slider.element.querySelectorAll('.mtrl-slider__tick');
|
|
770
|
-
// Should have 6 ticks for values 0, 2, 4, 6, 8, 10
|
|
771
|
-
expect(ticks.length).toBe(6);
|
|
772
|
-
});
|
|
773
|
-
|
|
774
|
-
test('should display current value when configured', () => {
|
|
775
|
-
const slider = createMockSlider({
|
|
776
|
-
value: 50,
|
|
777
|
-
showValue: true
|
|
778
|
-
});
|
|
779
|
-
|
|
780
|
-
const valueElement = slider.element.querySelector('.mtrl-slider__value');
|
|
781
|
-
expect(valueElement).toBeDefined();
|
|
782
|
-
expect(valueElement?.textContent).toBe('50');
|
|
783
|
-
});
|
|
784
|
-
|
|
785
|
-
test('should apply custom value formatter', () => {
|
|
786
|
-
const formatter = (value: number) => `$${value}`;
|
|
787
|
-
|
|
788
|
-
const slider = createMockSlider({
|
|
789
|
-
value: 50,
|
|
790
|
-
showValue: true,
|
|
791
|
-
valueFormatter: formatter
|
|
792
|
-
});
|
|
793
|
-
|
|
794
|
-
const valueElement = slider.element.querySelector('.mtrl-slider__value');
|
|
795
|
-
expect(valueElement?.textContent).toBe('$50');
|
|
796
|
-
});
|
|
797
|
-
|
|
798
|
-
test('should apply disabled state', () => {
|
|
799
|
-
const slider = createMockSlider({
|
|
800
|
-
disabled: true
|
|
801
|
-
});
|
|
802
|
-
|
|
803
|
-
expect(slider.element.className).toContain('mtrl-slider--disabled');
|
|
804
|
-
expect(slider.isDisabled()).toBe(true);
|
|
805
|
-
});
|
|
806
|
-
|
|
807
|
-
test('should set label', () => {
|
|
808
|
-
const slider = createMockSlider({
|
|
809
|
-
label: 'Volume'
|
|
810
|
-
});
|
|
811
|
-
|
|
812
|
-
const label = slider.element.querySelector('.mtrl-slider__label');
|
|
813
|
-
expect(label).toBeDefined();
|
|
814
|
-
expect(label?.textContent).toBe('Volume');
|
|
815
|
-
expect(slider.getLabel()).toBe('Volume');
|
|
816
|
-
});
|
|
817
|
-
|
|
818
|
-
test('should set label position', () => {
|
|
819
|
-
const slider = createMockSlider({
|
|
820
|
-
label: 'Volume',
|
|
821
|
-
labelPosition: 'end'
|
|
822
|
-
});
|
|
823
|
-
|
|
824
|
-
expect(slider.element.className).toContain('mtrl-slider--label-end');
|
|
825
|
-
});
|
|
826
|
-
|
|
827
|
-
test('should set icon', () => {
|
|
828
|
-
const iconHtml = '<svg>volume</svg>';
|
|
829
|
-
|
|
830
|
-
const slider = createMockSlider({
|
|
831
|
-
icon: iconHtml
|
|
832
|
-
});
|
|
833
|
-
|
|
834
|
-
const icon = slider.element.querySelector('.mtrl-slider__icon');
|
|
835
|
-
expect(icon).toBeDefined();
|
|
836
|
-
expect(icon?.innerHTML).toBe(iconHtml);
|
|
837
|
-
expect(slider.getIcon()).toBe(iconHtml);
|
|
838
|
-
});
|
|
839
|
-
|
|
840
|
-
test('should set icon position', () => {
|
|
841
|
-
const slider = createMockSlider({
|
|
842
|
-
icon: '<svg>volume</svg>',
|
|
843
|
-
iconPosition: 'end'
|
|
844
|
-
});
|
|
845
|
-
|
|
846
|
-
const icon = slider.element.querySelector('.mtrl-slider__icon');
|
|
847
|
-
expect(icon?.className).toContain('mtrl-slider__icon--end');
|
|
848
|
-
});
|
|
849
|
-
|
|
850
|
-
test('should update value', () => {
|
|
851
|
-
const slider = createMockSlider({
|
|
852
|
-
min: 0,
|
|
853
|
-
max: 100,
|
|
854
|
-
value: 0
|
|
855
|
-
});
|
|
856
|
-
|
|
857
|
-
expect(slider.getValue()).toBe(0);
|
|
858
|
-
|
|
859
|
-
slider.setValue(75);
|
|
860
|
-
|
|
861
|
-
expect(slider.getValue()).toBe(75);
|
|
862
|
-
|
|
863
|
-
const fill = slider.element.querySelector('.mtrl-slider__fill');
|
|
864
|
-
expect(fill?.style.width).toBe('75%');
|
|
865
|
-
|
|
866
|
-
const thumb = slider.element.querySelector('.mtrl-slider__thumb');
|
|
867
|
-
expect(thumb?.style.left).toBe('75%');
|
|
868
|
-
});
|
|
869
|
-
|
|
870
|
-
test('should constrain value to min/max range', () => {
|
|
871
|
-
const slider = createMockSlider({
|
|
872
|
-
min: 0,
|
|
873
|
-
max: 100,
|
|
874
|
-
value: 50
|
|
875
|
-
});
|
|
876
|
-
|
|
877
|
-
slider.setValue(-10);
|
|
878
|
-
expect(slider.getValue()).toBe(0);
|
|
879
|
-
|
|
880
|
-
slider.setValue(150);
|
|
881
|
-
expect(slider.getValue()).toBe(100);
|
|
882
|
-
});
|
|
883
|
-
|
|
884
|
-
test('should snap to steps when configured', () => {
|
|
885
|
-
const slider = createMockSlider({
|
|
886
|
-
min: 0,
|
|
887
|
-
max: 10,
|
|
888
|
-
step: 2,
|
|
889
|
-
snapToSteps: true
|
|
890
|
-
});
|
|
891
|
-
|
|
892
|
-
slider.setValue(3);
|
|
893
|
-
// Rounding to nearest step, 3 is closer to 4 than 2
|
|
894
|
-
expect(slider.getValue()).toBe(4); // Snaps to nearest step (4)
|
|
895
|
-
|
|
896
|
-
slider.setValue(5.1);
|
|
897
|
-
// Rounding to nearest step, 5.1 is closer to 6 than 4
|
|
898
|
-
expect(slider.getValue()).toBe(6); // Snaps to nearest step (6)
|
|
899
|
-
});
|
|
900
|
-
|
|
901
|
-
test('should update second value for range slider', () => {
|
|
902
|
-
const slider = createMockSlider({
|
|
903
|
-
range: true,
|
|
904
|
-
value: 25,
|
|
905
|
-
secondValue: 75
|
|
906
|
-
});
|
|
907
|
-
|
|
908
|
-
slider.setSecondValue(60);
|
|
909
|
-
|
|
910
|
-
expect(slider.getSecondValue()).toBe(60);
|
|
911
|
-
|
|
912
|
-
const secondThumb = slider.element.querySelector('.mtrl-slider__thumb--second');
|
|
913
|
-
expect(secondThumb?.style.left).toBe('60%');
|
|
914
|
-
|
|
915
|
-
const fill = slider.element.querySelector('.mtrl-slider__fill');
|
|
916
|
-
expect(fill?.style.left).toBe('25%');
|
|
917
|
-
expect(fill?.style.width).toBe('35%');
|
|
918
|
-
});
|
|
919
|
-
|
|
920
|
-
test('should update min value', () => {
|
|
921
|
-
const slider = createMockSlider({
|
|
922
|
-
min: 0,
|
|
923
|
-
max: 100,
|
|
924
|
-
value: 50
|
|
925
|
-
});
|
|
926
|
-
|
|
927
|
-
slider.setMin(20);
|
|
928
|
-
|
|
929
|
-
expect(slider.getMin()).toBe(20);
|
|
930
|
-
|
|
931
|
-
// Value percentage should now be (50-20)/(100-20) = 30/80 = 37.5%
|
|
932
|
-
const fill = slider.element.querySelector('.mtrl-slider__fill');
|
|
933
|
-
expect(fill?.style.width).toBe('37.5%');
|
|
934
|
-
});
|
|
935
|
-
|
|
936
|
-
test('should update max value', () => {
|
|
937
|
-
const slider = createMockSlider({
|
|
938
|
-
min: 0,
|
|
939
|
-
max: 100,
|
|
940
|
-
value: 50
|
|
941
|
-
});
|
|
942
|
-
|
|
943
|
-
slider.setMax(200);
|
|
944
|
-
|
|
945
|
-
expect(slider.getMax()).toBe(200);
|
|
946
|
-
|
|
947
|
-
// Value percentage should now be (50-0)/(200-0) = 50/200 = 25%
|
|
948
|
-
const fill = slider.element.querySelector('.mtrl-slider__fill');
|
|
949
|
-
expect(fill?.style.width).toBe('25%');
|
|
950
|
-
});
|
|
951
|
-
|
|
952
|
-
test('should adjust value when min/max changes', () => {
|
|
953
|
-
const slider = createMockSlider({
|
|
954
|
-
min: 0,
|
|
955
|
-
max: 100,
|
|
956
|
-
value: 10
|
|
957
|
-
});
|
|
958
|
-
|
|
959
|
-
// Value should be adjusted to new minimum
|
|
960
|
-
slider.setMin(20);
|
|
961
|
-
expect(slider.getValue()).toBe(20);
|
|
962
|
-
|
|
963
|
-
// Set value to test max adjustment
|
|
964
|
-
slider.setValue(90);
|
|
965
|
-
|
|
966
|
-
// Value should be adjusted to new maximum
|
|
967
|
-
slider.setMax(80);
|
|
968
|
-
expect(slider.getValue()).toBe(80);
|
|
969
|
-
});
|
|
970
|
-
|
|
971
|
-
test('should update step size', () => {
|
|
972
|
-
const slider = createMockSlider({
|
|
973
|
-
min: 0,
|
|
974
|
-
max: 10,
|
|
975
|
-
step: 1,
|
|
976
|
-
ticks: true
|
|
977
|
-
});
|
|
978
|
-
|
|
979
|
-
// Initially should have 11 ticks (0 to 10 in steps of 1)
|
|
980
|
-
let ticks = slider.element.querySelectorAll('.mtrl-slider__tick');
|
|
981
|
-
expect(ticks.length).toBe(11);
|
|
982
|
-
|
|
983
|
-
slider.setStep(2);
|
|
984
|
-
|
|
985
|
-
expect(slider.getStep()).toBe(2);
|
|
986
|
-
|
|
987
|
-
// After updating step to 2, should have 6 ticks (0, 2, 4, 6, 8, 10)
|
|
988
|
-
ticks = slider.element.querySelectorAll('.mtrl-slider__tick');
|
|
989
|
-
expect(ticks.length).toBe(6);
|
|
990
|
-
});
|
|
991
|
-
|
|
992
|
-
test('should adjust value to new step', () => {
|
|
993
|
-
const slider = createMockSlider({
|
|
994
|
-
min: 0,
|
|
995
|
-
max: 10,
|
|
996
|
-
value: 3,
|
|
997
|
-
step: 1,
|
|
998
|
-
snapToSteps: true
|
|
999
|
-
});
|
|
1000
|
-
|
|
1001
|
-
slider.setStep(5);
|
|
1002
|
-
|
|
1003
|
-
// Value should be adjusted to nearest step of 5
|
|
1004
|
-
expect(slider.getValue()).toBe(5);
|
|
1005
|
-
});
|
|
1006
|
-
|
|
1007
|
-
test('should enable and disable slider', () => {
|
|
1008
|
-
const slider = createMockSlider();
|
|
1009
|
-
|
|
1010
|
-
expect(slider.isDisabled()).toBe(false);
|
|
1011
|
-
|
|
1012
|
-
slider.disable();
|
|
1013
|
-
|
|
1014
|
-
expect(slider.isDisabled()).toBe(true);
|
|
1015
|
-
expect(slider.element.className).toContain('mtrl-slider--disabled');
|
|
1016
|
-
|
|
1017
|
-
slider.enable();
|
|
1018
|
-
|
|
1019
|
-
expect(slider.isDisabled()).toBe(false);
|
|
1020
|
-
expect(slider.element.className).not.toContain('mtrl-slider--disabled');
|
|
1021
|
-
});
|
|
1022
|
-
|
|
1023
|
-
test('should change color', () => {
|
|
1024
|
-
const slider = createMockSlider({
|
|
1025
|
-
color: SLIDER_COLORS.PRIMARY
|
|
1026
|
-
});
|
|
1027
|
-
|
|
1028
|
-
expect(slider.getColor()).toBe(SLIDER_COLORS.PRIMARY);
|
|
1029
|
-
expect(slider.element.className).toContain('mtrl-slider--primary');
|
|
1030
|
-
|
|
1031
|
-
slider.setColor(SLIDER_COLORS.SECONDARY);
|
|
1032
|
-
|
|
1033
|
-
expect(slider.getColor()).toBe(SLIDER_COLORS.SECONDARY);
|
|
1034
|
-
expect(slider.element.className).toContain('mtrl-slider--secondary');
|
|
1035
|
-
expect(slider.element.className).not.toContain('mtrl-slider--primary');
|
|
1036
|
-
});
|
|
1037
|
-
|
|
1038
|
-
test('should change size', () => {
|
|
1039
|
-
const slider = createMockSlider({
|
|
1040
|
-
size: SLIDER_SIZES.MEDIUM
|
|
1041
|
-
});
|
|
1042
|
-
|
|
1043
|
-
expect(slider.getSize()).toBe(SLIDER_SIZES.MEDIUM);
|
|
1044
|
-
expect(slider.element.className).toContain('mtrl-slider--medium');
|
|
1045
|
-
|
|
1046
|
-
slider.setSize(SLIDER_SIZES.LARGE);
|
|
1047
|
-
|
|
1048
|
-
expect(slider.getSize()).toBe(SLIDER_SIZES.LARGE);
|
|
1049
|
-
expect(slider.element.className).toContain('mtrl-slider--large');
|
|
1050
|
-
expect(slider.element.className).not.toContain('mtrl-slider--medium');
|
|
1051
|
-
});
|
|
1052
|
-
|
|
1053
|
-
test('should toggle tick marks', () => {
|
|
1054
|
-
const slider = createMockSlider({
|
|
1055
|
-
ticks: false
|
|
1056
|
-
});
|
|
1057
|
-
|
|
1058
|
-
expect(slider.element.className).not.toContain('mtrl-slider--ticks');
|
|
1059
|
-
expect(slider.element.querySelector('.mtrl-slider__ticks')).toBeNull();
|
|
1060
|
-
|
|
1061
|
-
slider.showTicks(true);
|
|
1062
|
-
|
|
1063
|
-
expect(slider.element.className).toContain('mtrl-slider--ticks');
|
|
1064
|
-
expect(slider.element.querySelector('.mtrl-slider__ticks')).not.toBeNull();
|
|
1065
|
-
|
|
1066
|
-
slider.showTicks(false);
|
|
1067
|
-
|
|
1068
|
-
expect(slider.element.className).not.toContain('mtrl-slider--ticks');
|
|
1069
|
-
expect(slider.element.querySelector('.mtrl-slider__ticks')).toBeNull();
|
|
1070
|
-
});
|
|
1071
|
-
|
|
1072
|
-
test('should toggle value display', () => {
|
|
1073
|
-
const slider = createMockSlider({
|
|
1074
|
-
showValue: false,
|
|
1075
|
-
value: 50
|
|
1076
|
-
});
|
|
1077
|
-
|
|
1078
|
-
expect(slider.element.querySelector('.mtrl-slider__value')).toBeNull();
|
|
1079
|
-
|
|
1080
|
-
slider.showCurrentValue(true);
|
|
1081
|
-
|
|
1082
|
-
const valueElement = slider.element.querySelector('.mtrl-slider__value');
|
|
1083
|
-
expect(valueElement).not.toBeNull();
|
|
1084
|
-
expect(valueElement?.textContent).toBe('50');
|
|
1085
|
-
|
|
1086
|
-
slider.showCurrentValue(false);
|
|
1087
|
-
|
|
1088
|
-
expect(slider.element.querySelector('.mtrl-slider__value')).toBeNull();
|
|
1089
|
-
});
|
|
1090
|
-
|
|
1091
|
-
test('should change label', () => {
|
|
1092
|
-
const slider = createMockSlider();
|
|
1093
|
-
|
|
1094
|
-
expect(slider.getLabel()).toBe('');
|
|
1095
|
-
expect(slider.element.querySelector('.mtrl-slider__label')).toBeNull();
|
|
1096
|
-
|
|
1097
|
-
slider.setLabel('Volume');
|
|
1098
|
-
|
|
1099
|
-
expect(slider.getLabel()).toBe('Volume');
|
|
1100
|
-
const label = slider.element.querySelector('.mtrl-slider__label');
|
|
1101
|
-
expect(label).not.toBeNull();
|
|
1102
|
-
expect(label?.textContent).toBe('Volume');
|
|
1103
|
-
|
|
1104
|
-
// Change existing label
|
|
1105
|
-
slider.setLabel('Brightness');
|
|
1106
|
-
expect(label?.textContent).toBe('Brightness');
|
|
1107
|
-
|
|
1108
|
-
// Remove label
|
|
1109
|
-
slider.setLabel('');
|
|
1110
|
-
expect(slider.element.querySelector('.mtrl-slider__label')).toBeNull();
|
|
1111
|
-
});
|
|
1112
|
-
|
|
1113
|
-
test('should change icon', () => {
|
|
1114
|
-
const slider = createMockSlider();
|
|
1115
|
-
|
|
1116
|
-
expect(slider.getIcon()).toBe('');
|
|
1117
|
-
expect(slider.element.querySelector('.mtrl-slider__icon')).toBeNull();
|
|
1118
|
-
|
|
1119
|
-
const iconHtml = '<svg>volume</svg>';
|
|
1120
|
-
slider.setIcon(iconHtml);
|
|
1121
|
-
|
|
1122
|
-
expect(slider.getIcon()).toBe(iconHtml);
|
|
1123
|
-
const icon = slider.element.querySelector('.mtrl-slider__icon');
|
|
1124
|
-
expect(icon).not.toBeNull();
|
|
1125
|
-
expect(icon?.innerHTML).toBe(iconHtml);
|
|
1126
|
-
|
|
1127
|
-
// Change existing icon
|
|
1128
|
-
const newIconHtml = '<svg>brightness</svg>';
|
|
1129
|
-
slider.setIcon(newIconHtml);
|
|
1130
|
-
expect(icon?.innerHTML).toBe(newIconHtml);
|
|
1131
|
-
|
|
1132
|
-
// Remove icon
|
|
1133
|
-
slider.setIcon('');
|
|
1134
|
-
expect(slider.element.querySelector('.mtrl-slider__icon')).toBeNull();
|
|
1135
|
-
});
|
|
1136
|
-
|
|
1137
|
-
test('should emit change events', () => {
|
|
1138
|
-
const slider = createMockSlider({
|
|
1139
|
-
value: 0
|
|
1140
|
-
});
|
|
1141
|
-
|
|
1142
|
-
let changeEventFired = false;
|
|
1143
|
-
let eventValue: number | null = null;
|
|
1144
|
-
|
|
1145
|
-
slider.on(SLIDER_EVENTS.CHANGE, (event) => {
|
|
1146
|
-
changeEventFired = true;
|
|
1147
|
-
eventValue = event.value;
|
|
1148
|
-
});
|
|
1149
|
-
|
|
1150
|
-
slider.setValue(50, true);
|
|
1151
|
-
|
|
1152
|
-
expect(changeEventFired).toBe(true);
|
|
1153
|
-
expect(eventValue).toBe(50);
|
|
1154
|
-
});
|
|
1155
|
-
|
|
1156
|
-
test('should emit input events', () => {
|
|
1157
|
-
const slider = createMockSlider({
|
|
1158
|
-
value: 0
|
|
1159
|
-
});
|
|
1160
|
-
|
|
1161
|
-
let inputEventFired = false;
|
|
1162
|
-
|
|
1163
|
-
slider.on(SLIDER_EVENTS.INPUT, () => {
|
|
1164
|
-
inputEventFired = true;
|
|
1165
|
-
});
|
|
1166
|
-
|
|
1167
|
-
slider.setValue(50, true);
|
|
1168
|
-
|
|
1169
|
-
expect(inputEventFired).toBe(true);
|
|
1170
|
-
});
|
|
1171
|
-
|
|
1172
|
-
test('should include secondValue in events for range slider', () => {
|
|
1173
|
-
const slider = createMockSlider({
|
|
1174
|
-
range: true,
|
|
1175
|
-
value: 25,
|
|
1176
|
-
secondValue: 75
|
|
1177
|
-
});
|
|
1178
|
-
|
|
1179
|
-
let secondValue: number | null = null;
|
|
1180
|
-
|
|
1181
|
-
slider.on(SLIDER_EVENTS.CHANGE, (event) => {
|
|
1182
|
-
secondValue = event.secondValue;
|
|
1183
|
-
});
|
|
1184
|
-
|
|
1185
|
-
slider.setValue(30, true);
|
|
1186
|
-
|
|
1187
|
-
expect(secondValue).toBe(75);
|
|
1188
|
-
});
|
|
1189
|
-
|
|
1190
|
-
test('should remove event listeners', () => {
|
|
1191
|
-
const slider = createMockSlider();
|
|
1192
|
-
|
|
1193
|
-
let eventCount = 0;
|
|
1194
|
-
|
|
1195
|
-
const handler = () => {
|
|
1196
|
-
eventCount++;
|
|
1197
|
-
};
|
|
1198
|
-
|
|
1199
|
-
slider.on(SLIDER_EVENTS.CHANGE, handler);
|
|
1200
|
-
|
|
1201
|
-
slider.setValue(10, true);
|
|
1202
|
-
expect(eventCount).toBe(1);
|
|
1203
|
-
|
|
1204
|
-
slider.off(SLIDER_EVENTS.CHANGE, handler);
|
|
1205
|
-
|
|
1206
|
-
slider.setValue(20, true);
|
|
1207
|
-
expect(eventCount).toBe(1); // Count should not increase
|
|
1208
|
-
});
|
|
1209
|
-
|
|
1210
|
-
test('should be properly destroyed', () => {
|
|
1211
|
-
const slider = createMockSlider();
|
|
1212
|
-
document.body.appendChild(slider.element);
|
|
1213
|
-
|
|
1214
|
-
expect(document.body.contains(slider.element)).toBe(true);
|
|
1215
|
-
|
|
1216
|
-
slider.destroy();
|
|
1217
|
-
|
|
1218
|
-
expect(document.body.contains(slider.element)).toBe(false);
|
|
1219
|
-
});
|
|
1220
|
-
});
|