mtrl 0.2.8 → 0.3.0

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.
Files changed (64) hide show
  1. package/index.ts +4 -0
  2. package/package.json +1 -1
  3. package/src/components/button/button.ts +34 -5
  4. package/src/components/navigation/api.ts +131 -96
  5. package/src/components/navigation/features/controller.ts +273 -0
  6. package/src/components/navigation/features/items.ts +133 -64
  7. package/src/components/navigation/navigation.ts +17 -2
  8. package/src/components/navigation/system/core.ts +302 -0
  9. package/src/components/navigation/system/events.ts +240 -0
  10. package/src/components/navigation/system/index.ts +184 -0
  11. package/src/components/navigation/system/mobile.ts +278 -0
  12. package/src/components/navigation/system/state.ts +77 -0
  13. package/src/components/navigation/system/types.ts +364 -0
  14. package/src/components/slider/config.ts +20 -2
  15. package/src/components/slider/features/controller.ts +737 -0
  16. package/src/components/slider/features/handlers.ts +18 -16
  17. package/src/components/slider/features/index.ts +3 -2
  18. package/src/components/slider/features/range.ts +104 -0
  19. package/src/components/slider/schema.ts +141 -0
  20. package/src/components/slider/slider.ts +34 -13
  21. package/src/components/switch/api.ts +16 -0
  22. package/src/components/switch/config.ts +1 -18
  23. package/src/components/switch/features.ts +198 -0
  24. package/src/components/switch/index.ts +1 -0
  25. package/src/components/switch/switch.ts +3 -3
  26. package/src/components/switch/types.ts +14 -2
  27. package/src/components/textfield/api.ts +53 -0
  28. package/src/components/textfield/features.ts +322 -0
  29. package/src/components/textfield/textfield.ts +8 -0
  30. package/src/components/textfield/types.ts +12 -3
  31. package/src/components/timepicker/clockdial.ts +1 -4
  32. package/src/core/compose/features/textinput.ts +15 -2
  33. package/src/core/composition/features/dom.ts +45 -0
  34. package/src/core/composition/features/icon.ts +131 -0
  35. package/src/core/composition/features/index.ts +12 -0
  36. package/src/core/composition/features/label.ts +155 -0
  37. package/src/core/composition/features/layout.ts +47 -0
  38. package/src/core/composition/index.ts +26 -0
  39. package/src/core/index.ts +1 -1
  40. package/src/core/layout/README.md +350 -0
  41. package/src/core/layout/array.ts +181 -0
  42. package/src/core/layout/create.ts +55 -0
  43. package/src/core/layout/index.ts +26 -0
  44. package/src/core/layout/object.ts +124 -0
  45. package/src/core/layout/processor.ts +58 -0
  46. package/src/core/layout/result.ts +85 -0
  47. package/src/core/layout/types.ts +125 -0
  48. package/src/core/layout/utils.ts +136 -0
  49. package/src/index.ts +1 -0
  50. package/src/styles/abstract/_variables.scss +28 -0
  51. package/src/styles/components/_navigation-mobile.scss +244 -0
  52. package/src/styles/components/_navigation-system.scss +151 -0
  53. package/src/styles/components/_switch.scss +133 -69
  54. package/src/styles/components/_textfield.scss +259 -27
  55. package/demo/build.ts +0 -349
  56. package/demo/index.html +0 -110
  57. package/demo/main.js +0 -448
  58. package/demo/styles.css +0 -239
  59. package/server.ts +0 -86
  60. package/src/components/slider/features/slider.ts +0 -318
  61. package/src/components/slider/features/structure.ts +0 -181
  62. package/src/components/slider/features/ui.ts +0 -388
  63. package/src/components/textfield/constants.ts +0 -100
  64. package/src/core/layout/index.js +0 -95
