forked from extern/egroupware
Et2Dialog: Better non-modal handling, now with less internal conflicts with SlDialog
This commit is contained in:
parent
32248e67ee
commit
12151139ff
@ -224,11 +224,11 @@ export class Et2Dialog extends Et2Widget(SlotMixin(SlDialog))
|
||||
}
|
||||
|
||||
/* Non-modal dialogs don't have an overlay */
|
||||
:host(:not([modal])) .dialog, :host(:not([modal])) .dialog__overlay {
|
||||
:host(:not([isModal])) .dialog, :host(:not([isModal])) .dialog__overlay {
|
||||
pointer-events: none;
|
||||
background: transparent;
|
||||
}
|
||||
:host(:not([modal])) .dialog__panel {
|
||||
:host(:not([isModal])) .dialog__panel {
|
||||
pointer-events: auto;
|
||||
}
|
||||
`
|
||||
@ -243,8 +243,9 @@ export class Et2Dialog extends Et2Widget(SlotMixin(SlDialog))
|
||||
|
||||
/**
|
||||
* Allow other controls to be accessed while the dialog is visible
|
||||
* while not conflicting with internal attribute
|
||||
*/
|
||||
modal: {type: Boolean, reflect: true},
|
||||
isModal: {type: Boolean, reflect: true},
|
||||
|
||||
/**
|
||||
* Title for the dialog, goes in the header
|
||||
@ -362,7 +363,7 @@ export class Et2Dialog extends Et2Widget(SlotMixin(SlDialog))
|
||||
{
|
||||
this._setApiInstance(parent_egw);
|
||||
}
|
||||
this.modal = true;
|
||||
this.isModal = false;
|
||||
this.dialog_type = Et2Dialog.PLAIN_MESSAGE;
|
||||
this.destroyOnClose = true;
|
||||
this.hideOnEscape = this.hideOnEscape === false ? false : true;
|
||||
@ -396,7 +397,7 @@ export class Et2Dialog extends Et2Widget(SlotMixin(SlDialog))
|
||||
// Prevent close if they click the overlay when the dialog is modal
|
||||
this.addEventListener('sl-request-close', event =>
|
||||
{
|
||||
if(this.modal && event.detail.source === 'overlay')
|
||||
if(this.isModal && event.detail.source === 'overlay')
|
||||
{
|
||||
event.preventDefault();
|
||||
}
|
||||
@ -999,7 +1000,7 @@ export class Et2Dialog extends Et2Widget(SlotMixin(SlDialog))
|
||||
listeners: {
|
||||
move: this._onMoveResize
|
||||
},
|
||||
modifiers: (this.modal ? [] : [
|
||||
modifiers: (this.isModal ? [] : [
|
||||
interact.modifiers.restrict({
|
||||
restriction: 'parent',
|
||||
endOnly: true
|
||||
@ -1069,6 +1070,7 @@ export class Et2Dialog extends Et2Widget(SlotMixin(SlDialog))
|
||||
message: _message,
|
||||
title: _title || dialog.egw().lang('Confirmation required'),
|
||||
buttons: typeof _buttons != 'undefined' ? _buttons : Et2Dialog.BUTTONS_YES_NO,
|
||||
isModal: true,
|
||||
dialog_type: typeof _type != 'undefined' ? _type : Et2Dialog.QUESTION_MESSAGE,
|
||||
icon: _icon,
|
||||
value: _value
|
||||
@ -1096,6 +1098,7 @@ export class Et2Dialog extends Et2Widget(SlotMixin(SlDialog))
|
||||
message: _message,
|
||||
title: _title,
|
||||
buttons: Et2Dialog.BUTTONS_OK,
|
||||
isModal: true,
|
||||
dialog_type: _type || Et2Dialog.INFORMATION_MESSAGE
|
||||
});
|
||||
|
||||
@ -1131,6 +1134,7 @@ export class Et2Dialog extends Et2Widget(SlotMixin(SlDialog))
|
||||
},
|
||||
title: _title || 'Input required',
|
||||
buttons: _buttons || Et2Dialog.BUTTONS_OK_CANCEL,
|
||||
isModal: true,
|
||||
value: {
|
||||
content: {
|
||||
value: _value,
|
||||
@ -1252,6 +1256,7 @@ export class Et2Dialog extends Et2Widget(SlotMixin(SlDialog))
|
||||
}
|
||||
},
|
||||
title: _title || 'please wait...',
|
||||
isModal: true,
|
||||
buttons: buttons
|
||||
});
|
||||
dialog.egw().window.document.body.appendChild(<LitElement><unknown>dialog);
|
||||
|
@ -1,89 +0,0 @@
|
||||
import {css, CSSResultArray, html, LitElement} from "@lion/core";
|
||||
import {Et2Widget} from "../Et2Widget/Et2Widget";
|
||||
import shoelace from "../Styles/shoelace";
|
||||
|
||||
/**
|
||||
* Widget for the actual content of a dialog, used when we're not doing a template
|
||||
*
|
||||
*/
|
||||
export class Et2DialogContent extends Et2Widget(LitElement)
|
||||
{
|
||||
static styles : CSSResultArray = [
|
||||
shoelace,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
min-width: 300px;
|
||||
min-height: 32px;
|
||||
}
|
||||
.dialog {
|
||||
}
|
||||
.dialog_icon {
|
||||
margin-right: 2ex;
|
||||
vertical-align: middle;
|
||||
}
|
||||
`
|
||||
];
|
||||
|
||||
|
||||
get properties()
|
||||
{
|
||||
return {
|
||||
...super.properties(),
|
||||
|
||||
message: String,
|
||||
dialogType: Number,
|
||||
icon: String,
|
||||
value: Object
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Details for dialog type options
|
||||
*/
|
||||
private readonly _dialogTypes : any = [
|
||||
//PLAIN_MESSAGE: 0
|
||||
"",
|
||||
//INFORMATION_MESSAGE: 1,
|
||||
"dialog_info",
|
||||
//QUESTION_MESSAGE: 2,
|
||||
"dialog_help",
|
||||
//WARNING_MESSAGE: 3,
|
||||
"dialog_warning",
|
||||
//ERROR_MESSAGE: 4,
|
||||
"dialog_error"
|
||||
];
|
||||
|
||||
constructor()
|
||||
{
|
||||
super();
|
||||
|
||||
this.icon = "";
|
||||
this.dialogType = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
|
||||
render()
|
||||
{
|
||||
let icon = this.icon || this.parentNode.egw().image(this._dialogTypes[this.dialogType]) || "";
|
||||
return html`
|
||||
<div class="dialog ${this._dialogTypes[this.dialogType]}">
|
||||
<img class="dialog_icon" src=${icon}/>
|
||||
<slot>Empty dialog - add some content</slot>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("et2-dialog-content", Et2DialogContent);
|
@ -1,268 +0,0 @@
|
||||
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>
|
||||
`
|
||||
})}`;
|
||||
}
|
||||
}
|
@ -54,6 +54,13 @@ export class et2_dialog extends Et2Dialog
|
||||
constructor(parent?, attrs?)
|
||||
{
|
||||
super(parent?.egw() || egw);
|
||||
if(attrs.hasOwnProperty("modal"))
|
||||
{
|
||||
// modal is an internal property of SlDialog
|
||||
console.warn("modal is an internal property, use isModal instead");
|
||||
attrs.isModal = attrs.modal;
|
||||
delete attrs.modal;
|
||||
}
|
||||
if(attrs)
|
||||
{
|
||||
this.transformAttributes(attrs);
|
||||
@ -95,7 +102,7 @@ export class et2_dialog extends Et2Dialog
|
||||
// move the overlay dialog into appendTo dom since we want it to be shown in that container
|
||||
if(this.appendTo)
|
||||
{
|
||||
document.getElementsByClassName(this.appendTo.replace('.', ''))[0].appendChild(this._cachedOverlayContentNode);
|
||||
document.getElementsByClassName(this.appendTo.replace('.', ''))[0].appendChild(this);
|
||||
}
|
||||
}
|
||||
|
||||
@ -104,7 +111,7 @@ export class et2_dialog extends Et2Dialog
|
||||
// revert the moved container back to its original position in order to be able to teardown the overlay properly
|
||||
if(this.appendTo)
|
||||
{
|
||||
document.getElementsByClassName('global-overlays__overlay-container')[0].appendChild(this._cachedOverlayContentNode);
|
||||
document.getElementsByClassName('global-overlays__overlay-container')[0].appendChild(this);
|
||||
}
|
||||
super.handleClose(ev);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user