mtrl 0.2.6 → 0.2.8
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/demo/build.ts +349 -0
- package/demo/index.html +110 -0
- package/demo/main.js +448 -0
- package/demo/styles.css +239 -0
- package/index.ts +18 -0
- package/package.json +14 -3
- package/server.ts +86 -0
- package/src/components/badge/api.ts +70 -63
- package/src/components/badge/badge.ts +16 -2
- package/src/components/badge/config.ts +66 -13
- package/src/components/badge/features.ts +51 -42
- package/src/components/badge/index.ts +27 -2
- package/src/components/badge/types.ts +62 -30
- package/src/components/bottom-app-bar/bottom-app-bar.ts +154 -0
- package/src/components/bottom-app-bar/config.ts +29 -0
- package/src/components/bottom-app-bar/index.ts +17 -0
- package/src/components/bottom-app-bar/types.ts +114 -0
- package/src/components/button/api.ts +5 -0
- package/src/components/button/button.ts +0 -1
- package/src/components/button/config.ts +6 -2
- package/src/components/button/index.ts +10 -2
- package/src/components/button/types.ts +20 -2
- package/src/components/card/card.ts +13 -25
- package/src/components/card/config.ts +83 -30
- package/src/components/card/content.ts +8 -10
- package/src/components/card/features.ts +4 -3
- package/src/components/card/index.ts +29 -2
- package/src/components/card/types.ts +33 -22
- package/src/components/checkbox/config.ts +3 -4
- package/src/components/checkbox/index.ts +1 -2
- package/src/components/checkbox/types.ts +12 -3
- package/src/components/chip/api.ts +170 -221
- package/src/components/chip/chip.ts +34 -302
- package/src/components/chip/config.ts +1 -2
- package/src/components/chip/index.ts +10 -2
- package/src/components/chip/types.ts +224 -35
- package/src/components/datepicker/api.ts +265 -0
- package/src/components/datepicker/config.ts +141 -0
- package/src/components/datepicker/datepicker.ts +341 -0
- package/src/components/datepicker/index.ts +12 -0
- package/src/components/datepicker/render.ts +450 -0
- package/src/components/datepicker/types.ts +397 -0
- package/src/components/datepicker/utils.ts +289 -0
- package/src/components/dialog/api.ts +55 -21
- package/src/components/dialog/config.ts +12 -9
- package/src/components/dialog/dialog.ts +6 -3
- package/src/components/dialog/features.ts +345 -151
- package/src/components/dialog/index.ts +38 -8
- package/src/components/dialog/types.ts +40 -14
- package/src/components/divider/config.ts +81 -0
- package/src/components/divider/divider.ts +37 -0
- package/src/components/divider/features.ts +207 -0
- package/src/components/divider/index.ts +9 -0
- package/src/components/divider/types.ts +55 -0
- package/src/components/extended-fab/api.ts +141 -0
- package/src/components/extended-fab/config.ts +112 -0
- package/src/components/extended-fab/extended-fab.ts +125 -0
- package/src/components/extended-fab/index.ts +9 -0
- package/src/components/extended-fab/types.ts +304 -0
- package/src/components/fab/api.ts +97 -0
- package/src/components/fab/config.ts +93 -0
- package/src/components/fab/fab.ts +67 -0
- package/src/components/fab/index.ts +9 -0
- package/src/components/fab/types.ts +251 -0
- package/src/components/list/config.ts +4 -5
- package/src/components/list/features.ts +6 -7
- package/src/components/list/index.ts +7 -9
- package/src/components/list/list-item.ts +12 -13
- package/src/components/list/types.ts +50 -5
- package/src/components/list/utils.ts +30 -3
- package/src/components/menu/features/items-manager.ts +9 -9
- package/src/components/menu/features/positioning.ts +7 -7
- package/src/components/menu/features/visibility.ts +7 -7
- package/src/components/menu/index.ts +7 -9
- package/src/components/menu/menu-item.ts +6 -6
- package/src/components/menu/menu.ts +22 -0
- package/src/components/menu/types.ts +29 -10
- package/src/components/menu/utils.ts +67 -0
- package/src/components/navigation/api.ts +78 -50
- package/src/components/navigation/config.ts +22 -10
- package/src/components/navigation/features/items.ts +284 -0
- package/src/components/navigation/index.ts +0 -6
- package/src/components/navigation/nav-item.ts +70 -33
- package/src/components/navigation/navigation.ts +53 -3
- package/src/components/navigation/types.ts +117 -70
- package/src/components/progress/api.ts +2 -3
- package/src/components/progress/config.ts +2 -3
- package/src/components/progress/index.ts +0 -1
- package/src/components/progress/progress.ts +1 -2
- package/src/components/progress/types.ts +186 -33
- package/src/components/radios/config.ts +1 -1
- package/src/components/radios/index.ts +0 -1
- package/src/components/radios/types.ts +0 -7
- package/src/components/search/api.ts +203 -0
- package/src/components/search/config.ts +86 -0
- package/src/components/search/features/index.ts +4 -0
- package/src/components/search/features/search.ts +717 -0
- package/src/components/search/features/states.ts +169 -0
- package/src/components/search/features/structure.ts +197 -0
- package/src/components/search/index.ts +7 -0
- package/src/components/search/search.ts +52 -0
- package/src/components/search/types.ts +175 -0
- package/src/components/segmented-button/config.ts +80 -0
- package/src/components/segmented-button/index.ts +4 -0
- package/src/components/segmented-button/segment.ts +154 -0
- package/src/components/segmented-button/segmented-button.ts +249 -0
- package/src/components/segmented-button/types.ts +254 -0
- package/src/components/slider/accessibility.md +5 -5
- package/src/components/slider/api.ts +41 -120
- package/src/components/slider/config.ts +51 -47
- package/src/components/slider/features/handlers.ts +495 -0
- package/src/components/slider/features/index.ts +1 -2
- package/src/components/slider/features/slider.ts +66 -84
- package/src/components/slider/features/states.ts +195 -0
- package/src/components/slider/features/structure.ts +136 -206
- package/src/components/slider/features/ui.ts +145 -206
- package/src/components/slider/index.ts +2 -11
- package/src/components/slider/slider.ts +9 -12
- package/src/components/slider/types.ts +67 -26
- package/src/components/snackbar/config.ts +2 -3
- package/src/components/snackbar/constants.ts +0 -32
- package/src/components/snackbar/index.ts +0 -1
- package/src/components/snackbar/position.ts +9 -1
- package/src/components/snackbar/types.ts +122 -46
- package/src/components/switch/config.ts +2 -3
- package/src/components/switch/index.ts +0 -1
- package/src/components/switch/types.ts +3 -2
- package/src/components/tabs/config.ts +3 -4
- package/src/components/tabs/features.ts +4 -2
- package/src/components/tabs/index.ts +0 -15
- package/src/components/tabs/indicator.ts +73 -13
- package/src/components/tabs/tab-api.ts +12 -4
- package/src/components/tabs/tab.ts +18 -6
- package/src/components/tabs/types.ts +23 -5
- package/src/components/textfield/config.ts +2 -3
- package/src/components/textfield/index.ts +0 -1
- package/src/components/textfield/types.ts +17 -3
- package/src/components/timepicker/README.md +277 -0
- package/src/components/timepicker/api.ts +632 -0
- package/src/components/timepicker/clockdial.ts +482 -0
- package/src/components/timepicker/config.ts +228 -0
- package/src/components/timepicker/index.ts +3 -0
- package/src/components/timepicker/render.ts +613 -0
- package/src/components/timepicker/timepicker.ts +117 -0
- package/src/components/timepicker/types.ts +336 -0
- package/src/components/timepicker/utils.ts +241 -0
- package/src/components/tooltip/api.ts +1 -1
- package/src/components/tooltip/config.ts +27 -6
- package/src/components/tooltip/index.ts +0 -1
- package/src/components/tooltip/types.ts +13 -3
- package/src/components/top-app-bar/config.ts +83 -0
- package/src/components/top-app-bar/index.ts +11 -0
- package/src/components/top-app-bar/top-app-bar.ts +316 -0
- package/src/components/top-app-bar/types.ts +140 -0
- package/src/core/build/_ripple.scss +6 -6
- package/src/core/build/ripple.ts +72 -95
- package/src/core/compose/features/icon.ts +3 -1
- package/src/core/compose/features/ripple.ts +4 -1
- package/src/core/compose/features/textlabel.ts +23 -2
- package/src/core/dom/create.ts +5 -0
- package/src/index.ts +9 -0
- package/src/styles/abstract/_theme.scss +9 -1
- package/src/styles/components/_badge.scss +182 -0
- package/src/styles/components/_bottom-app-bar.scss +103 -0
- package/src/{components/button/_styles.scss → styles/components/_button.scss} +0 -10
- package/src/{components/checkbox/_styles.scss → styles/components/_checkbox.scss} +0 -2
- package/src/styles/components/_datepicker.scss +358 -0
- package/src/styles/components/_dialog.scss +259 -0
- package/src/styles/components/_divider.scss +57 -0
- package/src/styles/components/_extended-fab.scss +267 -0
- package/src/styles/components/_fab.scss +225 -0
- package/src/{components/navigation/_styles.scss → styles/components/_navigation.scss} +1 -0
- package/src/styles/components/_search.scss +306 -0
- package/src/styles/components/_segmented-button.scss +117 -0
- package/src/{components/slider/_styles.scss → styles/components/_slider.scss} +83 -24
- package/src/{components/switch/_styles.scss → styles/components/_switch.scss} +0 -2
- package/src/{components/tabs/_styles.scss → styles/components/_tabs.scss} +95 -33
- package/src/{components/textfield/_styles.scss → styles/components/_textfield.scss} +70 -67
- package/src/styles/components/_timepicker.scss +451 -0
- package/src/styles/components/_top-app-bar.scss +225 -0
- package/src/styles/main.scss +98 -49
- package/src/styles/themes/_autumn.scss +21 -0
- package/src/styles/themes/_base-theme.scss +61 -0
- package/src/styles/themes/_baseline.scss +58 -0
- package/src/styles/themes/_bluekhaki.scss +125 -0
- package/src/styles/themes/_brownbeige.scss +125 -0
- package/src/styles/themes/_browngreen.scss +125 -0
- package/src/styles/themes/_forest.scss +6 -0
- package/src/styles/themes/_greenbeige.scss +125 -0
- package/src/styles/themes/_material.scss +125 -0
- package/src/styles/themes/_ocean.scss +6 -0
- package/src/styles/themes/_sageivory.scss +125 -0
- package/src/styles/themes/_spring.scss +6 -0
- package/src/styles/themes/_summer.scss +5 -0
- package/src/styles/themes/_sunset.scss +5 -0
- package/src/styles/themes/_tealcaramel.scss +125 -0
- package/src/styles/themes/_winter.scss +6 -0
- package/src/components/badge/_styles.scss +0 -174
- package/src/components/badge/constants.ts +0 -30
- package/src/components/button/constants.ts +0 -11
- package/src/components/card/constants.ts +0 -84
- package/src/components/dialog/_styles.scss +0 -213
- package/src/components/dialog/constants.ts +0 -32
- package/src/components/menu/constants.ts +0 -154
- package/src/components/navigation/constants.ts +0 -200
- package/src/components/navigation/features/items.js +0 -192
- package/src/components/progress/constants.ts +0 -29
- package/src/components/slider/features/appearance.ts +0 -94
- package/src/components/slider/features/disabled.ts +0 -68
- package/src/components/slider/features/events.ts +0 -164
- package/src/components/slider/features/interactions.ts +0 -396
- package/src/components/slider/features/keyboard.ts +0 -233
- package/src/components/switch/constants.ts +0 -80
- package/src/components/tabs/constants.ts +0 -89
- package/src/core/collection/adapters/mongodb.js +0 -232
- /package/src/{components/card/_styles.scss → styles/components/_card.scss} +0 -0
- /package/src/{components/carousel/_styles.scss → styles/components/_carousel.scss} +0 -0
- /package/src/{components/chip/_styles.scss → styles/components/_chip.scss} +0 -0
- /package/src/{components/list/_styles.scss → styles/components/_list.scss} +0 -0
- /package/src/{components/menu/_styles.scss → styles/components/_menu.scss} +0 -0
- /package/src/{components/progress/_styles.scss → styles/components/_progress.scss} +0 -0
- /package/src/{components/radios/_styles.scss → styles/components/_radios.scss} +0 -0
- /package/src/{components/sheet/_styles.scss → styles/components/_sheet.scss} +0 -0
- /package/src/{components/snackbar/_styles.scss → styles/components/_snackbar.scss} +0 -0
- /package/src/{components/tooltip/_styles.scss → styles/components/_tooltip.scss} +0 -0
- /package/src/styles/utilities/{_color.scss → _colors.scss} +0 -0
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
// src/components/slider/features/handlers.ts
|
|
2
|
+
import { SLIDER_EVENTS } from '../constants';
|
|
3
|
+
import { SliderConfig, SliderEvent } from '../types';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Create consolidated event handlers for slider component (mouse, touch, keyboard)
|
|
7
|
+
*
|
|
8
|
+
* @param config Slider configuration
|
|
9
|
+
* @param state Slider state object
|
|
10
|
+
* @param uiHelpers UI helper methods
|
|
11
|
+
* @param eventHelpers Event helper methods
|
|
12
|
+
* @returns Event handlers for all slider interactions
|
|
13
|
+
*/
|
|
14
|
+
export const createHandlers = (config: SliderConfig, state, uiHelpers, eventHelpers) => {
|
|
15
|
+
// Get required elements from structure (with fallbacks)
|
|
16
|
+
const {
|
|
17
|
+
container = null,
|
|
18
|
+
track = null,
|
|
19
|
+
handle = null,
|
|
20
|
+
valueBubble = null,
|
|
21
|
+
secondHandle = null,
|
|
22
|
+
secondValueBubble = null
|
|
23
|
+
} = state.component?.structure || {};
|
|
24
|
+
|
|
25
|
+
// Get required helper methods (with fallbacks)
|
|
26
|
+
const {
|
|
27
|
+
getValueFromPosition = () => 0,
|
|
28
|
+
roundToStep = value => value,
|
|
29
|
+
clamp = (value, min, max) => value,
|
|
30
|
+
showValueBubble = () => {},
|
|
31
|
+
updateUi = () => {}
|
|
32
|
+
} = uiHelpers;
|
|
33
|
+
|
|
34
|
+
const { triggerEvent = () => ({ defaultPrevented: false }) } = eventHelpers;
|
|
35
|
+
|
|
36
|
+
// Track whether a real drag has started
|
|
37
|
+
let hasActualDragStarted = false;
|
|
38
|
+
let initialX = 0;
|
|
39
|
+
const DRAG_THRESHOLD = 3;
|
|
40
|
+
|
|
41
|
+
// Last focused handle tracker for keyboard navigation
|
|
42
|
+
let lastFocusedHandle = null;
|
|
43
|
+
|
|
44
|
+
// Bubble management
|
|
45
|
+
const clearBubbleHideTimer = () => {
|
|
46
|
+
if (state.valueHideTimer) {
|
|
47
|
+
clearTimeout(state.valueHideTimer);
|
|
48
|
+
state.valueHideTimer = null;
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const hideAllBubbles = () => {
|
|
53
|
+
clearBubbleHideTimer();
|
|
54
|
+
if (valueBubble) showValueBubble(valueBubble, false);
|
|
55
|
+
if (secondValueBubble) showValueBubble(secondValueBubble, false);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const showActiveBubble = (bubble) => {
|
|
59
|
+
hideAllBubbles();
|
|
60
|
+
if (bubble && config.showValue) showValueBubble(bubble, true);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const hideActiveBubble = (bubble, delay = 0) => {
|
|
64
|
+
clearBubbleHideTimer();
|
|
65
|
+
if (!bubble || !config.showValue) return;
|
|
66
|
+
|
|
67
|
+
if (delay > 0) {
|
|
68
|
+
state.valueHideTimer = setTimeout(() => {
|
|
69
|
+
showValueBubble(bubble, false);
|
|
70
|
+
}, delay);
|
|
71
|
+
} else {
|
|
72
|
+
showValueBubble(bubble, false);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// Focus management
|
|
77
|
+
const clearKeyboardFocus = () => {
|
|
78
|
+
// Clear local focus indicators
|
|
79
|
+
if (handle) handle.classList.remove(`${state.component.getClass('slider-handle')}--focused`);
|
|
80
|
+
if (secondHandle) secondHandle.classList.remove(`${state.component.getClass('slider-handle')}--focused`);
|
|
81
|
+
|
|
82
|
+
// Clear global focus indicators
|
|
83
|
+
try {
|
|
84
|
+
const focusClass = state.component.getClass('slider-handle--focused');
|
|
85
|
+
document.querySelectorAll(`.${focusClass}`).forEach(el => {
|
|
86
|
+
el.classList.remove(focusClass);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
if (document.activeElement?.classList.contains(state.component.getClass('slider-handle'))) {
|
|
90
|
+
(document.activeElement as HTMLElement).blur();
|
|
91
|
+
}
|
|
92
|
+
} catch (error) {
|
|
93
|
+
console.warn('Error clearing keyboard focus:', error);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Handle mouse/touch down on a handle
|
|
99
|
+
*/
|
|
100
|
+
const handleHandleMouseDown = (e, isSecondHandle = false) => {
|
|
101
|
+
// Check if disabled
|
|
102
|
+
if (!state.component || (state.component.disabled && state.component.disabled.isDisabled())) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
e.preventDefault();
|
|
107
|
+
e.stopPropagation();
|
|
108
|
+
|
|
109
|
+
hideAllBubbles();
|
|
110
|
+
clearKeyboardFocus();
|
|
111
|
+
|
|
112
|
+
// Capture initial position
|
|
113
|
+
initialX = e.type.includes('touch') ? e.touches[0].clientX : e.clientX;
|
|
114
|
+
|
|
115
|
+
// Setup drag state
|
|
116
|
+
state.dragging = false;
|
|
117
|
+
hasActualDragStarted = false;
|
|
118
|
+
state.activeHandle = isSecondHandle ? secondHandle : handle;
|
|
119
|
+
state.activeBubble = isSecondHandle ? secondValueBubble : valueBubble;
|
|
120
|
+
|
|
121
|
+
// Show bubble immediately
|
|
122
|
+
showActiveBubble(state.activeBubble);
|
|
123
|
+
|
|
124
|
+
// Add global event listeners
|
|
125
|
+
document.addEventListener('mousemove', handleMouseMove);
|
|
126
|
+
document.addEventListener('mouseup', handleMouseUp);
|
|
127
|
+
document.addEventListener('touchmove', handleMouseMove, { passive: false });
|
|
128
|
+
document.addEventListener('touchend', handleMouseUp);
|
|
129
|
+
|
|
130
|
+
updateUi();
|
|
131
|
+
triggerEvent(SLIDER_EVENTS.START, e);
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Handle mouse/touch down on the track
|
|
136
|
+
*/
|
|
137
|
+
const handleTrackMouseDown = (e) => {
|
|
138
|
+
// Check if disabled
|
|
139
|
+
if (!state.component || (state.component.disabled && state.component.disabled.isDisabled()) || !track || !container) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
e.preventDefault();
|
|
144
|
+
hideAllBubbles();
|
|
145
|
+
clearKeyboardFocus();
|
|
146
|
+
|
|
147
|
+
// Determine which handle to move
|
|
148
|
+
let isSecondHandle = false;
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
const containerRect = container.getBoundingClientRect();
|
|
152
|
+
const position = e.type.includes('touch') ? e.touches[0].clientX : e.clientX;
|
|
153
|
+
|
|
154
|
+
// Calculate value at click position and apply constraints
|
|
155
|
+
let newValue = getValueFromPosition(position);
|
|
156
|
+
if (config.snapToSteps && state.step > 0) newValue = roundToStep(newValue);
|
|
157
|
+
newValue = clamp(newValue, state.min, state.max);
|
|
158
|
+
|
|
159
|
+
if (config.range && state.secondValue !== null) {
|
|
160
|
+
// For range slider, move the closest handle
|
|
161
|
+
const distToFirst = Math.abs(newValue - state.value);
|
|
162
|
+
const distToSecond = Math.abs(newValue - state.secondValue);
|
|
163
|
+
|
|
164
|
+
isSecondHandle = distToSecond < distToFirst;
|
|
165
|
+
isSecondHandle ? (state.secondValue = newValue) : (state.value = newValue);
|
|
166
|
+
} else {
|
|
167
|
+
// Single handle slider - just update value
|
|
168
|
+
state.value = newValue;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Update UI and trigger events
|
|
172
|
+
updateUi();
|
|
173
|
+
triggerEvent(SLIDER_EVENTS.INPUT, e);
|
|
174
|
+
triggerEvent(SLIDER_EVENTS.CHANGE, e);
|
|
175
|
+
} catch (error) {
|
|
176
|
+
console.warn('Error handling track click:', error);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Set active elements
|
|
180
|
+
state.activeHandle = isSecondHandle ? secondHandle : handle;
|
|
181
|
+
state.activeBubble = isSecondHandle ? secondValueBubble : valueBubble;
|
|
182
|
+
|
|
183
|
+
// Store the initial position and start dragging
|
|
184
|
+
initialX = e.type.includes('touch') ? e.touches[0].clientX : e.clientX;
|
|
185
|
+
handleHandleMouseDown(e, isSecondHandle);
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Handle mouse/touch move during drag
|
|
190
|
+
*/
|
|
191
|
+
const handleMouseMove = (e) => {
|
|
192
|
+
if (!state.activeHandle || !container) return;
|
|
193
|
+
e.preventDefault();
|
|
194
|
+
|
|
195
|
+
try {
|
|
196
|
+
// Get current position
|
|
197
|
+
const currentX = e.type.includes('touch') ? e.touches[0].clientX : e.clientX;
|
|
198
|
+
|
|
199
|
+
// Determine if we've started a real drag
|
|
200
|
+
if (!hasActualDragStarted && Math.abs(currentX - initialX) >= DRAG_THRESHOLD) {
|
|
201
|
+
hasActualDragStarted = true;
|
|
202
|
+
state.dragging = true;
|
|
203
|
+
state.component.element.classList.add(`${state.component.getClass('slider')}--dragging`);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Calculate new value and apply constraints
|
|
207
|
+
let newValue = getValueFromPosition(currentX);
|
|
208
|
+
if (config.snapToSteps && state.step > 0) newValue = roundToStep(newValue);
|
|
209
|
+
newValue = clamp(newValue, state.min, state.max);
|
|
210
|
+
|
|
211
|
+
// Handle crossing points and handle swapping for range sliders
|
|
212
|
+
const isSecondHandle = state.activeHandle === secondHandle;
|
|
213
|
+
|
|
214
|
+
if (config.range && state.secondValue !== null) {
|
|
215
|
+
if (isSecondHandle) {
|
|
216
|
+
// Second handle is active
|
|
217
|
+
if (newValue >= state.value) {
|
|
218
|
+
state.secondValue = newValue;
|
|
219
|
+
} else {
|
|
220
|
+
// Handles are crossed, swap them
|
|
221
|
+
hideActiveBubble(state.activeBubble, 0);
|
|
222
|
+
state.secondValue = state.value;
|
|
223
|
+
state.value = newValue;
|
|
224
|
+
state.activeHandle = handle;
|
|
225
|
+
state.activeBubble = valueBubble;
|
|
226
|
+
showActiveBubble(state.activeBubble);
|
|
227
|
+
}
|
|
228
|
+
} else {
|
|
229
|
+
// First handle is active
|
|
230
|
+
if (newValue <= state.secondValue) {
|
|
231
|
+
state.value = newValue;
|
|
232
|
+
} else {
|
|
233
|
+
// Handles are crossed, swap them
|
|
234
|
+
hideActiveBubble(state.activeBubble, 0);
|
|
235
|
+
state.value = state.secondValue;
|
|
236
|
+
state.secondValue = newValue;
|
|
237
|
+
state.activeHandle = secondHandle;
|
|
238
|
+
state.activeBubble = secondValueBubble;
|
|
239
|
+
showActiveBubble(state.activeBubble);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
} else {
|
|
243
|
+
// Regular slider
|
|
244
|
+
state.value = newValue;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
updateUi();
|
|
248
|
+
triggerEvent(SLIDER_EVENTS.INPUT, e);
|
|
249
|
+
} catch (error) {
|
|
250
|
+
console.warn('Error during slider drag:', error);
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Handle mouse/touch up after drag
|
|
256
|
+
*/
|
|
257
|
+
const handleMouseUp = (e) => {
|
|
258
|
+
if (!state.activeHandle) return;
|
|
259
|
+
e.preventDefault();
|
|
260
|
+
|
|
261
|
+
// Reset drag states
|
|
262
|
+
state.dragging = false;
|
|
263
|
+
hasActualDragStarted = false;
|
|
264
|
+
state.component.element.classList.remove(`${state.component.getClass('slider')}--dragging`);
|
|
265
|
+
|
|
266
|
+
// Hide bubble with delay
|
|
267
|
+
hideActiveBubble(state.activeBubble, 1000);
|
|
268
|
+
|
|
269
|
+
// Remove global event listeners
|
|
270
|
+
document.removeEventListener('mousemove', handleMouseMove);
|
|
271
|
+
document.removeEventListener('mouseup', handleMouseUp);
|
|
272
|
+
document.removeEventListener('touchmove', handleMouseMove);
|
|
273
|
+
document.removeEventListener('touchend', handleMouseUp);
|
|
274
|
+
|
|
275
|
+
// Reset active handle and update UI
|
|
276
|
+
state.activeHandle = null;
|
|
277
|
+
updateUi();
|
|
278
|
+
|
|
279
|
+
// Trigger events
|
|
280
|
+
triggerEvent(SLIDER_EVENTS.CHANGE, e);
|
|
281
|
+
triggerEvent(SLIDER_EVENTS.END, e);
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Handle keyboard input
|
|
286
|
+
*/
|
|
287
|
+
const handleKeyDown = (e, isSecondHandle = false) => {
|
|
288
|
+
if (state.component.disabled && state.component.disabled.isDisabled()) return;
|
|
289
|
+
|
|
290
|
+
const step = state.step || 1;
|
|
291
|
+
let newValue = isSecondHandle ? state.secondValue : state.value;
|
|
292
|
+
let stepSize = e.shiftKey ? step * 10 : step;
|
|
293
|
+
|
|
294
|
+
// Handle tab key separately
|
|
295
|
+
if (e.key === 'Tab') return;
|
|
296
|
+
|
|
297
|
+
let valueChanged = false;
|
|
298
|
+
|
|
299
|
+
switch (e.key) {
|
|
300
|
+
case 'ArrowRight':
|
|
301
|
+
case 'ArrowUp':
|
|
302
|
+
e.preventDefault();
|
|
303
|
+
newValue = Math.min(newValue + stepSize, state.max);
|
|
304
|
+
valueChanged = true;
|
|
305
|
+
break;
|
|
306
|
+
|
|
307
|
+
case 'ArrowLeft':
|
|
308
|
+
case 'ArrowDown':
|
|
309
|
+
e.preventDefault();
|
|
310
|
+
newValue = Math.max(newValue - stepSize, state.min);
|
|
311
|
+
valueChanged = true;
|
|
312
|
+
break;
|
|
313
|
+
|
|
314
|
+
case 'Home':
|
|
315
|
+
e.preventDefault();
|
|
316
|
+
newValue = state.min;
|
|
317
|
+
valueChanged = true;
|
|
318
|
+
break;
|
|
319
|
+
|
|
320
|
+
case 'End':
|
|
321
|
+
e.preventDefault();
|
|
322
|
+
newValue = state.max;
|
|
323
|
+
valueChanged = true;
|
|
324
|
+
break;
|
|
325
|
+
|
|
326
|
+
case 'PageUp':
|
|
327
|
+
e.preventDefault();
|
|
328
|
+
newValue = Math.min(newValue + (step * 10), state.max);
|
|
329
|
+
valueChanged = true;
|
|
330
|
+
break;
|
|
331
|
+
|
|
332
|
+
case 'PageDown':
|
|
333
|
+
e.preventDefault();
|
|
334
|
+
newValue = Math.max(newValue - (step * 10), state.min);
|
|
335
|
+
valueChanged = true;
|
|
336
|
+
break;
|
|
337
|
+
|
|
338
|
+
default:
|
|
339
|
+
return; // Exit if not a handled key
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (!valueChanged) return;
|
|
343
|
+
|
|
344
|
+
// Update active bubble reference
|
|
345
|
+
state.activeBubble = isSecondHandle ? secondValueBubble : valueBubble;
|
|
346
|
+
|
|
347
|
+
// Show value bubble during keyboard interaction
|
|
348
|
+
showActiveBubble(state.activeBubble);
|
|
349
|
+
|
|
350
|
+
// Update the value
|
|
351
|
+
if (isSecondHandle) {
|
|
352
|
+
state.secondValue = newValue;
|
|
353
|
+
} else {
|
|
354
|
+
state.value = newValue;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Update UI and trigger events
|
|
358
|
+
updateUi();
|
|
359
|
+
triggerEvent(SLIDER_EVENTS.INPUT, e);
|
|
360
|
+
triggerEvent(SLIDER_EVENTS.CHANGE, e);
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Handle focus events
|
|
365
|
+
*/
|
|
366
|
+
const handleFocus = (e, isSecondHandle = false) => {
|
|
367
|
+
if (state.component.disabled && state.component.disabled.isDisabled()) return;
|
|
368
|
+
|
|
369
|
+
// Track the currently focused handle
|
|
370
|
+
const currentHandle = isSecondHandle ? secondHandle : handle;
|
|
371
|
+
|
|
372
|
+
// Hide previous bubble if switching between handles
|
|
373
|
+
if (lastFocusedHandle && lastFocusedHandle !== currentHandle) {
|
|
374
|
+
hideAllBubbles();
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
lastFocusedHandle = currentHandle;
|
|
378
|
+
|
|
379
|
+
// Add focus class and show bubble
|
|
380
|
+
currentHandle.classList.add(`${state.component.getClass('slider-handle')}--focused`);
|
|
381
|
+
showActiveBubble(isSecondHandle ? secondValueBubble : valueBubble);
|
|
382
|
+
state.activeBubble = isSecondHandle ? secondValueBubble : valueBubble;
|
|
383
|
+
|
|
384
|
+
triggerEvent(SLIDER_EVENTS.FOCUS, e);
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Handle blur events
|
|
389
|
+
*/
|
|
390
|
+
const handleBlur = (e, isSecondHandle = false) => {
|
|
391
|
+
const handleElement = isSecondHandle ? secondHandle : handle;
|
|
392
|
+
handleElement.classList.remove(`${state.component.getClass('slider-handle')}--focused`);
|
|
393
|
+
|
|
394
|
+
// Only hide bubble if not tabbing to another handle
|
|
395
|
+
const relatedTarget = e.relatedTarget;
|
|
396
|
+
const otherHandle = isSecondHandle ? handle : secondHandle;
|
|
397
|
+
|
|
398
|
+
if (!relatedTarget || relatedTarget !== otherHandle) {
|
|
399
|
+
hideActiveBubble(isSecondHandle ? secondValueBubble : valueBubble, 200);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
triggerEvent(SLIDER_EVENTS.BLUR, e);
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Set up all event listeners
|
|
407
|
+
*/
|
|
408
|
+
const setupEventListeners = () => {
|
|
409
|
+
if (!state.component || !state.component.structure) {
|
|
410
|
+
console.warn('Cannot set up event listeners: missing component structure');
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// Use container for track events instead of track for better UX
|
|
415
|
+
container.addEventListener('mousedown', handleTrackMouseDown);
|
|
416
|
+
container.addEventListener('touchstart', handleTrackMouseDown, { passive: false });
|
|
417
|
+
|
|
418
|
+
// Handle events
|
|
419
|
+
handle.addEventListener('mousedown', e => handleHandleMouseDown(e, false));
|
|
420
|
+
handle.addEventListener('touchstart', e => handleHandleMouseDown(e, false), { passive: false });
|
|
421
|
+
handle.addEventListener('keydown', e => handleKeyDown(e, false));
|
|
422
|
+
handle.addEventListener('focus', e => handleFocus(e, false));
|
|
423
|
+
handle.addEventListener('blur', e => handleBlur(e, false));
|
|
424
|
+
|
|
425
|
+
// Second handle events for range slider
|
|
426
|
+
if (config.range && secondHandle) {
|
|
427
|
+
secondHandle.addEventListener('mousedown', e => handleHandleMouseDown(e, true));
|
|
428
|
+
secondHandle.addEventListener('touchstart', e => handleHandleMouseDown(e, true), { passive: false });
|
|
429
|
+
secondHandle.addEventListener('keydown', e => handleKeyDown(e, true));
|
|
430
|
+
secondHandle.addEventListener('focus', e => handleFocus(e, true));
|
|
431
|
+
secondHandle.addEventListener('blur', e => handleBlur(e, true));
|
|
432
|
+
}
|
|
433
|
+
};
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Clean up all event listeners
|
|
437
|
+
*/
|
|
438
|
+
const cleanupEventListeners = () => {
|
|
439
|
+
if (!state.component || !state.component.structure) return;
|
|
440
|
+
|
|
441
|
+
// Clean up container listeners
|
|
442
|
+
if (container) {
|
|
443
|
+
container.removeEventListener('mousedown', handleTrackMouseDown);
|
|
444
|
+
container.removeEventListener('touchstart', handleTrackMouseDown);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// Clean up handle listeners
|
|
448
|
+
if (handle) {
|
|
449
|
+
handle.removeEventListener('mousedown', e => handleHandleMouseDown(e, false));
|
|
450
|
+
handle.removeEventListener('touchstart', e => handleHandleMouseDown(e, false));
|
|
451
|
+
handle.removeEventListener('keydown', e => handleKeyDown(e, false));
|
|
452
|
+
handle.removeEventListener('focus', e => handleFocus(e, false));
|
|
453
|
+
handle.removeEventListener('blur', e => handleBlur(e, false));
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// Clean up second handle listeners
|
|
457
|
+
if (config.range && secondHandle) {
|
|
458
|
+
secondHandle.removeEventListener('mousedown', e => handleHandleMouseDown(e, true));
|
|
459
|
+
secondHandle.removeEventListener('touchstart', e => handleHandleMouseDown(e, true));
|
|
460
|
+
secondHandle.removeEventListener('keydown', e => handleKeyDown(e, true));
|
|
461
|
+
secondHandle.removeEventListener('focus', e => handleFocus(e, true));
|
|
462
|
+
secondHandle.removeEventListener('blur', e => handleBlur(e, true));
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// Clean up document listeners
|
|
466
|
+
document.removeEventListener('mousemove', handleMouseMove);
|
|
467
|
+
document.removeEventListener('mouseup', handleMouseUp);
|
|
468
|
+
document.removeEventListener('touchmove', handleMouseMove);
|
|
469
|
+
document.removeEventListener('touchend', handleMouseUp);
|
|
470
|
+
};
|
|
471
|
+
|
|
472
|
+
// Return consolidated handlers
|
|
473
|
+
return {
|
|
474
|
+
// Mouse/Touch handlers
|
|
475
|
+
handleHandleMouseDown,
|
|
476
|
+
handleTrackMouseDown,
|
|
477
|
+
handleMouseMove,
|
|
478
|
+
handleMouseUp,
|
|
479
|
+
|
|
480
|
+
// Keyboard handlers
|
|
481
|
+
handleKeyDown,
|
|
482
|
+
handleFocus,
|
|
483
|
+
handleBlur,
|
|
484
|
+
|
|
485
|
+
// Event management
|
|
486
|
+
setupEventListeners,
|
|
487
|
+
cleanupEventListeners,
|
|
488
|
+
|
|
489
|
+
// Bubble management
|
|
490
|
+
showActiveBubble,
|
|
491
|
+
hideActiveBubble,
|
|
492
|
+
hideAllBubbles,
|
|
493
|
+
clearKeyboardFocus
|
|
494
|
+
};
|
|
495
|
+
};
|