Skip to content

Commit 5706a94

Browse files
authored
Merge pull request #1167 from adamscott/fix-touch-issues-with-header
Fix header issues by relying on `isTouchDevice`
2 parents 8d7309c + d3b1d29 commit 5706a94

File tree

1 file changed

+69
-46
lines changed

1 file changed

+69
-46
lines changed

_includes/header.html

Lines changed: 69 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -230,9 +230,6 @@
230230
<script>
231231
// Open dropdown on hover (desktop) or tap (touch devices)
232232
document.addEventListener('DOMContentLoaded', function() {
233-
// Detect if device has touch capability
234-
const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
235-
236233
// Find all dropdown triggers
237234
const dropdownTriggers = document.querySelectorAll('[data-dropdown]');
238235

@@ -241,71 +238,97 @@
241238
const dropdownMenu = document.getElementById(dropdownId);
242239

243240
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+
}
245258

246-
function showDropdown() {
247259
// 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();
250264
const rect = trigger.getBoundingClientRect();
251265
dropdownMenu.style.top = (rect.bottom) + 'px';
252266
dropdownMenu.style.left = (rect.left) + 'px';
253267
dropdownMenu.style.display = 'block';
254268
trigger.classList.add('dropdown-open');
255-
}
269+
};
256270

257271
// 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();
260279
dropdownMenu.style.display = 'none';
261280
trigger.classList.remove('dropdown-open');
262-
}, 100);
281+
};
282+
283+
if (instant) {
284+
hideDropdownTrigger();
285+
return;
286+
}
287+
hideTimeout = setTimeout(hideDropdownTrigger, 100);
263288
}
264289

265290
// 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+
}
268295
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 })
273298
} else {
274299
showDropdown();
275300
}
276301
}
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+
}
289312
}
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()));
290330
}
291331
});
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-
}
309332
});
310333
</script>
311334

0 commit comments

Comments
 (0)