2024-06-15 00:22:31 +02:00
|
|
|
import {css, html, LitElement, nothing} from "lit";
|
|
|
|
import {SlMenu, SlMenuItem} from "@shoelace-style/shoelace";
|
|
|
|
import {egwMenuItem} from "./egw_menu";
|
|
|
|
import {customElement} from "lit/decorators/custom-element.js";
|
|
|
|
import {repeat} from "lit/directives/repeat.js";
|
|
|
|
import {classMap} from "lit/directives/class-map.js";
|
|
|
|
|
|
|
|
@customElement("egw-menu-shoelace")
|
|
|
|
export class EgwMenuShoelace extends LitElement
|
|
|
|
{
|
|
|
|
static get styles()
|
|
|
|
{
|
|
|
|
return [
|
|
|
|
css`
|
|
|
|
:host {
|
|
|
|
display: block;
|
|
|
|
}
|
|
|
|
|
|
|
|
.default-item::part(label) {
|
|
|
|
font-weight: var(--sl-font-weight-bold, bold);
|
|
|
|
}
|
|
|
|
|
2024-07-16 20:37:30 +02:00
|
|
|
.custom-color::part(label) {
|
|
|
|
--color: var(--color);
|
|
|
|
}
|
|
|
|
|
2024-07-11 19:27:31 +02:00
|
|
|
sl-menu-item::part(base) {
|
|
|
|
height: 1.6em;
|
|
|
|
line-height: var(--sl-line-height-dense);
|
|
|
|
align-items: center;
|
|
|
|
padding: 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
sl-menu-item::part(prefix) {
|
|
|
|
min-width: var(--sl-spacing-2x-large);
|
|
|
|
}
|
|
|
|
|
2024-06-15 00:22:31 +02:00
|
|
|
et2-image {
|
2024-07-11 19:27:31 +02:00
|
|
|
line-height: normal;
|
2024-07-16 20:37:30 +02:00
|
|
|
width: 1.3em;
|
2024-06-15 00:22:31 +02:00
|
|
|
}
|
|
|
|
`
|
|
|
|
]
|
|
|
|
}
|
|
|
|
|
|
|
|
private structure = [];
|
|
|
|
private popup = null;
|
|
|
|
private removeCallback = null;
|
|
|
|
|
|
|
|
private get menu() : SlMenu { return this.shadowRoot?.querySelector("sl-menu");}
|
|
|
|
|
|
|
|
constructor(_structure : egwMenuItem[])
|
|
|
|
{
|
|
|
|
super();
|
|
|
|
this.structure = _structure;
|
2024-07-09 23:35:46 +02:00
|
|
|
|
|
|
|
this.handleDocumentClick = this.handleDocumentClick.bind(this);
|
|
|
|
this.handleKeypress = this.handleKeypress.bind(this);
|
2024-06-15 00:22:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
connectedCallback()
|
|
|
|
{
|
|
|
|
super.connectedCallback();
|
2024-07-09 23:35:46 +02:00
|
|
|
|
|
|
|
document.addEventListener("click", this.handleDocumentClick);
|
|
|
|
document.addEventListener("keydown", this.handleKeypress);
|
2024-06-15 00:22:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
disconnectedCallback()
|
|
|
|
{
|
|
|
|
super.disconnectedCallback();
|
2024-07-09 23:35:46 +02:00
|
|
|
document.removeEventListener("click", this.handleDocumentClick);
|
|
|
|
document.removeEventListener("keydown", this.handleKeypress);
|
2024-06-15 00:22:31 +02:00
|
|
|
if(this.popup)
|
|
|
|
{
|
|
|
|
this.popup.remove();
|
|
|
|
this.popup = null;
|
|
|
|
}
|
|
|
|
if(this.removeCallback)
|
|
|
|
{
|
|
|
|
this.removeCallback.call();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public showAt(_x, _y, _onHide)
|
|
|
|
{
|
|
|
|
this.removeCallback = _onHide;
|
|
|
|
if(this.popup == null)
|
|
|
|
{
|
2024-07-11 19:27:31 +02:00
|
|
|
this.popup = Object.assign(document.createElement("sl-popup"), {
|
2024-07-16 20:37:30 +02:00
|
|
|
placement: "bottom-start",
|
2024-07-11 19:27:31 +02:00
|
|
|
autoSize: "vertical",
|
|
|
|
flip: true,
|
2024-07-16 20:37:30 +02:00
|
|
|
flipFallbackPlacements: "top-start",
|
2024-07-11 19:27:31 +02:00
|
|
|
flipFallbackStrategy: "initial",
|
|
|
|
shift: true
|
|
|
|
});
|
2024-06-15 00:22:31 +02:00
|
|
|
this.popup.append(this);
|
2024-07-11 19:27:31 +02:00
|
|
|
this.popup.classList.add("egw_menu");
|
2024-06-15 00:22:31 +02:00
|
|
|
}
|
2024-07-11 19:27:31 +02:00
|
|
|
let menu = this;
|
2024-06-15 00:22:31 +02:00
|
|
|
this.popup.anchor = {
|
|
|
|
getBoundingClientRect()
|
|
|
|
{
|
|
|
|
return {
|
|
|
|
x: _x,
|
|
|
|
y: _y,
|
2024-07-16 20:37:30 +02:00
|
|
|
width: 0, //menu.clientWidth,
|
|
|
|
height: 0, //menu.clientHeight,
|
2024-06-15 00:22:31 +02:00
|
|
|
top: _y,
|
|
|
|
left: _x,
|
|
|
|
right: _x,
|
|
|
|
bottom: _y
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
this.popup.active = true;
|
2024-07-11 19:27:31 +02:00
|
|
|
document.body.append(this.popup);
|
2024-06-15 00:22:31 +02:00
|
|
|
Promise.all([this.updateComplete, this.popup.updateComplete]).then(() =>
|
|
|
|
{
|
|
|
|
// Causes scroll issues if we don't position
|
|
|
|
this.popup.popup.style = "top: 0px";
|
|
|
|
(<SlMenuItem>this.menu.querySelector('sl-menu-item')).focus();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
public hide()
|
|
|
|
{
|
2024-07-09 23:35:46 +02:00
|
|
|
if(this.popup)
|
|
|
|
{
|
|
|
|
this.popup.active = false;
|
|
|
|
}
|
2024-06-17 17:26:10 +02:00
|
|
|
|
|
|
|
// egw_menu always creates a new menu
|
|
|
|
this.remove();
|
2024-06-15 00:22:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
handleSelect(event)
|
|
|
|
{
|
|
|
|
if(!this.popup)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(event.detail.item.value)
|
|
|
|
{
|
|
|
|
const item = <egwMenuItem>event.detail.item.value;
|
2024-06-17 17:26:10 +02:00
|
|
|
if(item.checkbox)
|
2024-06-15 00:22:31 +02:00
|
|
|
{
|
|
|
|
item.checked = event.detail.item.checked;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if(typeof item.onClick == "function")
|
|
|
|
{
|
|
|
|
this.hide();
|
|
|
|
item.onClick.call(event.detail.item, item, event);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-09 23:35:46 +02:00
|
|
|
handleDocumentClick(event)
|
|
|
|
{
|
|
|
|
if(!event.composedPath().includes(this))
|
|
|
|
{
|
|
|
|
this.hide();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
handleKeypress(event : KeyboardEvent)
|
|
|
|
{
|
|
|
|
if(event.key == "Escape")
|
|
|
|
{
|
|
|
|
event.preventDefault();
|
|
|
|
event.stopPropagation();
|
|
|
|
this.hide();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-06-15 00:22:31 +02:00
|
|
|
private itemTemplate(item : egwMenuItem)
|
|
|
|
{
|
|
|
|
if(item.caption == "-")
|
|
|
|
{
|
|
|
|
return html`
|
|
|
|
<sl-divider></sl-divider>`;
|
|
|
|
}
|
|
|
|
|
|
|
|
return html`
|
|
|
|
<sl-menu-item
|
|
|
|
class=${classMap({
|
|
|
|
"default-item": item.default
|
|
|
|
})}
|
|
|
|
id=${item.id}
|
|
|
|
type="${item.checkbox ? "checkbox" : "normal"}"
|
|
|
|
?checked=${item.checkbox && item.checked}
|
|
|
|
?disabled=${!item.enabled}
|
|
|
|
.value=${item}
|
|
|
|
>
|
|
|
|
${item.iconUrl ? html`
|
|
|
|
<et2-image slot="prefix" src="${item.iconUrl}"></et2-image>` : nothing}
|
2024-07-16 20:37:30 +02:00
|
|
|
<span style="color: ${item.color || nothing}">${item.caption}</span>
|
2024-06-15 00:22:31 +02:00
|
|
|
${item.shortcutCaption ? html`<span slot="suffix"
|
|
|
|
class="keyboard_shortcut">
|
|
|
|
${item.shortcutCaption}
|
|
|
|
</span>` : nothing}
|
|
|
|
${item.children.length == 0 ? nothing : html`
|
|
|
|
<sl-menu slot="submenu">
|
|
|
|
${repeat(item.children, i => this.itemTemplate(i))}
|
|
|
|
</sl-menu>
|
|
|
|
`}
|
|
|
|
</sl-menu-item>
|
|
|
|
`;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
render()
|
|
|
|
{
|
|
|
|
return html`
|
|
|
|
<sl-menu
|
|
|
|
@sl-select=${this.handleSelect}
|
|
|
|
>
|
|
|
|
${repeat(this.structure, i => this.itemTemplate(i))}
|
|
|
|
</sl-menu>`;
|
|
|
|
}
|
|
|
|
}
|