mirror of
https://github.com/EGroupware/egroupware.git
synced 2025-01-12 00:49:04 +01:00
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 */
|
/* 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;
|
pointer-events: none;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
:host(:not([modal])) .dialog__panel {
|
:host(:not([isModal])) .dialog__panel {
|
||||||
pointer-events: auto;
|
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
|
* 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
|
* Title for the dialog, goes in the header
|
||||||
@ -362,7 +363,7 @@ export class Et2Dialog extends Et2Widget(SlotMixin(SlDialog))
|
|||||||
{
|
{
|
||||||
this._setApiInstance(parent_egw);
|
this._setApiInstance(parent_egw);
|
||||||
}
|
}
|
||||||
this.modal = true;
|
this.isModal = false;
|
||||||
this.dialog_type = Et2Dialog.PLAIN_MESSAGE;
|
this.dialog_type = Et2Dialog.PLAIN_MESSAGE;
|
||||||
this.destroyOnClose = true;
|
this.destroyOnClose = true;
|
||||||
this.hideOnEscape = this.hideOnEscape === false ? false : 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
|
// Prevent close if they click the overlay when the dialog is modal
|
||||||
this.addEventListener('sl-request-close', event =>
|
this.addEventListener('sl-request-close', event =>
|
||||||
{
|
{
|
||||||
if(this.modal && event.detail.source === 'overlay')
|
if(this.isModal && event.detail.source === 'overlay')
|
||||||
{
|
{
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
@ -999,7 +1000,7 @@ export class Et2Dialog extends Et2Widget(SlotMixin(SlDialog))
|
|||||||
listeners: {
|
listeners: {
|
||||||
move: this._onMoveResize
|
move: this._onMoveResize
|
||||||
},
|
},
|
||||||
modifiers: (this.modal ? [] : [
|
modifiers: (this.isModal ? [] : [
|
||||||
interact.modifiers.restrict({
|
interact.modifiers.restrict({
|
||||||
restriction: 'parent',
|
restriction: 'parent',
|
||||||
endOnly: true
|
endOnly: true
|
||||||
@ -1069,6 +1070,7 @@ export class Et2Dialog extends Et2Widget(SlotMixin(SlDialog))
|
|||||||
message: _message,
|
message: _message,
|
||||||
title: _title || dialog.egw().lang('Confirmation required'),
|
title: _title || dialog.egw().lang('Confirmation required'),
|
||||||
buttons: typeof _buttons != 'undefined' ? _buttons : Et2Dialog.BUTTONS_YES_NO,
|
buttons: typeof _buttons != 'undefined' ? _buttons : Et2Dialog.BUTTONS_YES_NO,
|
||||||
|
isModal: true,
|
||||||
dialog_type: typeof _type != 'undefined' ? _type : Et2Dialog.QUESTION_MESSAGE,
|
dialog_type: typeof _type != 'undefined' ? _type : Et2Dialog.QUESTION_MESSAGE,
|
||||||
icon: _icon,
|
icon: _icon,
|
||||||
value: _value
|
value: _value
|
||||||
@ -1096,6 +1098,7 @@ export class Et2Dialog extends Et2Widget(SlotMixin(SlDialog))
|
|||||||
message: _message,
|
message: _message,
|
||||||
title: _title,
|
title: _title,
|
||||||
buttons: Et2Dialog.BUTTONS_OK,
|
buttons: Et2Dialog.BUTTONS_OK,
|
||||||
|
isModal: true,
|
||||||
dialog_type: _type || Et2Dialog.INFORMATION_MESSAGE
|
dialog_type: _type || Et2Dialog.INFORMATION_MESSAGE
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1131,6 +1134,7 @@ export class Et2Dialog extends Et2Widget(SlotMixin(SlDialog))
|
|||||||
},
|
},
|
||||||
title: _title || 'Input required',
|
title: _title || 'Input required',
|
||||||
buttons: _buttons || Et2Dialog.BUTTONS_OK_CANCEL,
|
buttons: _buttons || Et2Dialog.BUTTONS_OK_CANCEL,
|
||||||
|
isModal: true,
|
||||||
value: {
|
value: {
|
||||||
content: {
|
content: {
|
||||||
value: _value,
|
value: _value,
|
||||||
@ -1252,6 +1256,7 @@ export class Et2Dialog extends Et2Widget(SlotMixin(SlDialog))
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
title: _title || 'please wait...',
|
title: _title || 'please wait...',
|
||||||
|
isModal: true,
|
||||||
buttons: buttons
|
buttons: buttons
|
||||||
});
|
});
|
||||||
dialog.egw().window.document.body.appendChild(<LitElement><unknown>dialog);
|
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?)
|
constructor(parent?, attrs?)
|
||||||
{
|
{
|
||||||
super(parent?.egw() || egw);
|
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)
|
if(attrs)
|
||||||
{
|
{
|
||||||
this.transformAttributes(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
|
// move the overlay dialog into appendTo dom since we want it to be shown in that container
|
||||||
if(this.appendTo)
|
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
|
// revert the moved container back to its original position in order to be able to teardown the overlay properly
|
||||||
if(this.appendTo)
|
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);
|
super.handleClose(ev);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user