UNPKG

@snippetify/book-reader-component

Version:
195 lines (194 loc) 7.59 kB
import { Component, Host, h, Prop, Event } from '@stencil/core'; import defaultConfig from '../../config/config'; import { BookReaderEvent, ContextMenuAction, EventName } from '../../events/events'; import { SelectionManager } from '../../managers/selection-manager'; export class ContextMenu { constructor() { this.config = defaultConfig.contextMenu; } componentDidRender() { this.setMenuPosition(); } publishMenuItemClicked(_, item) { this.menuItemClicked.emit(new BookReaderEvent(EventName.CONTEXT_MENU, ContextMenuAction[item.event.toUpperCase()], SelectionManager.getInstance().create())); } slideMenu(target, direction) { let slideWidth = 0; const slide = target.closest('.menu-wrapper').querySelector('ul'); const position = Math.abs(parseFloat(slide.style.left)); const increment = target.closest('.menu-wrapper').querySelector('.menu-inner').offsetWidth; let leftGap = direction === 'right' ? (position + increment) : (position - increment); slide.querySelectorAll('li').forEach(v => (slideWidth += v.offsetWidth)); target.closest('.menu-wrapper').querySelector('[data-type="arrow-left"]').removeAttribute('disabled'); target.closest('.menu-wrapper').querySelector('[data-type="arrow-right"]').removeAttribute('disabled'); if ((leftGap + increment) > slideWidth) { leftGap = slideWidth - increment; target.closest('.menu-wrapper').querySelector('[data-type="arrow-right"]').setAttribute('disabled', 'true'); } if (leftGap < 0) { leftGap = 0; target.closest('.menu-wrapper').querySelector('[data-type="arrow-left"]').setAttribute('disabled', 'true'); } let left = position; let timeout; let animate = () => { if (direction === 'right') { left += 10; left = left > leftGap ? leftGap : left; } else { left -= 10; left = left < leftGap ? leftGap : left; } slide.style.left = `${-1 * left}px`; timeout = setTimeout(animate, 10); if (left == leftGap) { clearTimeout(timeout); return; } }; animate(); } setMenuPosition() { if (!this.anchor) return; const screenWidth = window.innerWidth; const anchorwidth = this.anchor.offsetWidth; const anchorHeight = this.anchor.offsetHeight; const anchorTop = this.anchor.offsetTop; const anchorCenter = this.anchor.offsetLeft + (anchorwidth / 2); let innerMenuWidth = 0; // Si le menu est plus large que l'ecran, activer le slider mode this.menu.querySelectorAll('li').forEach(elem => { innerMenuWidth += elem.offsetWidth; }); if ((innerMenuWidth + 120) > screenWidth) { this.menu.querySelector('[data-type="arrow-left"]').classList.remove('hide'); this.menu.querySelector('[data-type="arrow-right"]').classList.remove('hide'); this.menu.querySelector('.menu-inner').style.maxWidth = `${screenWidth - 120}px`; } else { this.menu.querySelector('[data-type="arrow-left"]').classList.add('hide'); this.menu.querySelector('[data-type="arrow-right"]').classList.add('hide'); this.menu.querySelector('.menu-inner').style.maxWidth = `${innerMenuWidth}px`; } // Always reset slider this.menu.querySelector('.menu-inner').querySelector('ul').style.left = '0'; this.menu.querySelector('[data-type="arrow-left"]').setAttribute('disabled', 'true'); const gap = 2; // decalage const menuWidth = this.menu.offsetWidth; const menuHeight = this.menu.offsetHeight; let menuTop = anchorTop + anchorHeight - 8; let menuLeft = anchorCenter - (menuWidth / 2); const menuTopToWindow = menuTop - (window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop); // Selectionner le caret à afficher if ((menuTopToWindow + menuHeight + 30) > window.innerHeight) { menuTop = anchorTop - menuHeight + 10; this.menu.querySelector('[data-type="arrow-down"]').classList.add('active'); this.menu.querySelector('[data-type="arrow-up"]').classList.remove('active'); } else { this.menu.querySelector('[data-type="arrow-up"]').classList.add('active'); this.menu.querySelector('[data-type="arrow-down"]').classList.remove('active'); } menuLeft = menuLeft < 0 ? gap : menuLeft; menuLeft = (menuLeft + menuWidth) > screenWidth ? (screenWidth - menuWidth - gap) : menuLeft; // Afficher le menu, positionner le caret ainsi que le menu this.menu.querySelectorAll('.arrow-wrapper span').forEach((v) => v.style.marginLeft = `${anchorCenter - menuLeft - 7}px`); this.menu.style.top = `${menuTop}px`; this.menu.style.left = `${menuLeft}px`; } render() { return (h(Host, { class: "context-menu", "data-type": "context-menu", ref: el => this.menu = el }, h("div", { class: "arrow-wrapper active", "data-type": "arrow-up" }, h("span", null, h("i", { class: "fas fa-caret-up" }))), h("div", { class: "menu-wrapper" }, h("button", { class: "hide", "data-type": "arrow-left", onClick: e => this.slideMenu(e.target, 'left') }, h("i", { class: "fas fa-caret-left" })), h("div", { class: "menu-inner" }, h("ul", null, this.config.items.map((item) => h("li", null, h("button", { onClick: e => this.publishMenuItemClicked(e, item) }, item.name))))), h("button", { class: "hide", "data-type": "arrow-right", onClick: e => this.slideMenu(e.target, 'right') }, h("i", { class: "fas fa-caret-right" }))), h("div", { class: "arrow-wrapper down", "data-type": "arrow-down" }, h("span", null, h("i", { class: "fas fa-caret-down" }))))); } static get is() { return "context-menu"; } static get encapsulation() { return "scoped"; } static get originalStyleUrls() { return { "$": ["context-menu.css"] }; } static get styleUrls() { return { "$": ["context-menu.css"] }; } static get properties() { return { "anchor": { "type": "unknown", "mutable": false, "complexType": { "original": "HTMLElement", "resolved": "HTMLElement", "references": { "HTMLElement": { "location": "global" } } }, "required": false, "optional": false, "docs": { "tags": [], "text": "" } }, "config": { "type": "any", "mutable": false, "complexType": { "original": "any", "resolved": "any", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "" }, "attribute": "config", "reflect": false, "defaultValue": "defaultConfig.contextMenu" } }; } static get events() { return [{ "method": "menuItemClicked", "name": "menuItemClicked", "bubbles": true, "cancelable": true, "composed": true, "docs": { "tags": [], "text": "" }, "complexType": { "original": "BookReaderEvent<ContextMenuAction, Selection>", "resolved": "BookReaderEvent<ContextMenuAction, Selection>", "references": { "BookReaderEvent": { "location": "import", "path": "../../events/events" }, "ContextMenuAction": { "location": "import", "path": "../../events/events" }, "Selection": { "location": "import", "path": "../../models/selection" } } } }]; } }