|
230 | 230 | <script> |
231 | 231 | // Open dropdown on hover (desktop) or tap (touch devices) |
232 | 232 | document.addEventListener('DOMContentLoaded', function() { |
233 | | - // Detect if device has touch capability |
234 | | - const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0; |
235 | | - |
236 | 233 | // Find all dropdown triggers |
237 | 234 | const dropdownTriggers = document.querySelectorAll('[data-dropdown]'); |
238 | 235 |
|
|
241 | 238 | const dropdownMenu = document.getElementById(dropdownId); |
242 | 239 |
|
243 | 240 | if (dropdownMenu) { |
244 | | - let hideTimeout; |
| 241 | + let hideTimeout = -1; |
| 242 | + const clearHideTimeout = () => { |
| 243 | + if (hideTimeout === -1) { |
| 244 | + return; |
| 245 | + } |
| 246 | + clearTimeout(hideTimeout); |
| 247 | + hideTimeout = -1; |
| 248 | + }; |
| 249 | + |
| 250 | + const isDropdownVisible = () => { |
| 251 | + return dropdownMenu.style.display === 'block'; |
| 252 | + }; |
| 253 | + |
| 254 | + const showDropdown = () => { |
| 255 | + if (isDropdownVisible()) { |
| 256 | + return; |
| 257 | + } |
245 | 258 |
|
246 | | - function showDropdown() { |
247 | 259 | // Don't show dropdown on small screens (width < 1200px) |
248 | | - if (window.innerWidth < 1200) return; |
249 | | - clearTimeout(hideTimeout); |
| 260 | + if (window.innerWidth < 1200) { |
| 261 | + return; |
| 262 | + } |
| 263 | + clearHideTimeout(); |
250 | 264 | const rect = trigger.getBoundingClientRect(); |
251 | 265 | dropdownMenu.style.top = (rect.bottom) + 'px'; |
252 | 266 | dropdownMenu.style.left = (rect.left) + 'px'; |
253 | 267 | dropdownMenu.style.display = 'block'; |
254 | 268 | trigger.classList.add('dropdown-open'); |
255 | | - } |
| 269 | + }; |
256 | 270 |
|
257 | 271 | // Hide dropdown after a delay |
258 | | - function hideDropdown() { |
259 | | - hideTimeout = setTimeout(() => { |
| 272 | + const hideDropdown = ({ instant = false } = {}) => { |
| 273 | + if (!isDropdownVisible()) { |
| 274 | + return; |
| 275 | + } |
| 276 | + |
| 277 | + const hideDropdownTrigger = () => { |
| 278 | + clearHideTimeout(); |
260 | 279 | dropdownMenu.style.display = 'none'; |
261 | 280 | trigger.classList.remove('dropdown-open'); |
262 | | - }, 100); |
| 281 | + }; |
| 282 | + |
| 283 | + if (instant) { |
| 284 | + hideDropdownTrigger(); |
| 285 | + return; |
| 286 | + } |
| 287 | + hideTimeout = setTimeout(hideDropdownTrigger, 100); |
263 | 288 | } |
264 | 289 |
|
265 | 290 | // Toggle dropdown on high-resolution tablets |
266 | | - function toggleDropdown(event) { |
267 | | - if (window.innerWidth < 1200) return; |
| 291 | + const toggleDropdown = (event) => { |
| 292 | + if (window.innerWidth < 1200) { |
| 293 | + return; |
| 294 | + } |
268 | 295 | event.preventDefault(); |
269 | | - const isVisible = dropdownMenu.style.display === 'block'; |
270 | | - if (isVisible) { |
271 | | - dropdownMenu.style.display = 'none'; |
272 | | - trigger.classList.remove('dropdown-open'); |
| 296 | + if (isDropdownVisible()) { |
| 297 | + hideDropdown({ instant: true }) |
273 | 298 | } else { |
274 | 299 | showDropdown(); |
275 | 300 | } |
276 | 301 | } |
277 | | - |
278 | | - if (isTouchDevice) { |
279 | | - // Touch device: use click/tap to toggle dropdown |
280 | | - trigger.addEventListener('click', toggleDropdown); |
281 | | - } else { |
282 | | - // Desktop: use hover |
283 | | - trigger.addEventListener('mouseenter', showDropdown); |
284 | | - trigger.addEventListener('mouseleave', hideDropdown); |
285 | | - |
286 | | - // Keep dropdown visible when hovering over it |
287 | | - dropdownMenu.addEventListener('mouseenter', () => clearTimeout(hideTimeout)); |
288 | | - dropdownMenu.addEventListener('mouseleave', hideDropdown); |
| 302 | + |
| 303 | + const onlyOnTouch = (callback) => (event) => { |
| 304 | + if (event.pointerType === "touch") { |
| 305 | + callback(event); |
| 306 | + } |
| 307 | + }; |
| 308 | + const notOnTouch = (callback) => (event) => { |
| 309 | + if (event.pointerType !== "touch") { |
| 310 | + callback(event); |
| 311 | + } |
289 | 312 | } |
| 313 | + |
| 314 | + // Touch device: use click/tap to toggle dropdown |
| 315 | + trigger.addEventListener('pointerup', onlyOnTouch((event) => toggleDropdown(event))); |
| 316 | + // Close dropdown when clicking outside (for touch devices) |
| 317 | + document.documentElement.addEventListener('pointerup', onlyOnTouch((event) => { |
| 318 | + if (!trigger.contains(event.target) && !dropdownMenu.contains(event.target)) { |
| 319 | + hideDropdown({ instant: true }); |
| 320 | + } |
| 321 | + })); |
| 322 | + |
| 323 | + // Desktop: use hover |
| 324 | + trigger.addEventListener('pointerenter', notOnTouch((_event) => showDropdown())); |
| 325 | + trigger.addEventListener('pointerleave', notOnTouch((_event) => hideDropdown())); |
| 326 | + |
| 327 | + // Keep dropdown visible when hovering over it |
| 328 | + dropdownMenu.addEventListener('pointerenter', notOnTouch((_event) => clearHideTimeout())); |
| 329 | + dropdownMenu.addEventListener('pointerleave', notOnTouch((_event) => hideDropdown())); |
290 | 330 | } |
291 | 331 | }); |
292 | | - |
293 | | - // Close dropdown when clicking outside (for touch devices) |
294 | | - if (isTouchDevice) { |
295 | | - document.addEventListener('click', function(event) { |
296 | | - dropdownTriggers.forEach(trigger => { |
297 | | - const dropdownId = trigger.getAttribute('data-dropdown'); |
298 | | - const dropdownMenu = document.getElementById(dropdownId); |
299 | | - |
300 | | - if (dropdownMenu && |
301 | | - !trigger.contains(event.target) && |
302 | | - !dropdownMenu.contains(event.target)) { |
303 | | - dropdownMenu.style.display = 'none'; |
304 | | - trigger.classList.remove('dropdown-open'); |
305 | | - } |
306 | | - }); |
307 | | - }); |
308 | | - } |
309 | 332 | }); |
310 | 333 | </script> |
311 | 334 |
|
|
0 commit comments