<!-- // Copyright © 2020 Anticrm Platform Contributors. // // Licensed under the Eclipse Public License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. You may // obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // // See the License for the specific language governing permissions and // limitations under the License. --> <script lang="ts"> import { afterUpdate, onDestroy } from 'svelte/internal' export let margin: number = 12 export let show: boolean let trigger: HTMLElement let popup: HTMLElement let scrolling: boolean let elScroll: Node afterUpdate(() => { if (show) showPopup() else hidePopup() }) const showPopup = (): void => { fitPopup() popup.style.visibility = 'visible' elScroll = findNode(trigger, 'scrollBox') if (elScroll) elScroll.addEventListener('scroll', startScroll) } const hidePopup = (): void => { if (popup) { popup.style.visibility = 'hidden' popup.style.maxHeight = '' } if (elScroll) elScroll.removeEventListener('scroll', startScroll) } const fitPopup = (): void => { const rectT = trigger.getBoundingClientRect() const rectP = popup.getBoundingClientRect() scrolling = false if (rectT.top > document.body.clientHeight - rectT.bottom) { // Up if (rectT.top - 10 - margin < rectP.height) { scrolling = true popup.style.maxHeight = `${rectT.top - margin - 10}px` popup.style.top = '10px' } else popup.style.top = `${rectT.top - rectP.height - margin}px` } else { // Down if (rectT.bottom + rectP.height + 10 + margin > document.body.clientHeight) { scrolling = true popup.style.maxHeight = `${document.body.clientHeight - rectT.bottom - margin - 10}px` } popup.style.top = `${rectT.bottom + margin}px` } if (rectT.left + rectP.width + 10 > document.body.clientWidth) { popup.style.left = `${document.body.clientWidth - rectP.width - 10}px` } else popup.style.left = `${rectT.left}px` } const findNode = (el: HTMLElement, name: string): any => { while (el.parentNode !== null) { if (el.classList.contains(name)) return el el = el.parentNode as HTMLElement } return false } const waitClick = (event: any): void => { event.stopPropagation() if (show) { if (!findNode(event.target, 'popup-menu')) show = false } } const startScroll = (): void => { show = false } onDestroy(() => { if (elScroll) elScroll.removeEventListener('scroll', startScroll) }) </script> <svelte:window on:mouseup={waitClick} on:resize={startScroll} /> <div class="popup-menu"> <div bind:this={trigger}> <slot name="trigger" /> </div> <div class="popup" bind:this={popup}> {#if show} <div class="flex-col" class:scrolling><slot /></div> {/if} </div> </div> <style lang="scss"> .popup { position: fixed; visibility: hidden; display: flex; flex-direction: column; padding: 1rem; color: var(--caption-color); background-color: var(--popup-bg-color); border-radius: 0.75rem; box-shadow: var(--popup-shadow); user-select: none; z-index: 10; } .scrolling { overflow-y: auto; } </style>