@snippetify/book-reader-component
Version:
Book Reader Component
195 lines (194 loc) • 7.59 kB
JavaScript
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"
}
}
}
}]; }
}