Commit 7cbc5166 authored by Juliusz Chroboczek's avatar Juliusz Chroboczek
parent 96839094
MIT License
Copyright (c) 2019 Lucas Reade
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
/* Main context menu outer */
.contextualMenu{
font-size: 13px;
position: absolute;
padding: 8px 0;
background: var(--contextualMenuBg);
box-shadow: var(--contextualMenuShadow);
border-radius: var(--contextualMenuRadius);
margin:0;
list-style: none;
color: var(--contextualMenuText);
}
/* Menu seperator item */
.contextualMenuSeperator{
display: block;
position: relative;
padding: 5px 5px;
}
.contextualMenuSeperator span{
display: block;
width:100%;
height:1px;
background: var(--contextualSeperator);
}
/* Default menu item */
.contextualMenuItemOuter {
position: relative;
}
.contextualMenuItem{
display: block;
padding: 5px 8px;
cursor: default;
}
.contextualMenuItem:hover{
background: var(--contextualHover);
}
.contextualMenuItemIcon{
float: left;
width:16px;
height: 16px;
}
.contextualMenuItemTitle{
text-align: left;
line-height: 16px;
display: inline-block;
padding: 0px 0px 0px 7px;
}
.contextualMenuItemTip{
float: right;
padding: 0px 0px 0px 50px;
text-align: right;
line-height: 16px;
}
.contextualMenuItemOverflow{
float: right;
width:16px;
height: 16px;
padding: 1px 0px 0px 7px;
}
.contextualMenuItemOverflow .contextualMenuItemOverflowLine{
display: block;
height: 1px;
margin: 3px 2px;
background: var(--contextualOverflowIcon);
}
.contextualMenuItemOverflow.hidden{
display: none;
}
.contextualMenuItem.disabled{
opacity: 0.4;
}
.contextualMenuItem.disabled:hover{
background: none;
}
/* Submenu item */
.contextualSubMenu{
padding: 0;
margin: 0;
background: var(--contextualSubMenuBg);
border-radius: var(--contextualMenuRadius);
width: 100%;
height: auto;
max-height: 1000px;
transition: max-height 0.5s;
overflow: hidden;
}
.contextualSubMenu .contextualMenuItem:hover{
background: var(--contextualHover);
}
.contextualMenuHidden{
max-height: 0;
}
/* Multi item button */
.contextualMultiItem{
display: flex;
position: relative;
}
.contextualMultiItem .contextualMenuItemOuter{
flex: auto;
display: inline-block;
}
/* Hover menu */
.contextualHoverMenuOuter{
position: relative;
}
.contextualHoverMenuItem{
display: block;
padding: 5px 8px;
cursor: default;
}
.contextualHoverMenuItem.disabled{
opacity: 0.4;
}
.contextualHoverMenuItem.disabled:hover{
background: none;
}
.contextualHoverMenuItem:hover{
background: var(--contextualHover);
}
.contextualHoverMenuOuter > .contextualHoverMenu{
display: none;
}
.contextualHoverMenuOuter:hover > .contextualHoverMenu{
display: block;
position: absolute;
left: 100%;
top: 0;
background: var(--contextualMenuBg);
box-shadow: var(--contextualMenuShadow);
border-radius: var(--contextualMenuRadius);
padding: 8px 0;
width: 100%;
z-index: 1000;
list-style: none;
}
class Contextual{
/**
* Creates a new contextual menu
* @param {object} opts options which build the menu e.g. position and items
* @param {number} opts.width sets the width of the menu including children
* @param {boolean} opts.isSticky sets how the menu apears, follow the mouse or sticky
* @param {Array<ContextualItem>} opts.items sets the default items in the menu
*/
constructor(opts){
contextualCore.CloseMenu();
this.position = opts.isSticky != null ? opts.isSticky : false;
this.menuControl = contextualCore.CreateEl(`<ul class='contextualJs contextualMenu'></ul>`);
this.menuControl.style.width = opts.width != null ? opts.width : '200px';
opts.items.forEach(i => {
let item = new ContextualItem(i);
this.menuControl.appendChild(item.element);
});
if(event != undefined){
event.stopPropagation()
document.body.appendChild(this.menuControl);
contextualCore.PositionMenu(this.position, event, this.menuControl);
}
document.onclick = function(e){
if(!e.target.classList.contains('contextualJs')){
contextualCore.CloseMenu();
}
}
}
/**
* Adds item to this contextual menu instance
* @param {ContextualItem} item item to add to the contextual menu
*/
add(item){
this.menuControl.appendChild(item.element);
}
/**
* Makes this contextual menu visible
*/
show(){
event.stopPropagation()
document.body.appendChild(this.menuControl);
contextualCore.PositionMenu(this.position, event, this.menuControl);
}
/**
* Hides this contextual menu
*/
hide(){
event.stopPropagation()
contextualCore.CloseMenu();
}
/**
* Toggle visibility of menu
*/
toggle(){
event.stopPropagation()
if(this.menuControl.parentElement != document.body){
document.body.appendChild(this.menuControl);
contextualCore.PositionMenu(this.position, event, this.menuControl);
}else{
contextualCore.CloseMenu();
}
}
}
class ContextualItem{
element;
/**
*
* @param {Object} opts
* @param {string} [opts.label]
* @param {string} [opts.type]
* @param {string} [opts.markup]
* @param {string} [opts.icon]
* @param {string} [opts.cssIcon]
* @param {string} [opts.shortcut]
* @param {void} [opts.onClick]
* @param {boolean} [opts.enabled]
* @param {Array<ContextualItem>} [opts.items]
*
*/
constructor(opts){
switch(opts.type){
case 'seperator':
this.seperator();
break;
case 'custom':
this.custom(opts.markup);
break;
case 'multi':
this.multiButton(opts.items);
break;
case 'submenu':
this.subMenu(opts.label, opts.items, (opts.icon !== undefined ? opts.icon : ''), (opts.cssIcon !== undefined ? opts.cssIcon : ''), (opts.enabled !== undefined ? opts.enabled : true));
break;
case 'hovermenu':
this.hoverMenu(opts.label, opts.items, (opts.icon !== undefined ? opts.icon : ''), (opts.cssIcon !== undefined ? opts.cssIcon : ''), (opts.enabled !== undefined ? opts.enabled : true));
break;
case 'normal':
default:
this.button(opts.label, opts.onClick, (opts.shortcut !== undefined ? opts.shortcut : ''), (opts.icon !== undefined ? opts.icon : ''), (opts.cssIcon !== undefined ? opts.cssIcon : ''), (opts.enabled !== undefined ? opts.enabled : true));
}
}
button(label, onClick, shortcut = '', icon = '', cssIcon = '', enabled = true){
this.element = contextualCore.CreateEl( `
<li class='contextualJs contextualMenuItemOuter'>
<div class='contextualJs contextualMenuItem ${enabled == true ? '' : 'disabled'}'>
${icon != ''? `<img src='${icon}' class='contextualJs contextualMenuItemIcon'/>` : `<div class='contextualJs contextualMenuItemIcon ${cssIcon != '' ? cssIcon : ''}'></div>`}
<span class='contextualJs contextualMenuItemTitle'>${label == undefined? 'No label' : label}</span>
<span class='contextualJs contextualMenuItemTip'>${shortcut == ''? '' : shortcut}</span>
</div>
</li>`);
if(enabled == true){
this.element.addEventListener('click', () => {
event.stopPropagation();
if(onClick !== undefined){ onClick(); }
contextualCore.CloseMenu();
}, false);
}
}
custom(markup){
this.element = contextualCore.CreateEl(`<li class='contextualJs contextualCustomEl'>${markup}</li>`);
}
hoverMenu(label, items, icon = '', cssIcon = '', enabled = true){
this.element = contextualCore.CreateEl(`
<li class='contextualJs contextualHoverMenuOuter'>
<div class='contextualJs contextualHoverMenuItem ${enabled == true ? '' : 'disabled'}'>
${icon != ''? `<img src='${icon}' class='contextualJs contextualMenuItemIcon'/>` : `<div class='contextualJs contextualMenuItemIcon ${cssIcon != '' ? cssIcon : ''}'></div>`}
<span class='contextualJs contextualMenuItemTitle'>${label == undefined? 'No label' : label}</span>
<span class='contextualJs contextualMenuItemOverflow'>></span>
</div>
<ul class='contextualJs contextualHoverMenu'>
</ul>
</li>
`);
let childMenu = this.element.querySelector('.contextualHoverMenu'),
menuItem = this.element.querySelector('.contextualHoverMenuItem');
if(items !== undefined) {
items.forEach(i => {
let item = new ContextualItem(i);
childMenu.appendChild(item.element);
});
}
if(enabled == true){
menuItem.addEventListener('mouseenter', () => {
});
menuItem.addEventListener('mouseleave', () => {
});
}
}
multiButton(buttons) {
this.element = contextualCore.CreateEl(`
<li class='contextualJs contextualMultiItem'>
</li>
`);
buttons.forEach(i => {
let item = new ContextualItem(i);
this.element.appendChild(item.element);
});
}
subMenu(label, items, icon = '', cssIcon = '', enabled = true){
this.element = contextualCore.CreateEl( `
<li class='contextualJs contextualMenuItemOuter'>
<div class='contextualJs contextualMenuItem ${enabled == true ? '' : 'disabled'}'>
${icon != ''? `<img src='${icon}' class='contextualJs contextualMenuItemIcon'/>` : `<div class='contextualJs contextualMenuItemIcon ${cssIcon != '' ? cssIcon : ''}'></div>`}
<span class='contextualJs contextualMenuItemTitle'>${label == undefined? 'No label' : label}</span>
<span class='contextualJs contextualMenuItemOverflow'>
<span class='contextualJs contextualMenuItemOverflowLine'></span>
<span class='contextualJs contextualMenuItemOverflowLine'></span>
<span class='contextualJs contextualMenuItemOverflowLine'></span>
</span>
</div>
<ul class='contextualJs contextualSubMenu contextualMenuHidden'>
</ul>
</li>`);
let childMenu = this.element.querySelector('.contextualSubMenu'),
menuItem = this.element.querySelector('.contextualMenuItem');
if(items !== undefined) {
items.forEach(i => {
let item = new ContextualItem(i);
childMenu.appendChild(item.element);
});
}
if(enabled == true){
menuItem.addEventListener('click',() => {
menuItem.classList.toggle('SubMenuActive');
childMenu.classList.toggle('contextualMenuHidden');
}, false);
}
}
seperator(label, items) {
this.element = contextualCore.CreateEl(`<li class='contextualJs contextualMenuSeperator'><span></span></li>`);
}
}
const contextualCore = {
PositionMenu: (docked, el, menu) => {
if(docked){
menu.style.left = ((el.target.offsetLeft + menu.offsetWidth) >= window.innerWidth) ?
((el.target.offsetLeft - menu.offsetWidth) + el.target.offsetWidth)+"px"
: (el.target.offsetLeft)+"px";
menu.style.top = ((el.target.offsetTop + menu.offsetHeight) >= window.innerHeight) ?
(el.target.offsetTop - menu.offsetHeight)+"px"
: (el.target.offsetHeight + el.target.offsetTop)+"px";
}else{
menu.style.left = ((el.clientX + menu.offsetWidth) >= window.innerWidth) ?
((el.clientX - menu.offsetWidth))+"px"
: (el.clientX)+"px";
menu.style.top = ((el.clientY + menu.offsetHeight) >= window.innerHeight) ?
(el.clientY - menu.offsetHeight)+"px"
: (el.clientY)+"px";
}
},
CloseMenu: () => {
let openMenuItem = document.querySelector('.contextualMenu:not(.contextualMenuHidden)');
if(openMenuItem != null){ document.body.removeChild(openMenuItem); }
},
CreateEl: (template) => {
var el = document.createElement('div');
el.innerHTML = template;
return el.firstElementChild;
}
};
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment