gem-panel
Version:
A custom element <gem-panel>, let you easily create layout similar to Adobe After Effects.
197 lines • 7.22 kB
JavaScript
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, privateMap) {
if (!privateMap.has(receiver)) {
throw new TypeError("attempted to get private field on non-instance");
}
return privateMap.get(receiver);
};
var _enterMenu;
import { html, GemElement, customElement, connectStore, createStore, updateStore, styleMap } from '@mantou/gem';
import { MENU_Z_INDEX } from '../lib/const';
import { theme } from '../lib/theme';
export const menuStore = createStore({
activeElement: null,
open: false,
menuStack: [],
});
export function openContextMenu(activeElement, x, y, menu) {
updateStore(menuStore, { open: true, activeElement, menuStack: [{ x, y, menu }] });
}
export function pointerDownHandle() {
setTimeout(() => { var _a; return (_a = menuStore.activeElement) === null || _a === void 0 ? void 0 : _a.focus(); });
updateStore(menuStore, { open: false });
}
function addMenuStack(x, y, menu) {
const index = menuStore.menuStack.findIndex((e) => e.menu === menu);
if (index > -1) {
updateStore(menuStore, { menuStack: menuStore.menuStack.slice(0, index + 1) });
}
else {
updateStore(menuStore, { menuStack: [...menuStore.menuStack, { x, y, menu }] });
}
}
let GemPanelMenuElement = class GemPanelMenuElement extends GemElement {
constructor() {
super(...arguments);
_enterMenu.set(this, (evt, menu) => {
const { x, y, width } = evt.target.getBoundingClientRect();
const em = parseInt(getComputedStyle(this).fontSize);
addMenuStack(x + 2 * width < innerWidth ? x + width - em : x - width + em, y - 0.4 * em, menu);
});
this.stopPropagation = (evt) => {
evt.stopPropagation();
};
this.mounted = () => {
this.addEventListener('pointerdown', pointerDownHandle);
};
this.render = () => {
if (!menuStore.open) {
return html `<style>
:host {
display: none;
}
</style>`;
}
const { menuStack } = menuStore;
return html `
<style>
:host {
position: fixed;
z-index: ${MENU_Z_INDEX};
display: block;
top: 0;
left: 0;
width: 100%;
height: 100%;
text-transform: capitalize;
font-size: 0.85em;
}
[part~='menu'] {
position: absolute;
gap: 1px;
background: ${theme.backgroundColor};
color: ${theme.secondaryColor};
border: 1px solid ${theme.borderColor};
box-shadow: 0 0.3em 1em rgba(0, 0, 0, 0.4);
border-radius: 4px;
overflow: auto;
}
[part~='menu-item'] {
line-height: 1.5;
padding: 0.4em 1em;
display: flex;
gap: 1em;
}
[part~='menu-item'] span {
flex-grow: 1;
flex-shrink: 1;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
[part~='menu-disabled-item'] {
opacity: 0.5;
}
[part~='menu-item']:not([part~='menu-disabled-item']):hover,
.open {
color: ${theme.primaryColor};
}
[part~='menu-selected-item-mark'],
[part~='menu-submenu-mark'] {
flex-shrink: 0;
position: relative;
width: 1em;
}
[part~='menu-submenu-mark']::before,
[part~='menu-submenu-mark']::after {
position: absolute;
content: '';
width: 80%;
height: 2px;
border-radius: 1em;
top: 50%;
background: currentColor;
transform-origin: center right;
}
[part~='menu-submenu-mark']::before {
transform: translateY(-50%) scale(0.8) rotate(-45deg);
}
[part~='menu-submenu-mark']::after {
transform: translateY(-50%) scale(0.8) rotate(45deg);
}
[part~='menu-selected-item-mark']::before,
[part~='menu-selected-item-mark']::after {
position: absolute;
content: '';
height: 2px;
border-radius: 1em;
background: currentColor;
bottom: 0;
left: 25%;
}
[part~='menu-selected-item-mark']::before {
width: 60%;
transform: translate(-70%, -200%) scale(0.8) rotate(45deg);
transform-origin: bottom right;
}
[part~='menu-selected-item-mark']::after {
width: 100%;
transform: translate(20%, -200%) scale(0.8) rotate(-45deg);
transform-origin: bottom left;
}
[part~='menu-item-separator'] {
opacity: 0.3;
background: currentColor;
height: 1px;
margin: 0 1em;
}
</style>
${menuStack.map(({ x, y, menu }, index) => html `
<div
part="menu"
style=${styleMap({
width: '200px',
maxHeight: `calc(100vh - ${y}px)`,
top: `${y + 4}px`,
left: `min(${x}px, calc(100vw - 200px))`,
})}
>
${menu.map(({ text, handle, disabled, selected, menu: subMenu }) => {
var _a;
return text === '---'
? html `<div part="menu-item-separator"></div>`
: html `
<div
part=${`
menu-item
${disabled ? 'menu-disabled-item' : ''}
${selected ? 'menu-selected-item' : ''}
`}
class=${subMenu && subMenu === ((_a = menuStack[index + 1]) === null || _a === void 0 ? void 0 : _a.menu) ? 'open' : ''}
@pointerenter=${(evt) => __classPrivateFieldGet(this, _enterMenu).call(this, evt, subMenu || menu)}
@pointerdown=${disabled || !handle ? this.stopPropagation : handle}
>
<span>${text}</span>
${subMenu ? html `<div part="menu-submenu-mark"></div>` : ''}
${selected ? html `<div part="menu-selected-item-mark"></div>` : ''}
</div>
`;
})}
</div>
`)}
`;
};
}
};
_enterMenu = new WeakMap();
GemPanelMenuElement = __decorate([
customElement('gem-panel-menu'),
connectStore(menuStore)
], GemPanelMenuElement);
export { GemPanelMenuElement };
//# sourceMappingURL=menu.js.map