forked from extern/egroupware
268 lines
6.1 KiB
TypeScript
268 lines
6.1 KiB
TypeScript
import {css, html, ifDefined, LitElement, repeat, SlotMixin} from '@lion/core';
|
|
import {DialogButton, Et2Dialog} from "./Et2Dialog";
|
|
import {et2_template} from "../et2_widget_template";
|
|
import {Et2DialogContent} from "./Et2DialogContent";
|
|
import shoelace from "../Styles/shoelace";
|
|
|
|
/**
|
|
* This handles the visible portion of the dialog, including the title & close button.
|
|
*
|
|
* Note we can't extend Et2Widget. If I try, something in the render / creation breaks and calling open() gives an
|
|
* error with modal: true
|
|
*/
|
|
export class Et2DialogOverlay extends SlotMixin(LitElement)
|
|
{
|
|
|
|
protected buttons : DialogButton[];
|
|
|
|
protected _dialog : Et2Dialog;
|
|
|
|
|
|
/**
|
|
* I don't know what's going on with styles here, but if we define Et2DialogOverlay.styles it breaks
|
|
* dialog display in Firefox.
|
|
* The styles are added in render(), which is bad from a performance standpoint, but good in that it works.
|
|
*/
|
|
static get _styles()
|
|
{
|
|
return [shoelace,
|
|
css`
|
|
:host {
|
|
display: inline-block;
|
|
background: white;
|
|
position: relative;
|
|
border: 1px solid silver;
|
|
box-shadow: -2px 1px 9px 3px #b4b4b4;
|
|
min-width: 250px;
|
|
touch-action: none;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
:host([hidden]) {
|
|
display: none;
|
|
}
|
|
|
|
.overlay {
|
|
display: flex;
|
|
flex-direction: column;
|
|
height: 100%;
|
|
}
|
|
.overlay__header {
|
|
display: flex;
|
|
border-bottom: 1px inset;
|
|
}
|
|
|
|
.overlay__heading {
|
|
margin: 0px;
|
|
padding: 6px 10px 0px;
|
|
flex: 1;
|
|
font-size: 130%;
|
|
font-weight: 400;
|
|
}
|
|
#overlay-content-node-wrapper {
|
|
flex: 1 1 auto;
|
|
padding: 10px;
|
|
}
|
|
|
|
.overlay__heading > .overlay__close-button {
|
|
flex: none;
|
|
}
|
|
|
|
.overlay__close-button {
|
|
min-width: 24px;
|
|
min-height: 24px;
|
|
border-width: 0;
|
|
padding: 0;
|
|
font-size: 24px;
|
|
}
|
|
#overlay-content-buttons {
|
|
display: flex;
|
|
flex-wrap: nowrap;
|
|
justify-content: flex-start;
|
|
align-items: stretch;
|
|
gap: 5px;
|
|
border-top: 1px solid silver;
|
|
margin-top: 0.5em;
|
|
padding: 5px;
|
|
}
|
|
/* Override style for legacy nextmatch action dialogs */
|
|
::slotted([slot="content"]) {
|
|
display: block !important;
|
|
position: relative !important;
|
|
inset: initial !important;
|
|
}
|
|
::slotted([slot="buttons"]) {
|
|
flex: 1 0 auto;
|
|
}
|
|
::slotted([align="right"]) {
|
|
margin-left: auto;
|
|
order: 1;
|
|
}`];
|
|
}
|
|
|
|
get properties()
|
|
{
|
|
return {
|
|
// Allow to force size, otherwise it sizes to contents
|
|
width: Number,
|
|
height: Number,
|
|
}
|
|
}
|
|
|
|
get slots()
|
|
{
|
|
return {
|
|
...super.slots,
|
|
buttons: () =>
|
|
{
|
|
return this._buttonsTemplate();
|
|
}
|
|
}
|
|
}
|
|
|
|
constructor()
|
|
{
|
|
super();
|
|
this.buttons = [];
|
|
}
|
|
|
|
|
|
|
|
// Need to wait for Overlay
|
|
async getUpdateComplete()
|
|
{
|
|
let result = await super.getUpdateComplete();
|
|
if(this._contentNode && this._contentNode instanceof LitElement)
|
|
{
|
|
await (<LitElement>this._contentNode).updateComplete;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
connectedCallback()
|
|
{
|
|
super.connectedCallback();
|
|
// Need to wait for Overlay
|
|
this.updateComplete
|
|
.then(async() =>
|
|
{
|
|
if(this._contentNode && this._contentNode instanceof LitElement)
|
|
{
|
|
// Re-do render to get proper images
|
|
this._contentNode.requestUpdate();
|
|
|
|
await this._contentNode.updateComplete;
|
|
}
|
|
});
|
|
}
|
|
|
|
egw() : IegwAppLocal
|
|
{
|
|
if(this._dialog)
|
|
{
|
|
return this._dialog.egw();
|
|
}
|
|
else
|
|
{
|
|
return egw();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Block until after the paint - This is needed to deal with children not fully "done" before the OverlayController
|
|
* tries to do things with them
|
|
*
|
|
* @returns {Promise<any>}
|
|
*/
|
|
async performUpdate()
|
|
{
|
|
await new Promise((resolve) => setTimeout(() => resolve()));
|
|
return super.performUpdate();
|
|
}
|
|
|
|
get _contentNode() : Et2DialogContent | et2_template
|
|
{
|
|
// @ts-ignore
|
|
return this.querySelector("[slot='content']");
|
|
}
|
|
|
|
/** @private */
|
|
__dispatchCloseEvent()
|
|
{
|
|
this.dispatchEvent(new Event('close-overlay'));
|
|
}
|
|
|
|
render()
|
|
{
|
|
// This style is just for this dialog
|
|
let styles = [Et2DialogOverlay._styles];
|
|
|
|
if(this.width && Number.isInteger(this.width))
|
|
{
|
|
styles.push(css`.overlay {width: ${this.width}px}`);
|
|
}
|
|
if(this.height && Number.isInteger(this.height))
|
|
{
|
|
styles.push(css`.overlay {height: ${this.height}px}`);
|
|
}
|
|
return html`
|
|
<style>${styles}</style>
|
|
<div class="overlay">
|
|
<div class="overlay__header">
|
|
<h1 class="overlay__heading">
|
|
<slot name="heading"></slot>
|
|
</h1>
|
|
<slot name="header"></slot>
|
|
${this._closeButtonTemplate()}
|
|
</div>
|
|
<div id="overlay-content-node-wrapper">
|
|
<slot name="content"></slot>
|
|
</div>
|
|
<div id="overlay-content-buttons">
|
|
<slot name="buttons"></slot>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
_closeButtonTemplate()
|
|
{
|
|
if (this._dialog.noCloseButton)
|
|
{
|
|
return;
|
|
}
|
|
|
|
return html`<button
|
|
@click="${this.__dispatchCloseEvent}"
|
|
id="close-button"
|
|
title="${this.egw().lang("Close")}"
|
|
aria-label="${this.egw().lang("Close dialog")}"
|
|
class="overlay__close-button"
|
|
>
|
|
<slot name="close-icon">×</slot>
|
|
</button>
|
|
`;
|
|
}
|
|
|
|
_buttonsTemplate()
|
|
{
|
|
if(!this.buttons)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Set button._parent here, otherwise button will have trouble finding our egw()
|
|
return html`${repeat(this.buttons, (button : DialogButton) => button.id, (button, index) =>
|
|
{
|
|
return html`
|
|
<et2-button ._parent=${this} id=${button.id} button_id=${button.button_id}
|
|
label=${button.label}
|
|
.image=${ifDefined(button.image)}
|
|
.noSubmit=${true}
|
|
?disabled=${button.disabled}
|
|
align=${ifDefined(button.align)}>
|
|
</et2-button>
|
|
`
|
|
})}`;
|
|
}
|
|
} |