UNPKG

@ne1410s/menu

Version:
177 lines (170 loc) 10.1 kB
'use strict'; var dom = require('@ne1410s/dom'); var custElems = require('@ne1410s/cust-elems'); var markupUrl = "data:text/html;base64,PHNsb3Q+PC9zbG90Pg0KPHVsIGNsYXNzPSJ0b3AiPjwvdWw+DQo="; var stylesUrl = "data:text/css;base64,dWwsDQpsaSB7DQogIHBhZGRpbmc6IDA7DQogIG1hcmdpbjogMDsNCiAgZm9udDogaW5oZXJpdDsNCn0NCnNsb3QsDQpsaS5zcGxpdCAuaWNvbiB7DQogIGRpc3BsYXk6IG5vbmU7DQp9DQoudG9wOm5vdCgub3BlbiksDQp1bDpub3QoLnRvcCkgew0KICB2aXNpYmlsaXR5OiBoaWRkZW47DQp9DQoNCnVsIHsNCiAgYm9yZGVyOiB2YXIoLS1ib3JkZXIsIDFweCBzb2xpZCAjYmJiKTsNCiAgYmFja2dyb3VuZDogdmFyKC0tYmcsICNmZmYpOw0KICBib3gtc2hhZG93OiB2YXIoLS1ib3gtc2hhZG93LCAycHggMnB4IDNweCAjODg4KTsNCiAgdHJhbnNpdGlvbjogYmFja2dyb3VuZCAwLjZzOw0KICB6LWluZGV4OiA5OTk5OTk5Ow0KfQ0KDQoudG9wIHsNCiAgcG9zaXRpb246IGZpeGVkOw0KICBmb250LXNpemU6IHZhcigtLWZvbnQtc2l6ZSwgMC42NXJlbSk7DQogIHVzZXItc2VsZWN0OiBub25lOw0KfQ0KDQouaWNvbi5sZWZ0IHsNCiAgbGVmdDogMWVtOw0KICB0cmFuc2Zvcm06IHRyYW5zbGF0ZSgtNTAlLCAtNTAlKTsNCn0NCi5pY29uLnJpZ2h0IHsNCiAgcmlnaHQ6IDFlbTsNCiAgdHJhbnNmb3JtOiB0cmFuc2xhdGUoNTAlLCAtNTAlKTsNCn0NCi5pY29uIHsNCiAgbWF4LXdpZHRoOiAxZW07DQogIG1heC1oZWlnaHQ6IDFlbTsNCiAgcG9zaXRpb246IGFic29sdXRlOw0KICB0b3A6IDUwJTsNCiAgbGluZS1oZWlnaHQ6IDA7DQp9DQoNCmxpID4gcDpub3QoOmVtcHR5KSB7DQogIG1hcmdpbjogMCAxZW07DQogIHdoaXRlLXNwYWNlOiBub3dyYXA7DQp9DQpsaS5kaXNhYmxlZCB7DQogIGNvbG9yOiB2YXIoLS1kaXNhYmxlZC1mZywgI2JiYik7DQp9DQpsaS5zcGxpdCB7DQogIGJvcmRlci10b3A6IHZhcigtLXNwbGl0LWJvcmRlciwgdmFyKC0tYm9yZGVyLCAxcHggc29saWQgI2JiYikpOw0KICBwYWRkaW5nOiAwOw0KICBtYXJnaW46IDAuM2VtOw0KfQ0KbGkuc3BsaXQ6Zmlyc3QtY2hpbGQsDQpsaS5zcGxpdDpsYXN0LWNoaWxkLA0KbGkuc3BsaXQgKyBsaS5zcGxpdCB7DQogIGRpc3BsYXk6IG5vbmU7DQp9DQpsaS5ob3Zlcjpub3QoLmRpc2FiZWQpOm5vdCguc3BsaXQpIHsNCiAgYmFja2dyb3VuZDogdmFyKC0taG92ZXItaXRlbS1iZywgI2JiYik7DQp9DQoudG9wLm9wZW4gbGkuaG92ZXIgPiB1bCB7DQogIHZpc2liaWxpdHk6IHZpc2libGU7DQp9DQpsaSB7DQogIG1hcmdpbjogMC4yZW0gMDsNCiAgcGFkZGluZzogMC41ZW0gMWVtOw0KICBkaXNwbGF5OiBmbGV4Ow0KICBqdXN0aWZ5LWNvbnRlbnQ6IHNwYWNlLWJldHdlZW47DQogIHRleHQtYWxpZ246IGxlZnQ7DQogIHBvc2l0aW9uOiByZWxhdGl2ZTsNCiAgY3Vyc29yOiBkZWZhdWx0Ow0KICBjb2xvcjogdmFyKC0tZmcsICMwMDApOw0KfQ0KDQpsaS5ncm91cDo6YWZ0ZXIgew0KICBjb250ZW50OiAnXDI1YjYnOw0KICBwb3NpdGlvbjogYWJzb2x1dGU7DQogIGZvbnQtc2l6ZTogMC42NWVtOw0KICByaWdodDogMDsNCiAgdG9wOiA1MCU7DQogIHRyYW5zZm9ybTogdHJhbnNsYXRlKC01MCUsIC01MCUpOw0KfQ0KDQp1bDpub3QoLnRvcCkgew0KICBwb3NpdGlvbjogYWJzb2x1dGU7DQogIGxlZnQ6IDEwMCU7DQogIHRvcDogLTAuMjVlbTsNCn0NCg0KdWwubmVzdGxlIHsNCiAgbGVmdDogMC41ZW07DQogIHRvcDogMTAwJTsNCn0NCg=="; class NeMenu extends custElems.CustomElementBase { constructor() { super(NeMenu.Css, NeMenu.Html); this.top = this.root.querySelector('ul'); } connectedCallback() { if (!this._connected) { setTimeout(() => this.reload()); dom.q(this.parentNode).on('contextmenu', (e) => this.onParentContext(e)); dom.q(this, this.parentNode).on('contextmenu wheel', (e) => { e.preventDefault(); e.stopPropagation(); }); dom.q(this).on('mousedown', (e) => e.stopPropagation()); dom.q(window).on('mousedown resize wheel', () => this.close()); this._connected = true; } } /** Opens the menu. */ open() { // close all menus const doc = this.parentElement.getRootNode(); doc.querySelectorAll('ne14-menu').forEach((m) => m.close()); // style this one as open this.top.classList.add('open'); dom.q(this).fire('menuopen'); } /** Closes the menu. */ close() { if (this.top.classList.contains('open')) { this.top.classList.remove('open'); dom.q(this).fire('menuclose'); } } /** Reloads active contents based on client dom. */ reload() { dom.q(this.top) .empty() .append(...this.walk(this, false)); } onParentContext(event) { if (this.isConnected) { // update position (y) const y = event.clientY; const height = this.top.offsetHeight; const posY = y + height + 2 > window.innerHeight ? y - height : y; this.top.style.top = `${Math.max(0, posY)}px`; // update position (x) const x = event.clientX; const width = this.top.offsetWidth; const posX = x + width + 2 > window.innerWidth ? x - width : x; this.top.style.left = `${Math.max(0, posX)}px`; // open this.open(); } } walk(ul, parentDisabled, ref = '') { let levelItemNo = 0; return Array.from(ul.children) .filter((c) => c instanceof HTMLLIElement && !c.classList.contains('hidden') && (c.textContent || c.classList.contains('split'))) .reduce((acc, li) => { var _a, _b, _c; const children = Array.from(li.children).map((el) => el); const a = children.find((n) => n instanceof HTMLAnchorElement); const ul = children.find((n) => n instanceof HTMLUListElement); const isSplit = li.classList.contains('split'); const isGrouper = !isSplit && ul && ul.querySelector('li'); const isDisabled = !isSplit && (parentDisabled || li.classList.contains('disabled')); const aChildren = Array.from((a === null || a === void 0 ? void 0 : a.children) || []).map((el) => el); const imgs = children .concat(aChildren) .filter((n) => n instanceof HTMLImageElement) .map((n) => n); if (!isSplit) levelItemNo++; const classes = []; if (isSplit) classes.push('split'); else { if (isDisabled) classes.push('disabled'); if (isGrouper) classes.push('group'); if ((a === null || a === void 0 ? void 0 : a.target) === '_blank') classes.push('click-out'); else if (a) classes.push('click-in'); } const bestTextNode = [...children, li].find((c) => c.innerText); const bestText = isSplit ? null : ((_a = bestTextNode === null || bestTextNode === void 0 ? void 0 : bestTextNode.innerText) !== null && _a !== void 0 ? _a : `Item ${levelItemNo}`); const shortcut = isSplit || isGrouper ? null : li.getAttribute('aria-keyshortcuts'); const liRef = `${ref}${levelItemNo}`; const eventDetail = { ref: liRef, title: bestText, origin: a || li }; const handleClick = () => { if (!isDisabled && !isGrouper && !isSplit) { eventDetail.origin.click(); dom.q(this).fire('itemselect', eventDetail); this.close(); } }; const handleMouseEnter = (e) => { if (!isDisabled && !isSplit) { const domLi = e.target; if (isGrouper) { const domUl = Array.from(domLi.children).find((n) => n instanceof HTMLUListElement); const liRect = domLi.getBoundingClientRect(); domUl.classList.toggle('nestle', liRect.right + domUl.clientWidth + 2 > window.innerWidth); } domLi.classList.add('hover'); dom.q(this).fire('itemhover', eventDetail); } }; const handleMouseLeave = (e) => { if (!isDisabled && !isSplit) { e.target.classList.remove('hover'); dom.q(this).fire('itemunhover', eventDetail); } }; const $domItem = dom.q({ tag: 'li' }) .attr('class', classes.length ? classes.join(' ') : null) .attr('aria-keyshortcuts', shortcut) .on('click contextmenu', handleClick) .on('mouseenter', handleMouseEnter) .on('mouseleave', handleMouseLeave); const charLeft = li.dataset.charLeft; const charRight = li.dataset.charRight; const imgLeft = imgs.find((i) => !i.classList.contains('right')); const imgRight = imgs.find((i) => i.classList.contains('right')); if (!isSplit && !isGrouper && bestText) { if (NeMenu.CHAR_REF_REGEX.test(charLeft)) $domItem.append(`<span class='icon left'>&#x${charLeft};</span>`); else if (charLeft) console.warn(`ne14-menu: Bad hex code '${charLeft}' to left of '${bestText}'.`); else if (imgLeft) $domItem.append(`<img class='icon left' src='${imgLeft.src}'/>`); if (NeMenu.CHAR_REF_REGEX.test(charRight)) $domItem.append(`<span class='icon right'>&#x${charRight};</span>`); else if (charRight) console.warn(`ne14-menu: Bad hex code '${charRight}' to right of '${bestText}'.`); else if (imgRight) $domItem.append(`<img class='icon right' src='${imgRight.src}'/>`); } if (bestText) $domItem.append({ tag: 'p', text: bestText }); if (shortcut) $domItem.append({ tag: 'p', text: shortcut }); if (isGrouper) $domItem.appendIn({ tag: 'ul' }).append(...this.walk(ul, isDisabled, `${liRef}-`)); // Do not push two consecutive splits if (!isSplit || !((_c = (_b = acc[acc.length - 1]) === null || _b === void 0 ? void 0 : _b.classList) === null || _c === void 0 ? void 0 : _c.contains('split'))) { acc.push($domItem.elements[0]); } return acc; }, []); } } NeMenu.Css = custElems.reduceCss(custElems.decode(stylesUrl)); NeMenu.Html = custElems.reduceHtml(custElems.decode(markupUrl)); NeMenu.CHAR_REF_REGEX = /^[0-9a-f]{4,5}$/i; if ('customElements' in window) { window.customElements.define('ne14-menu', NeMenu); } exports.ContextMenu = NeMenu;