import { CssProps, RefProps, VNode, callUnload, mountInnerComponent } from 'lupine.web'; import { stopPropagation, backActionHelper } from '../lib'; export type FloatWindowCloseProps = () => void; export type FloatWindowShowProps = { title: string; children: VNode; buttons?: string[]; contentMaxHeight?: string; contentMinWidth?: string; noMoving?: boolean; noModal?: boolean; closeEvent?: () => void; handleClicked: (index: number, close: FloatWindowCloseProps) => void; closeWhenClickOutside?: boolean; // default false zIndex?: string; contentOverflowY?: string; }; // because it's over a mask, so it can use primary colors export class FloatWindow { static hostNode: HTMLElement; private static initialized = false; private static pressed = false; private static startX = 0; private static startY = 0; private static startTop = 0; private static startLeft = 0; static init() { window.addEventListener('mousemove', FloatWindow.onMousemove.bind(FloatWindow), false); document.documentElement.addEventListener('mouseup', FloatWindow.onMouseup.bind(FloatWindow), false); } static async show({ title, children, contentMaxHeight, contentMinWidth, buttons, noMoving = false, noModal = false, closeEvent, handleClicked, closeWhenClickOutside = false, zIndex, contentOverflowY = 'auto', // set to unset for having popup menu inside }: FloatWindowShowProps): Promise { const onClickContainer = (event: any) => { if (closeWhenClickOutside !== false && event.target.classList.contains('fwin-box')) { handleClose(); } }; const handleClose = () => { closeEvent?.(); ref.current.classList.add('transition'); ref.current.classList.remove('animation'); setTimeout(async () => { await callUnload(base); base.remove(); }, 300); }; const base = document.createElement('div'); const onMousedown = (event: any) => { if (noMoving) return; if (!this.initialized) { this.initialized = true; this.init(); } FloatWindow.hostNode = ref.current; FloatWindow.onMousedown.bind(FloatWindow)(event); }; const newButtons = !buttons || buttons.length === 0 ? ['OK', 'Cancel'] : buttons; const onClickButtons = (index: number) => { handleClicked(index, handleClose); }; const ref: RefProps = { onLoad: async () => { ref.current.classList.add('transition'); setTimeout(() => { ref.current.classList.add('animation'); }, 1); setTimeout(() => { // don't need transition for moving ref.current.classList.remove('transition'); }, 300); }, }; const cssContainer: CssProps = { position: noModal ? '' : 'fixed', top: 0, left: 0, width: '100%', height: '100%', backgroundColor: noModal ? '' : 'var(--cover-mask-bg-color)', '.fwin-body': { position: 'fixed', top: '50%', left: '50%', transform: 'translate(-50%, -50%) scale(0.1)', color: 'var(--primary-color)', backgroundColor: 'var(--cover-bg-color)', //'#fefefe', border: 'var(--primary-border)', //'1px solid #888', borderRadius: '6px', minWidth: contentMinWidth ? contentMinWidth : '', maxWidth: '90%', boxShadow: 'var(--cover-box-shadow)', //'#0000004c 0px 19px 38px, #00000038 0px 15px 12px', opacity: 0, // zIndex: 'var(--layer-float-window)', '&.transition': { transition: 'all 0.3s', }, '&.animation': { transform: 'translate(-50%, -50%) scale(1)', opacity: 1, }, '&.animation-close': { transition: 'all 0.3s', transform: 'translate(-50%, -50%) scale(0)', opacity: 0, }, '.fwin-title': { padding: '10px 15px 5px', borderBottom: 'var(--primary-border)', //'1px solid #e9ecef', '.fwin-close': { color: '#aaaaaa', float: 'right', fontSize: '26px', fontWeight: 'bold', cursor: 'pointer', marginTop: '-3px', marginRight: '-10px', }, '.fwin-close:hover': { transition: 'all 300ms ease', color: '#555555', }, }, '.fwin-content': { padding: '15px', maxHeight: contentMaxHeight ? `min(${contentMaxHeight}, calc(100% - 100px))` : 'calc(100% - 100px)', overflowY: contentOverflowY, boxSizing: 'border-box', width: '100%', }, '.fwin-bottom': { display: 'flex', padding: '5px 15px', borderTop: 'var(--primary-border)', //'1px solid #e9ecef', justifyContent: 'end', '>div': { marginLeft: '5px', }, }, }, }; const component = (
{title} ×
{children}
{newButtons.map((i, index) => ( ))}
); base.style.position = 'fixed'; base.style.zIndex = zIndex || 'var(--layer-float-window)'; document.body.appendChild(base); await mountInnerComponent(base, component); return handleClose; } static onMousedown(event: any) { if (event.buttons !== 1 || event.button !== 0) return; if (event.srcElement.className !== 'fwin-title') return; this.pressed = true; this.startX = event.clientX; this.startY = event.clientY; const nodeStyle = document.defaultView!.getComputedStyle(this.hostNode); this.startTop = parseInt(nodeStyle['top'], 10); this.startLeft = parseInt(nodeStyle['left'], 10); } static onMousemove(event: any) { if (!this.pressed || event.buttons !== 1 || event.button !== 0) { return; } // prevent text/element selection when drag stopPropagation(event); if ( event.clientX < 0 || event.clientY < 0 || event.clientX > window.innerWidth || event.clientY > window.innerHeight ) { return; } let movedX = this.startLeft + (event.clientX - this.startX); let movedY = this.startTop + (event.clientY - this.startY); if (movedY <= 0) movedY = 0; if (movedX <= 0) movedX = 0; this.hostNode.style.top = movedY + 'px'; this.hostNode.style.left = movedX + 'px'; } static onMouseup() { if (this.pressed) this.pressed = false; } }