Et2Dialog: Better non-modal handling, now with less internal conflicts with SlDialog

This commit is contained in:
nathan 2022-12-07 13:45:38 -07:00
parent 32248e67ee
commit 12151139ff
4 changed files with 20 additions and 365 deletions

View File

@ -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);

View File

@ -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);

View File

@ -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">&times;</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>
`
})}`;
}
}

View File

@ -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);
}