@@ -0,0 +1,737 @@
1
+ // src/components/slider/features/controller.ts
2
+ import { SLIDER_EVENTS } from '../constants';
3
+ import { SliderConfig } from '../types';
4
+ import { createHandlers } from './handlers';
5
+
6
+ /**
7
+ * Add controller functionality to slider component
8
+ * Manages state, events, user interactions, and UI rendering
9
+ *
10
+ * @param config Slider configuration
11
+ * @returns Component enhancer with slider controller functionality
12
+ */
13
+ export const withController = (config: SliderConfig) => component => {
14
+ // Ensure component has required properties
15
+ if (!component.element || !component.components) {
16
+ console.warn('Cannot initialize slider controller: missing element or components');
17
+ return component;
18
+ }
19
+
20
+ // Initialize state
21
+ const state = {
22
+ value: config.value !== undefined ? config.value : 0,
23
+ secondValue: config.secondValue !== undefined ? config.secondValue : null,
24
+ min: config.min !== undefined ? config.min : 0,
25
+ max: config.max !== undefined ? config.max : 100,
26
+ step: config.step !== undefined ? config.step : 1,
27
+ dragging: false,
28
+ activeBubble: null,
29
+ activeHandle: null,
30
+ ticks: [],
31
+ valueHideTimer: null,
32
+ component
33
+ };
34
+
35
+ // Create event helpers
36
+ const eventHelpers = {
37
+ triggerEvent(eventName, originalEvent = null) {
38
+ const eventData = {
39
+ slider: state.component,
40
+ value: state.value,
41
+ secondValue: state.secondValue,
42
+ originalEvent,
43
+ preventDefault: () => { eventData.defaultPrevented = true; },
44
+ defaultPrevented: false
45
+ };
46
+
47
+ state.component.emit(eventName, eventData);
48
+ return eventData;
49
+ }
50
+ };
51
+
52
+ //=============================================================================
53
+ // UI RENDERING FUNCTIONS
54
+ //=============================================================================
55
+
56
+ /**
57
+ * Gets required components from state, safely handling missing components
58
+ */
59
+ const getComponents = () => {
60
+ // Return empty object if component or components are missing
61
+ if (!state.component?.components) {
62
+ return {};
63
+ }
64
+
65
+ // Get flattened components
66
+ return state.component.components;
67
+ };
68
+
69
+ /**
70
+ * Calculates percentage position for a value
71
+ * Maps from value space (min-max) to percentage space (0-100)
72
+ */
73
+ const getPercentage = (value) => {
74
+ const range = state.max - state.min;
75
+ return range === 0 ? 0 : ((value - state.min) / range) * 100;
76
+ };
77
+
78
+ /**
79
+ * Gets track dimensions and constraints for positioning calculations
80
+ * Handles edge constraints and padding for proper handle positioning
81
+ */
82
+ const getTrackDimensions = () => {
83
+ const components = getComponents();
84
+ const { handle, container, track } = components;
85
+
86
+ if (!handle || !container || !track) return null;
87
+
88
+ try {
89
+ const handleRect = handle.getBoundingClientRect();
90
+ const trackRect = container.getBoundingClientRect();
91
+ const handleSize = handleRect.width || 20;
92
+ const trackSize = trackRect.width;
93
+
94
+ const edgeConstraint = (handleSize / 2) / trackSize * 100;
95
+ const paddingPixels = state.activeHandle ? 6 : 8;
96
+ const paddingPercent = (paddingPixels / trackSize) * 100;
97
+
98
+ return { handleSize, trackSize, edgeConstraint, paddingPercent };
99
+ } catch (error) {
100
+ console.warn('Error calculating track dimensions:', error);
101
+ return {
102
+ handleSize: 20,
103
+ trackSize: 200,
104
+ edgeConstraint: 5,
105
+ paddingPercent: 4
106
+ };
107
+ }
108
+ };
109
+
110
+ /**
111
+ * Maps value percentage to visual position with edge constraints
112
+ * Ensures handles stay within the visible track area
113
+ */
114
+ const mapValueToVisualPercent = (valuePercent, edgeConstraint) => {
115
+ const minEdge = edgeConstraint;
116
+ const maxEdge = 100 - edgeConstraint;
117
+ const visualRange = maxEdge - minEdge;
118
+
119
+ if (valuePercent <= 0) return minEdge;
120
+ if (valuePercent >= 100) return maxEdge;
121
+ return minEdge + (valuePercent / 100) * visualRange;
122
+ };
123
+
124
+ /**
125
+ * Gets slider value from a position on the track
126
+ * Maps from pixel position to slider value
127
+ */
128
+ const getValueFromPosition = (position) => {
129
+ const components = getComponents();
130
+ const { handle, container } = components;
131
+
132
+ if (!handle || !container) return state.min;
133
+
134
+ try {
135
+ const containerRect = container.getBoundingClientRect();
136
+ const range = state.max - state.min;
137
+ const handleWidth = handle.getBoundingClientRect().width || 20;
138
+
139
+ const leftEdge = containerRect.left + (handleWidth / 2);
140
+ const rightEdge = containerRect.right - (handleWidth / 2);
141
+ const effectiveWidth = rightEdge - leftEdge;
142
+
143
+ const adjustedPosition = Math.max(leftEdge, Math.min(rightEdge, position));
144
+ const percentageFromLeft = (adjustedPosition - leftEdge) / effectiveWidth;
145
+
146
+ return state.min + percentageFromLeft * range;
147
+ } catch (error) {
148
+ console.warn('Error calculating value from position:', error);
149
+ return state.min;
150
+ }
151
+ };
152
+
153
+ /**
154
+ * Rounds a value to the nearest step
155
+ * Used for discrete sliders
156
+ */
157
+ const roundToStep = (value) => {
158
+ const step = state.step;
159
+ if (!step || step <= 0) return value;
160
+
161
+ const steps = Math.round((value - state.min) / step);
162
+ return state.min + (steps * step);
163
+ };
164
+
165
+ /**
166
+ * Clamps a value between min and max
167
+ * Ensures values stay within valid range
168
+ */
169
+ const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
170
+
171
+ /**
172
+ * Updates handle and bubble positions
173
+ * Positions elements according to current state values
174
+ */
175
+ const updateHandlePositions = () => {
176
+ const components = getComponents();
177
+ const { handle, container, valueBubble, secondHandle, secondValueBubble } = components;
178
+
179
+ if (!handle || !container) return;
180
+
181
+ const dims = getTrackDimensions();
182
+ if (!dims) return;
183
+
184
+ const { edgeConstraint } = dims;
185
+
186
+ // Update main handle position
187
+ const percent = getPercentage(state.value);
188
+ const adjustedPercent = mapValueToVisualPercent(percent, edgeConstraint);
189
+
190
+ handle.style.left = `${adjustedPercent}%`;
191
+ handle.style.transform = 'translate(-50%, -50%)';
192
+
193
+ if (valueBubble) {
194
+ valueBubble.style.left = `${adjustedPercent}%`;
195
+ valueBubble.style.transform = 'translateX(-50%)';
196
+ }
197
+
198
+ // Update second handle if range slider
199
+ if (config.range && secondHandle && state.secondValue !== null) {
200
+ const secondPercent = getPercentage(state.secondValue);
201
+ const adjustedSecondPercent = mapValueToVisualPercent(secondPercent, edgeConstraint);
202
+
203
+ secondHandle.style.left = `${adjustedSecondPercent}%`;
204
+ secondHandle.style.transform = 'translate(-50%, -50%)';
205
+
206
+ if (secondValueBubble) {
207
+ secondValueBubble.style.left = `${adjustedSecondPercent}%`;
208
+ secondValueBubble.style.transform = 'translateX(-50%)';
209
+ }
210
+ }
211
+
212
+ // Update ARIA attributes
213
+ handle.setAttribute('aria-valuenow', String(state.value));
214
+ if (config.range && secondHandle && state.secondValue !== null) {
215
+ secondHandle.setAttribute('aria-valuenow', String(state.secondValue));
216
+ }
217
+ };
218
+
219
+ /**
220
+ * Updates all track segments
221
+ * Handles different rendering for single and range sliders
222
+ */
223
+ const updateTrackSegments = () => {
224
+ const components = getComponents();
225
+ const { track, container, handle, startTrack, activeTrack, remainingTrack } = components;
226
+
227
+ if (!track || !container || !handle) return;
228
+
229
+ // Safety check for required elements
230
+ if (!activeTrack || !remainingTrack) {
231
+ console.warn('Missing track segments, cannot update track display');
232
+ return;
233
+ }
234
+
235
+ const dims = getTrackDimensions();
236
+ if (!dims) return;
237
+
238
+ const { handleSize, trackSize, paddingPercent } = dims;
239
+ const edgeConstraint = (handleSize / 2) / trackSize * 100;
240
+
241
+ if (config.range && state.secondValue !== null) {
242
+ // Range slider setup
243
+ const lowerValue = Math.min(state.value, state.secondValue);
244
+ const higherValue = Math.max(state.value, state.secondValue);
245
+ const lowerPercent = getPercentage(lowerValue);
246
+ const higherPercent = getPercentage(higherValue);
247
+
248
+ const adjustedLower = mapValueToVisualPercent(lowerPercent, edgeConstraint);
249
+ const adjustedHigher = mapValueToVisualPercent(higherPercent, edgeConstraint);
250
+
251
+ // Start track (before first handle)
252
+ if (startTrack) {
253
+ if (lowerPercent > 1) {
254
+ startTrack.style.display = 'block';
255
+ startTrack.style.left = '0';
256
+ startTrack.style.right = `${100 - (adjustedLower - paddingPercent)}%`;
257
+ startTrack.style.width = 'auto';
258
+ } else {
259
+ startTrack.style.display = 'none';
260
+ }
261
+ }
262
+
263
+ // Active track (between handles)
264
+ const valueDiffPercent = Math.abs(higherPercent - lowerPercent);
265
+ const hideThreshold = (handleSize / trackSize) * 100;
266
+
267
+ if (valueDiffPercent <= hideThreshold) {
268
+ activeTrack.style.display = 'none';
269
+ } else {
270
+ activeTrack.style.display = 'block';
271
+ activeTrack.style.left = `${adjustedLower + paddingPercent}%`;
272
+ activeTrack.style.right = `${100 - (adjustedHigher - paddingPercent)}%`;
273
+ activeTrack.style.width = 'auto';
274
+ }
275
+
276
+ // Remaining track (after second handle)
277
+ remainingTrack.style.display = 'block';
278
+ remainingTrack.style.left = `${adjustedHigher + paddingPercent}%`;
279
+ remainingTrack.style.right = '0';
280
+ remainingTrack.style.width = 'auto';
281
+ } else {
282
+ // Single handle slider
283
+ const valuePercent = getPercentage(state.value);
284
+ const adjustedPercent = mapValueToVisualPercent(valuePercent, edgeConstraint);
285
+
286
+ // Hide start track for single slider
287
+ if (startTrack) {
288
+ startTrack.style.display = 'none';
289
+ }
290
+
291
+ // Active track (filled part)
292
+ activeTrack.style.display = 'block';
293
+ activeTrack.style.left = '0';
294
+ activeTrack.style.right = `${100 - (adjustedPercent - paddingPercent)}%`;
295
+ activeTrack.style.width = 'auto';
296
+
297
+ // Remaining track (unfilled part)
298
+ remainingTrack.style.display = 'block';
299
+ remainingTrack.style.left = `${adjustedPercent + paddingPercent}%`;
300
+ remainingTrack.style.right = '0';
301
+ remainingTrack.style.width = 'auto';
302
+ }
303
+ };
304
+
305
+ /**
306
+ * Updates value bubble content
307
+ * Applies formatting to displayed values
308
+ */
309
+ const updateValueBubbles = () => {
310
+ const components = getComponents();
311
+ const { valueBubble, secondValueBubble } = components;
312
+
313
+ if (!valueBubble) return;
314
+
315
+ const formatter = config.valueFormatter || (value => value.toString());
316
+ valueBubble.textContent = formatter(state.value);
317
+
318
+ if (config.range && secondValueBubble && state.secondValue !== null) {
319
+ secondValueBubble.textContent = formatter(state.secondValue);
320
+ }
321
+ };
322
+
323
+ /**
324
+ * Shows or hides value bubble
325
+ * Controls visibility for value indicators
326
+ */
327
+ const showValueBubble = (bubbleElement, show) => {
328
+ if (!bubbleElement || !config.showValue) return;
329
+
330
+ const bubbleClass = state.component.getClass('slider-value');
331
+ bubbleElement.classList[show ? 'add' : 'remove'](`${bubbleClass}--visible`);
332
+ };
333
+
334
+ /**
335
+ * Generates tick marks
336
+ * Creates visual indicators for discrete values
337
+ */
338
+ const generateTicks = () => {
339
+ const components = getComponents();
340
+ const { ticksContainer, container } = components;
341
+
342
+ if (!ticksContainer || !container) return;
343
+
344
+ // Clear existing ticks
345
+ while (ticksContainer.firstChild) {
346
+ ticksContainer.removeChild(ticksContainer.firstChild);
347
+ }
348
+
349
+ state.ticks = [];
350
+ if (!config.ticks) return;
351
+
352
+ const numSteps = Math.floor((state.max - state.min) / state.step);
353
+ const tickValues = [];
354
+
355
+ // Generate tick values
356
+ for (let i = 0; i <= numSteps; i++) {
357
+ const value = state.min + (i * state.step);
358
+ if (value <= state.max) tickValues.push(value);
359
+ }
360
+
361
+ // Ensure max value is included
362
+ if (tickValues[tickValues.length - 1] !== state.max) {
363
+ tickValues.push(state.max);
364
+ }
365
+
366
+ // CSS classes
367
+ const tickClass = state.component.getClass('slider-tick');
368
+ const activeClass = `${tickClass}--active`;
369
+ const inactiveClass = `${tickClass}--inactive`;
370
+ const hiddenClass = `${tickClass}--hidden`;
371
+
372
+ // Create tick elements
373
+ tickValues.forEach(value => {
374
+ const percent = getPercentage(value);
375
+ const tick = document.createElement('div');
376
+ tick.classList.add(tickClass);
377
+ tick.style.left = `${percent}%`;
378
+
379
+ // Determine tick active state
380
+ const isExactlySelected = (value === state.value ||
381
+ (config.range && state.secondValue !== null && value === state.secondValue));
382
+
383
+ if (isExactlySelected) {
384
+ tick.classList.add(hiddenClass);
385
+ } else if (config.range && state.secondValue !== null) {
386
+ const lowerValue = Math.min(state.value, state.secondValue);
387
+ const higherValue = Math.max(state.value, state.secondValue);
388
+
389
+ tick.classList.add(value >= lowerValue && value <= higherValue ? activeClass : inactiveClass);
390
+ } else {
391
+ tick.classList.add(value <= state.value ? activeClass : inactiveClass);
392
+ }
393
+
394
+ ticksContainer.appendChild(tick);
395
+ state.ticks.push(tick);
396
+ });
397
+ };
398
+
399
+ /**
400
+ * Updates active state of tick marks
401
+ * Sets visual state based on current values
402
+ */
403
+ const updateTicks = () => {
404
+ if (!state.ticks || state.ticks.length === 0) return;
405
+
406
+ const tickClass = state.component.getClass('slider-tick');
407
+ const activeClass = `${tickClass}--active`;
408
+ const inactiveClass = `${tickClass}--inactive`;
409
+ const hiddenClass = `${tickClass}--hidden`;
410
+
411
+ state.ticks.forEach((tick, index) => {
412
+ // Safety check for null tick
413
+ if (!tick) return;
414
+
415
+ // Calculate the value for this tick
416
+ const tickValue = state.min + (index * state.step);
417
+
418
+ // Reset visibility first
419
+ tick.classList.remove(hiddenClass);
420
+
421
+ // Check if this tick should be hidden (matches exactly a selected value)
422
+ const isExactlySelected = (tickValue === state.value ||
423
+ (config.range && state.secondValue !== null && tickValue === state.secondValue));
424
+
425
+ if (isExactlySelected) {
426
+ tick.classList.add(hiddenClass);
427
+ } else if (config.range && state.secondValue !== null) {
428
+ // Range slider - ticks between values should be active
429
+ const lowerValue = Math.min(state.value, state.secondValue);
430
+ const higherValue = Math.max(state.value, state.secondValue);
431
+
432
+ if (tickValue >= lowerValue && tickValue <= higherValue) {
433
+ tick.classList.add(activeClass);
434
+ tick.classList.remove(inactiveClass);
435
+ } else {
436
+ tick.classList.remove(activeClass);
437
+ tick.classList.add(inactiveClass);
438
+ }
439
+ } else {
440
+ // Single slider - ticks below value should be active
441
+ if (tickValue <= state.value) {
442
+ tick.classList.add(activeClass);
443
+ tick.classList.remove(inactiveClass);
444
+ } else {
445
+ tick.classList.remove(activeClass);
446
+ tick.classList.add(inactiveClass);
447
+ }
448
+ }
449
+ });
450
+ };
451
+
452
+ /**
453
+ * Renders all UI elements to match current state
454
+ * Central method for keeping UI in sync with state
455
+ */
456
+ const render = () => {
457
+ try {
458
+ updateHandlePositions();
459
+ updateTrackSegments();
460
+ updateValueBubbles();
461
+ updateTicks();
462
+ } catch (error) {
463
+ console.warn('Error rendering UI:', error);
464
+ }
465
+ };
466
+
467
+ // Create UI renderer interface for event handlers
468
+ const uiRenderer = {
469
+ getPercentage,
470
+ getValueFromPosition,
471
+ roundToStep,
472
+ clamp,
473
+ showValueBubble,
474
+ generateTicks,
475
+ updateTicks,
476
+ updateUi: render, // For backward compatibility
477
+ render
478
+ };
479
+
480
+ // Create event handlers with our renderer
481
+ const handlers = createHandlers(config, state, uiRenderer, eventHelpers);
482
+
483
+ // Initialize slider controller
484
+ const initController = () => {
485
+ try {
486
+ // Verify we have the necessary components
487
+ if (!component.components || !component.components.handle) {
488
+ console.warn('Cannot initialize slider controller: missing required components');
489
+ return;
490
+ }
491
+
492
+ // Set ARIA attributes
493
+ component.element.setAttribute('aria-valuemin', String(state.min));
494
+ component.element.setAttribute('aria-valuemax', String(state.max));
495
+ component.element.setAttribute('aria-valuenow', String(state.value));
496
+
497
+ const { handle, secondHandle } = component.components;
498
+
499
+ // Set handle attributes
500
+ if (handle) {
501
+ handle.setAttribute('aria-valuemin', String(state.min));
502
+ handle.setAttribute('aria-valuemax', String(state.max));
503
+ handle.setAttribute('aria-valuenow', String(state.value));
504
+
505
+ if (config.range && secondHandle && state.secondValue !== null) {
506
+ secondHandle.setAttribute('aria-valuemin', String(state.min));
507
+ secondHandle.setAttribute('aria-valuemax', String(state.max));
508
+ secondHandle.setAttribute('aria-valuenow', String(state.secondValue));
509
+ }
510
+ }
511
+
512
+ // Setup event listeners
513
+ handlers.setupEventListeners();
514
+
515
+ // Initially generate ticks if needed
516
+ if (config.ticks || config.tickLabels) {
517
+ generateTicks();
518
+ }
519
+
520
+ // Initial UI update
521
+ render();
522
+
523
+ // Force one more UI update after a delay to ensure proper positioning
524
+ setTimeout(() => {
525
+ render();
526
+ }, 50);
527
+ } catch (error) {
528
+ console.error('Error initializing slider controller:', error);
529
+ }
530
+ };
531
+
532
+ // Register with lifecycle if available
533
+ if (component.lifecycle) {
534
+ const originalDestroy = component.lifecycle.destroy || (() => {});
535
+ component.lifecycle.destroy = () => {
536
+ handlers.cleanupEventListeners();
537
+ originalDestroy();
538
+ };
539
+ }
540
+
541
+ // Schedule initialization after current execution completes
542
+ setTimeout(() => {
543
+ initController();
544
+ }, 0);
545
+
546
+ // Return enhanced component
547
+ return {
548
+ ...component,
549
+ // Provide controller API under 'slider' property for backward compatibility
550
+ slider: {
551
+ /**
552
+ * Sets slider value
553
+ * @param value New value
554
+ * @param triggerEvent Whether to trigger change event
555
+ * @returns Slider controller for chaining
556
+ */
557
+ setValue(value, triggerEvent = true) {
558
+ const newValue = clamp(value, state.min, state.max);
559
+ state.value = newValue;
560
+ render();
561
+
562
+ if (triggerEvent) {
563
+ eventHelpers.triggerEvent(SLIDER_EVENTS.CHANGE);
564
+ }
565
+
566
+ return this;
567
+ },
568
+
569
+ /**
570
+ * Gets slider value
571
+ * @returns Current value
572
+ */
573
+ getValue() {
574
+ return state.value;
575
+ },
576
+
577
+ /**
578
+ * Sets secondary slider value (for range slider)
579
+ * @param value New secondary value
580
+ * @param triggerEvent Whether to trigger change event
581
+ * @returns Slider controller for chaining
582
+ */
583
+ setSecondValue(value, triggerEvent = true) {
584
+ if (!config.range) return this;
585
+
586
+ const newValue = clamp(value, state.min, state.max);
587
+ state.secondValue = newValue;
588
+ render();
589
+
590
+ if (triggerEvent) {
591
+ eventHelpers.triggerEvent(SLIDER_EVENTS.CHANGE);
592
+ }
593
+
594
+ return this;
595
+ },
596
+
597
+ /**
598
+ * Gets secondary slider value
599
+ * @returns Current secondary value or null
600
+ */
601
+ getSecondValue() {
602
+ return config.range ? state.secondValue : null;
603
+ },
604
+
605
+ /**
606
+ * Sets slider minimum value
607
+ * @param min New minimum value
608
+ * @returns Slider controller for chaining
609
+ */
610
+ setMin(min) {
611
+ state.min = min;
612
+
613
+ // Update ARIA attributes if elements exist
614
+ component.element.setAttribute('aria-valuemin', String(min));
615
+ if (component.components?.handle) {
616
+ component.components.handle.setAttribute('aria-valuemin', String(min));
617
+ }
618
+
619
+ if (config.range && component.components?.secondHandle) {
620
+ component.components.secondHandle.setAttribute('aria-valuemin', String(min));
621
+ }
622
+
623
+ // Clamp values to new min
624
+ if (state.value < min) state.value = min;
625
+ if (config.range && state.secondValue !== null && state.secondValue < min) {
626
+ state.secondValue = min;
627
+ }
628
+
629
+ // Regenerate ticks if needed
630
+ if (config.ticks || config.tickLabels) {
631
+ generateTicks();
632
+ }
633
+
634
+ render();
635
+ return this;
636
+ },
637
+
638
+ /**
639
+ * Gets slider minimum value
640
+ * @returns Current minimum value
641
+ */
642
+ getMin() {
643
+ return state.min;
644
+ },
645
+
646
+ /**
647
+ * Sets slider maximum value
648
+ * @param max New maximum value
649
+ * @returns Slider controller for chaining
650
+ */
651
+ setMax(max) {
652
+ state.max = max;
653
+
654
+ // Update ARIA attributes if elements exist
655
+ component.element.setAttribute('aria-valuemax', String(max));
656
+ if (component.components?.handle) {
657
+ component.components.handle.setAttribute('aria-valuemax', String(max));
658
+ }
659
+
660
+ if (config.range && component.components?.secondHandle) {
661
+ component.components.secondHandle.setAttribute('aria-valuemax', String(max));
662
+ }
663
+
664
+ // Clamp values to new max
665
+ if (state.value > max) state.value = max;
666
+ if (config.range && state.secondValue !== null && state.secondValue > max) {
667
+ state.secondValue = max;
668
+ }
669
+
670
+ // Regenerate ticks if needed
671
+ if (config.ticks || config.tickLabels) {
672
+ generateTicks();
673
+ }
674
+
675
+ render();
676
+ return this;
677
+ },
678
+
679
+ /**
680
+ * Gets slider maximum value
681
+ * @returns Current maximum value
682
+ */
683
+ getMax() {
684
+ return state.max;
685
+ },
686
+
687
+ /**
688
+ * Sets slider step size
689
+ * @param step New step size
690
+ * @returns Slider controller for chaining
691
+ */
692
+ setStep(step) {
693
+ state.step = step;
694
+
695
+ // Add or remove discrete class
696
+ component.element.classList[step > 0 ? 'add' : 'remove'](
697
+ `${component.getClass('slider')}--discrete`
698
+ );
699
+
700
+ // Regenerate ticks if needed
701
+ if (config.ticks || config.tickLabels) {
702
+ generateTicks();
703
+ updateTicks();
704
+ }
705
+
706
+ return this;
707
+ },
708
+
709
+ /**
710
+ * Gets slider step size
711
+ * @returns Current step size
712
+ */
713
+ getStep() {
714
+ return state.step;
715
+ },
716
+
717
+ /**
718
+ * Regenerate tick marks and labels
719
+ * @returns Slider controller for chaining
720
+ */
721
+ regenerateTicks() {
722
+ generateTicks();
723
+ updateTicks();
724
+ return this;
725
+ },
726
+
727
+ /**
728
+ * Update all UI elements
729
+ * @returns Slider controller for chaining
730
+ */
731
+ updateUi() {
732
+ render();
733
+ return this;
734
+ }
735
+ }
736
+ };
737
+ };