import {Component} from '@f-list/vue-ts'; import Vue from 'vue'; @Component export default abstract class ContextMenu extends Vue { abstract propName: string; showMenu = false; position = {left: 0, top: 0}; selectedItem: HTMLElement | undefined; touchTimer = 0; abstract itemSelected(element: HTMLElement): void; shouldShowMenu(_: HTMLElement): boolean { return true; } hideMenu(): void { this.showMenu = false; this.selectedItem = undefined; } bindOffclick(): void { document.body.addEventListener('click', () => this.hideMenu()); } private fixPosition(e: MouseEvent | Touch): void { const getMenuPosition = (input: number, direction: string): number => { const win = (window as unknown as any)[`inner${direction}`] as number; // (window)[`inner${direction}`]; const menu = (this.$refs['menu'])[`offset${direction}`]; let position = input; if(input + menu > win) position = win - menu - 5; return position; }; const left = getMenuPosition(e.clientX, 'Width'); const top = getMenuPosition(e.clientY, 'Height'); this.position = {left, top}; } innerClick(): void { this.itemSelected(this.selectedItem!); this.hideMenu(); } outerClick(event: MouseEvent | TouchEvent): void { // Provide an opt-out if(event.ctrlKey) return; if(event.type === 'touchend') window.clearTimeout(this.touchTimer); const targetingEvent = event instanceof TouchEvent ? event.touches[0] : event; const findTarget = (): HTMLElement | undefined => { let element = targetingEvent.target; while(element !== document.body) { if(typeof element.dataset[this.propName] !== 'undefined' || element.parentElement === null) break; element = element.parentElement; } return typeof element.dataset[this.propName] === 'undefined' ? undefined : element; }; const target = findTarget(); if(target === undefined) { this.hideMenu(); return; } switch(event.type) { case 'click': case 'contextmenu': this.openMenu(targetingEvent, target); break; case 'touchstart': this.touchTimer = window.setTimeout(() => this.openMenu(targetingEvent, target), 500); } event.preventDefault(); } private openMenu(event: MouseEvent | Touch, element: HTMLElement): void { if(!this.shouldShowMenu(element)) return; this.showMenu = true; this.selectedItem = element; this.$nextTick(() => { this.fixPosition(event); }); } get positionStyle(): object { return {left: `${this.position.left}px`, top: `${this.position.top}px;`}; } }