I have a problem in svelteKit
with a collision. The button opens the menu but when I try to close it, it doesn't. When I click outside the button, this triggers handleClickOutside
which calls clickOutside.js
, and then closes the menu.
I think that the problem is that element.contains
includes the button and for that, the menu didn't close.
however, I couldn't fix it.
I'm using tailwindUI
, tailwindCSS
, SvelteKit
.
index.svelte
<script>
// @ts-nocheck
import bostonLogo from '../../img/bostonLogo.png';
import { clickOutside } from '../../lib/clickOutside';
// Example Profile
let profile = {
name: 'Matias',
lastName: 'Barletta'
};
// Show/Hide Menu
let menu = false;
// COLLISION WITH HANDLENAV
function handleClickOutside(event) {
menu = false;
}
function handleNav() {
menu = !menu;
}
</script>
<div>
<!-- Static sidebar for desktop -->
<div class:hidden={!menu}>
<!-- Sidebar component, swap this element with another sidebar if you like -->
<div
use:clickOutside
on:click_outside={menu? handleClickOutside : ''}
class:absolute={menu}
class:mt-11={menu}
>
...
clickOutside.js
// @ts-nocheck
/** Dispatch event on click outside of element */
// @ts-ignore
export function clickOutside(element) {
// @ts-ignore
const handleClick = (event) => {
console.log(event.target, document.body)
// element exist?, element contain where i did click, preventDefault = false?
if (element && !element.contains(event.target) && !event.defaultPrevented) {
element.dispatchEvent(
// Dispatch and create new custom event.
new CustomEvent('click_outside', element)
);
}
};
// add eventlistener when you click on document
document.addEventListener('click', handleClick, true);
return {
destroy() {
document.removeEventListener('click', handleClick, true);
}
};
}
CodePudding user response:
I've had this exact same issue not too long ago. My solution was to just ignore the menu click when the menu is open. You already have this: on:click_outside={menu? handleClickOutside : ''}
.
So now if the menu is closed, the action won't fire. If the menu is open, the menu button won't fire. So there's always just 1 callback that's being called.
function handleNav() {
if (menu) return;
menu = !menu;
}
As an Alternative you could add options to the action so that the handleClick
function can ignore the click if the target is within a list of ignored selectors (rather than just the element it is on).
Update
The alternative approach here, with a working REPL.
What we do, is we added an ID to the menu button
<button
id="menu-button"
type="button"
value="button"
on:click={handleNav}
>...</button>
We then add that ID as a parameter to the use:clickOutside
action:
<div
use:clickOutside={{ignore: 'menu-button'}}
on:click_outside={menu? handleClickOutside : ''}
class:absolute={menu}
class:mt-11={menu}
>
<div >
Some menu content here
</div>
</div>
And then in the action, we get the element by ID and check if we're in it.
const handleClick = (event) => {
event.preventDefault();
// This function kinda rearranged with early returns to take the new parameter into account.
if (!element) return;
if (element.contains(event.target)) return;
//Get the element based on the id we gave in the params
const ignore = document.getElementById(opts.ignore)
//Check that we're not clicking in the button.
if (ignore.contains(event.target)) return;
element.dispatchEvent(
// Dispatch and create new custom event.
new CustomEvent('click_outside', element)
);
};