Skip to content

Instantly share code, notes, and snippets.

@dimfeld
Last active September 30, 2020 02:31
Show Gist options
  • Save dimfeld/daa98d61dbb378197098287a3d8ba186 to your computer and use it in GitHub Desktop.
Save dimfeld/daa98d61dbb378197098287a3d8ba186 to your computer and use it in GitHub Desktop.
Very simplified version of nested dropdown tracking.
<!-- This is missing a bunch of the code that interacts with tippy to position the element, but
hopefully it makes sense -->
<script>
// The popup tracker makes sure that "outside click" handling doesn't trigger when a click occurs
// in a child dropdown, and the child dropdown's popup is fixed.
function makePopupTracker(parent) {
let contained: HTMLElement[] = [];
return {
contains(target) {
return contained.some((e) => e.contains(target));
},
register(element: HTMLElement) {
if (parent) {
parent.register(element);
}
contained.push(element);
},
unregister(element: HTMLElement) {
if (parent) {
parent.unregister(element);
}
contained = contained.filter((e) => e !== element);
},
};
}
// Get the popup tracker from the parent context, if any,
// and create a new popup tracker that inherits it.
let parentTracker = getContext('parent-dropdown-tracker');
let popupTracker = makePopupTracker(parentTracker);
setContext('parent-dropdown-tracker', popupTracker);
// Svelte action to add and remove the popup Element in the popup tracker.
function addToPopupTracker(node: HTMLElement) {
let elementExists = true;
if (closeOnClickOutside) {
setTimeout(() => {
if (elementExists) {
window.addEventListener('click', handleDocumentClick, {
capture: true,
});
}
});
}
popupTracker.register(node);
return {
destroy() {
elementExists = false;
if (closeOnClickOutside) {
window.removeEventListener('click', handleDocumentClick, {
capture: true,
});
}
popupTracker.unregister(node);
},
};
}
let open = false;
function handleDocumentClick(e: MouseEvent) {
if (
!popupTracker.contains(e.target as HTMLElement) &&
(e.target as Node).getRootNode() === document
) {
open = false;
// Don't let anything else handle this.
e.stopImmediatePropagation();
}
}
// Code to set this up omitted for brevity.
$: if (tippyInstance) {
if (open) {
tippyInstance.show();
} else {
tippyInstance.hide();
}
}
</script>
<slot name="button"><button type="button" on:click={() => (open = !open)}>{label}</button></slot>
{#if open}
<div use:addToPopupTracker>
<slot />
</div>
{/if}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment