nakarte

Source code of https://map.sikmir.ru (fork)
git clone git://git.sikmir.ru/nakarte
Log | Files | Refs | LICENSE

index.js (4147B)


      1 import './contextmenu.css';
      2 
      3 /*
      4     items = [
      5         {text: 'Hello', disabled: true},
      6         '-',
      7         {text: 'World', callback: fn},
      8         {text: 'section', separator: true},
      9     ]
     10  */
     11 
     12 function isDescendant(parent, child) {
     13     if (!parent) {
     14         return false;
     15     }
     16     while (child) {
     17         if (child === parent) {
     18             return true;
     19         }
     20         child = child.parentNode;
     21     }
     22     return false;
     23 }
     24 
     25 class Contextmenu {
     26     constructor(items) {
     27         this.items = items;
     28     }
     29 
     30     show(e) {
     31         if (this._container) {
     32             return;
     33         }
     34         if (e.originalEvent) {
     35             e = e.originalEvent;
     36         }
     37         if (e.preventDefault) {
     38             e.preventDefault();
     39         } else {
     40             e.returnValue = false;
     41         }
     42 
     43         const {clientX: x, clientY: y} = e;
     44 
     45         const container = this._container = document.createElement('div');
     46         document.body.appendChild(container);
     47         container.className = 'contextmenu';
     48         container.style.zIndex = 10000;
     49 
     50         window.addEventListener('keydown', this.onKeyDown, true);
     51         window.addEventListener('mousedown', this.onMouseDown, true);
     52         window.addEventListener('touchstart', this.onMouseDown, true);
     53 
     54         for (let item of this.createItems()) {
     55             container.appendChild(item);
     56         }
     57         this.setPosition(x, y);
     58     }
     59 
     60     hide() {
     61         if (!this._container) {
     62             return;
     63         }
     64         document.body.removeChild(this._container);
     65         this._container = null;
     66 
     67         window.removeEventListener('keydown', this.onKeyDown, true);
     68         window.removeEventListener('mousedown', this.onMouseDown, true);
     69         window.removeEventListener('touchstart', this.onMouseDown, true);
     70     }
     71 
     72     onKeyDown = (e) => {
     73         if (e.keyCode === 27) {
     74             this.hide();
     75         }
     76     };
     77 
     78     onMouseDown = (e) => {
     79         if (!isDescendant(this._container, e.target)) {
     80             this.hide();
     81         }
     82     };
     83 
     84     setPosition(x, y) {
     85         const window_width = window.innerWidth,
     86             window_height = window.innerHeight,
     87             menu_width = this._container.offsetWidth,
     88             menu_height = this._container.offsetHeight;
     89         if (x + menu_width >= window_width) {
     90             x -= menu_width;
     91             if (x < 0) {
     92                x = 0;
     93             }
     94         }
     95         if (y + menu_height >= window_height) {
     96             y -= menu_height;
     97             if (y < 0) {
     98                 y = 0;
     99             }
    100         }
    101         this._container.style.left = `${x}px`;
    102         this._container.style.top = `${y}px`;
    103     }
    104 
    105     *createItems() {
    106         let items = this.items;
    107         if (typeof items === 'function') {
    108             items = items();
    109         }
    110         for (let itemOptions of items) {
    111             if (typeof itemOptions === 'function') {
    112                 itemOptions = itemOptions();
    113             }
    114             if (itemOptions === '-' || itemOptions.separator) {
    115                 yield this.createSeparator(itemOptions);
    116             } else {
    117                 yield this.createItem(itemOptions);
    118             }
    119         }
    120     }
    121 
    122     createItem(itemOptions) {
    123         const el = document.createElement('a');
    124         let className = 'item';
    125         if (itemOptions.disabled) {
    126             className += ' disabled';
    127         }
    128         if (itemOptions.header) {
    129             className += ' header';
    130         }
    131         el.innerHTML = itemOptions.text;
    132 
    133         const callback = itemOptions.callback;
    134         if (callback && !itemOptions.disabled) {
    135             className += ' action';
    136             el.addEventListener('click', this.onItemClick.bind(this, callback));
    137         }
    138         el.className = className;
    139         return el;
    140     }
    141 
    142     onItemClick(callback, e) {
    143         e.stopPropagation();
    144         e.preventDefault();
    145         this.hide();
    146         callback(e);
    147     }
    148 
    149     createSeparator(itemOptions) {
    150         const el = document.createElement('div');
    151         el.className = 'separator';
    152         if (itemOptions.text) {
    153             el.innerHTML = `<span>${itemOptions.text}</span>`;
    154         }
    155         return el;
    156     }
    157 }
    158 
    159 export default Contextmenu;