platform/packages/ui/src/components/PopupMenu.svelte
Alexander Platov afdf55e0e7
Convert PX to REM (#28)
Signed-off-by: Alexander Platov <sas_lord@mail.ru>
2021-08-17 16:46:06 +02:00

118 lines
3.5 KiB
Svelte

<!--
// 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: Node, name: string): any => {
while (el.parentNode !== null) {
if (el.classList.contains(name)) return el
el = el.parentNode
}
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(--theme-caption-color);
background-color: var(--theme-button-bg-hovered);
border: 1px solid var(--theme-button-border-enabled);
border-radius: .75rem;
box-shadow: 0px 1.25rem 3.75rem rgba(0, 0, 0, .6);
user-select: none;
z-index: 10;
}
.scrolling { overflow-y: auto; }
</style>