forked from extern/egroupware
Dialog work
- Switch from LionDialog to SlDialog as base - First input should get focus - First button gets set as primary (if no default set), Enter key will act as a click on it - Escape key closes dialog
This commit is contained in:
parent
350dd31b2b
commit
390fbf3608
@ -620,8 +620,8 @@ class AdminApp extends EgwApp
|
|||||||
{
|
{
|
||||||
dialog_options['width'] = 550;
|
dialog_options['width'] = 550;
|
||||||
modifications.tabs = {
|
modifications.tabs = {
|
||||||
add_tabs: true,
|
addTabs: true,
|
||||||
tabs: [{
|
extraTabs: [{
|
||||||
label: egw.lang('Documentation'),
|
label: egw.lang('Documentation'),
|
||||||
template: 'policy.admin_cmd',
|
template: 'policy.admin_cmd',
|
||||||
prepend: false
|
prepend: false
|
||||||
|
@ -11,17 +11,17 @@
|
|||||||
|
|
||||||
import {Et2Widget} from "../Et2Widget/Et2Widget";
|
import {Et2Widget} from "../Et2Widget/Et2Widget";
|
||||||
import {et2_button} from "../et2_widget_button";
|
import {et2_button} from "../et2_widget_button";
|
||||||
import {LionDialog} from "@lion/dialog";
|
|
||||||
import {et2_widget} from "../et2_core_widget";
|
import {et2_widget} from "../et2_core_widget";
|
||||||
import {html, LitElement, ScopedElementsMixin, SlotMixin} from "@lion/core";
|
import {classMap, css, html, ifDefined, LitElement, render, repeat, SlotMixin, styleMap} from "@lion/core";
|
||||||
import {Et2DialogOverlay} from "./Et2DialogOverlay";
|
|
||||||
import {Et2DialogContent} from "./Et2DialogContent";
|
|
||||||
import {et2_template} from "../et2_widget_template";
|
import {et2_template} from "../et2_widget_template";
|
||||||
import {etemplate2} from "../etemplate2";
|
import {etemplate2} from "../etemplate2";
|
||||||
import {IegwAppLocal} from "../../jsapi/egw_global";
|
import {egw, IegwAppLocal} from "../../jsapi/egw_global";
|
||||||
import interact from "@interactjs/interactjs";
|
import interact from "@interactjs/interactjs";
|
||||||
import type {InteractEvent} from "@interactjs/core/InteractEvent";
|
import type {InteractEvent} from "@interactjs/core/InteractEvent";
|
||||||
import {Et2Button} from "../Et2Button/Et2Button";
|
import {Et2Button} from "../Et2Button/Et2Button";
|
||||||
|
import shoelace from "../Styles/shoelace";
|
||||||
|
import {SlDialog} from "@shoelace-style/shoelace";
|
||||||
|
import {egwIsMobile} from "../../egw_action/egw_action_common";
|
||||||
|
|
||||||
export interface DialogButton
|
export interface DialogButton
|
||||||
{
|
{
|
||||||
@ -122,11 +122,9 @@ export interface DialogButton
|
|||||||
* let result = await dialog.getComplete();
|
* let result = await dialog.getComplete();
|
||||||
*```
|
*```
|
||||||
*
|
*
|
||||||
* Because of how LionDialog does its layout and rendering, it's easiest to separate the dialog popup from
|
* Customize initial focus by setting the "autofocus" attribute on a control, otherwise first input will have focus
|
||||||
* the dialog content. This allows us to easily preserve the WebComponent styling. You should interact with the
|
|
||||||
* Et2Dialog though, and ignore the Et2DialogOverlay & Et2DialogContent.
|
|
||||||
*/
|
*/
|
||||||
export class Et2Dialog extends Et2Widget(ScopedElementsMixin(SlotMixin(LionDialog)))
|
export class Et2Dialog extends Et2Widget(SlotMixin(SlDialog))
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Dialogs don't always get added to an etemplate, so we keep our own egw
|
* Dialogs don't always get added to an etemplate, so we keep our own egw
|
||||||
@ -184,6 +182,31 @@ export class Et2Dialog extends Et2Widget(ScopedElementsMixin(SlotMixin(LionDialo
|
|||||||
public static readonly YES_BUTTON : number = 2;
|
public static readonly YES_BUTTON : number = 2;
|
||||||
public static readonly NO_BUTTON : number = 3;
|
public static readonly NO_BUTTON : number = 3;
|
||||||
|
|
||||||
|
|
||||||
|
static get styles()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
...shoelace,
|
||||||
|
...(super.styles || []),
|
||||||
|
css`
|
||||||
|
:host {
|
||||||
|
--header-spacing: var(--sl-spacing-medium);
|
||||||
|
--body-spacing: var(--sl-spacing-medium)
|
||||||
|
}
|
||||||
|
.dialog__title {
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
.dialog__footer {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: stretch;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
static get properties()
|
static get properties()
|
||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
@ -245,21 +268,14 @@ export class Et2Dialog extends Et2Widget(ScopedElementsMixin(SlotMixin(LionDialo
|
|||||||
get slots()
|
get slots()
|
||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
...super.slots
|
...super.slots,
|
||||||
|
'': () =>
|
||||||
|
{
|
||||||
|
return this._contentTemplate();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Still not sure what this does, but it's important.
|
|
||||||
// Seems to be related to the constructor, and what's available during the "creation"
|
|
||||||
static get scopedElements()
|
|
||||||
{
|
|
||||||
return {
|
|
||||||
...super.scopedElements,
|
|
||||||
'et2-dialog-overlay-frame': Et2DialogOverlay,
|
|
||||||
'et2-dialog-content': Et2DialogContent
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* List of properties that get translated
|
* List of properties that get translated
|
||||||
* Done separately to not interfere with properties - if we re-define label property,
|
* Done separately to not interfere with properties - if we re-define label property,
|
||||||
@ -323,14 +339,20 @@ export class Et2Dialog extends Et2Widget(ScopedElementsMixin(SlotMixin(LionDialo
|
|||||||
this.destroyOnClose = true;
|
this.destroyOnClose = true;
|
||||||
this.hideOnEscape = this.hideOnEscape === false ? false : true;
|
this.hideOnEscape = this.hideOnEscape === false ? false : true;
|
||||||
this.__value = {};
|
this.__value = {};
|
||||||
|
this.open = true;
|
||||||
|
|
||||||
this._onOpen = this._onOpen.bind(this);
|
this.handleOpen = this.handleOpen.bind(this);
|
||||||
this._onClose = this._onClose.bind(this);
|
this.handleClose = this.handleClose.bind(this);
|
||||||
this._onClick = this._onClick.bind(this);
|
this._onClick = this._onClick.bind(this);
|
||||||
this._onButtonClick = this._onButtonClick.bind(this);
|
this._onButtonClick = this._onButtonClick.bind(this);
|
||||||
this._onMoveResize = this._onMoveResize.bind(this);
|
this._onMoveResize = this._onMoveResize.bind(this);
|
||||||
|
this.handleKeyDown = this.handleKeyDown.bind(this);
|
||||||
this._adoptTemplateButtons = this._adoptTemplateButtons.bind(this);
|
this._adoptTemplateButtons = this._adoptTemplateButtons.bind(this);
|
||||||
|
|
||||||
|
// Don't leave it undefined, it's easier to deal with if it's just already resolved.
|
||||||
|
// It will be re-set if a template is loaded
|
||||||
|
this._template_promise = Promise.resolve(false);
|
||||||
|
|
||||||
// Create this here so we have something, otherwise the creator might continue with undefined while we
|
// Create this here so we have something, otherwise the creator might continue with undefined while we
|
||||||
// wait for the dialog to complete & open
|
// wait for the dialog to complete & open
|
||||||
this._complete_promise = new Promise<[number, Object]>((resolve) =>
|
this._complete_promise = new Promise<[number, Object]>((resolve) =>
|
||||||
@ -343,32 +365,82 @@ export class Et2Dialog extends Et2Widget(ScopedElementsMixin(SlotMixin(LionDialo
|
|||||||
{
|
{
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
|
|
||||||
// Wait for everything to complete, then auto-open
|
// Prevent close if they click the overlay when the dialog is modal
|
||||||
this.getUpdateComplete().then(() =>
|
this.addEventListener('sl-request-close', event =>
|
||||||
{
|
{
|
||||||
// _overlayCtrl is not available earlier
|
if(this.modal && event.detail.source === 'overlay')
|
||||||
this._overlayCtrl?.addEventListener("show", this._onOpen);
|
{
|
||||||
this._overlayCtrl.addEventListener('hide', this._onClose);
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
this.addEventListener("sl-after-show", this.handleOpen);
|
||||||
|
this.addEventListener('sl-hide', this.handleClose);
|
||||||
|
|
||||||
// Bind on the ancestor, not the buttons, so their click handler gets a chance to run
|
|
||||||
this._overlayContentNode.addEventListener("click", this._onButtonClick);
|
|
||||||
window.setTimeout(this.open, 0);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnectedCallback()
|
disconnectedCallback()
|
||||||
{
|
{
|
||||||
super.disconnectedCallback();
|
super.disconnectedCallback();
|
||||||
this._overlayCtrl.removeEventListener("hide", this._onClose);
|
this.removeEventListener("sl-hide", this.handleClose);
|
||||||
this._overlayCtrl.removeEventListener("show", this._onOpen);
|
this.removeEventListener("sl-after-show", this.handleOpen);
|
||||||
this._overlayContentNode.removeEventListener("click", this._onButtonClick);
|
}
|
||||||
|
|
||||||
|
addOpenListeners()
|
||||||
|
{
|
||||||
|
//super.addOpenListeners();
|
||||||
|
|
||||||
|
// Bind on the ancestor, not the buttons, so their click handler gets a chance to run
|
||||||
|
this.addEventListener("click", this._onButtonClick);
|
||||||
|
this.addEventListener("keydown", this.handleKeyDown);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeOpenListeners()
|
||||||
|
{
|
||||||
|
//super.removeOpenListeners();
|
||||||
|
this.removeEventListener("click", this._onButtonClick);
|
||||||
|
this.removeEventListener("keydown", this.handleKeyDown);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleKeyDown(event : KeyboardEvent)
|
||||||
|
{
|
||||||
|
// Parent handles escape, but is already bound
|
||||||
|
super.handleKeyDown(event);
|
||||||
|
|
||||||
|
// Trigger the "primary" or first button
|
||||||
|
if(this.open && event.key === 'Enter')
|
||||||
|
{
|
||||||
|
let button = this.querySelectorAll("[varient='primary']");
|
||||||
|
if(button.length == 0)
|
||||||
|
{
|
||||||
|
// Nothing explicitly marked, check for buttons in the footer
|
||||||
|
button = this.querySelectorAll("et2-button[slot='footer']");
|
||||||
|
}
|
||||||
|
if(button && button[0])
|
||||||
|
{
|
||||||
|
event.stopPropagation();
|
||||||
|
button[0].dispatchEvent(new CustomEvent('click', {bubbles: true}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
firstUpdated()
|
||||||
|
{
|
||||||
|
super.firstUpdated();
|
||||||
|
|
||||||
|
// If we start open, fire handler to get setup done
|
||||||
|
if(this.open)
|
||||||
|
{
|
||||||
|
this.handleOpenChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateComplete.then(() => this._setDefaultAutofocus());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Need to wait for Overlay
|
// Need to wait for Overlay
|
||||||
async getUpdateComplete()
|
async getUpdateComplete()
|
||||||
{
|
{
|
||||||
await super.getUpdateComplete();
|
await super.getUpdateComplete();
|
||||||
await this._overlayContentNode.getUpdateComplete();
|
|
||||||
|
|
||||||
// Wait for template to finish loading
|
// Wait for template to finish loading
|
||||||
if(this._template_widget)
|
if(this._template_widget)
|
||||||
@ -382,18 +454,28 @@ export class Et2Dialog extends Et2Widget(ScopedElementsMixin(SlotMixin(LionDialo
|
|||||||
return this._complete_promise;
|
return this._complete_promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
_onOpen()
|
handleOpen()
|
||||||
{
|
{
|
||||||
|
this.addOpenListeners();
|
||||||
this._button_id = null;
|
this._button_id = null;
|
||||||
this._complete_promise = this._complete_promise || new Promise<[number, Object]>((resolve) => this._completeResolver);
|
this._complete_promise = this._complete_promise || new Promise<[number, Object]>((resolve) => this._completeResolver);
|
||||||
|
|
||||||
this._setupMoveResize(this._overlayContentNode);
|
|
||||||
// Now consumers can listen for "open" event, though getUpdateComplete().then(...) also works
|
// Now consumers can listen for "open" event, though getUpdateComplete().then(...) also works
|
||||||
this.dispatchEvent(new Event('open'));
|
this.dispatchEvent(new Event('open'));
|
||||||
|
|
||||||
|
Promise.all([this._template_promise, this.updateComplete])
|
||||||
|
.then(() => this._setupMoveResize());
|
||||||
}
|
}
|
||||||
|
|
||||||
_onClose(ev : PointerEvent)
|
handleClose(ev : PointerEvent)
|
||||||
{
|
{
|
||||||
|
// Avoid closing if a selectbox is closed
|
||||||
|
if(ev.target !== this)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.removeOpenListeners();
|
||||||
this._completeResolver([this._button_id, this.value]);
|
this._completeResolver([this._button_id, this.value]);
|
||||||
this._button_id = null;
|
this._button_id = null;
|
||||||
this._complete_promise = undefined;
|
this._complete_promise = undefined;
|
||||||
@ -406,7 +488,6 @@ export class Et2Dialog extends Et2Widget(ScopedElementsMixin(SlotMixin(LionDialo
|
|||||||
{
|
{
|
||||||
this._template_widget.clear();
|
this._template_widget.clear();
|
||||||
}
|
}
|
||||||
this._overlayCtrl.teardown();
|
|
||||||
this.remove();
|
this.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -468,7 +549,7 @@ export class Et2Dialog extends Et2Widget(ScopedElementsMixin(SlotMixin(LionDialo
|
|||||||
{
|
{
|
||||||
console.log(e);
|
console.log(e);
|
||||||
}
|
}
|
||||||
this.close();
|
this.hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -518,15 +599,6 @@ export class Et2Dialog extends Et2Widget(ScopedElementsMixin(SlotMixin(LionDialo
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Fire the close-overlay event to use all registered listeners
|
|
||||||
* @deprecated
|
|
||||||
*/
|
|
||||||
destroy()
|
|
||||||
{
|
|
||||||
this._overlayContentNode.dispatchEvent(new Event('close-overlay'));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated
|
* @deprecated
|
||||||
* @returns {Object}
|
* @returns {Object}
|
||||||
@ -555,6 +627,12 @@ export class Et2Dialog extends Et2Widget(ScopedElementsMixin(SlotMixin(LionDialo
|
|||||||
return this._template_widget || null;
|
return this._template_widget || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get title() : string { return this.label }
|
||||||
|
|
||||||
|
set title(new_title : string)
|
||||||
|
{
|
||||||
|
this.label = new_title;
|
||||||
|
}
|
||||||
|
|
||||||
updated(changedProperties)
|
updated(changedProperties)
|
||||||
{
|
{
|
||||||
@ -563,6 +641,11 @@ export class Et2Dialog extends Et2Widget(ScopedElementsMixin(SlotMixin(LionDialo
|
|||||||
{
|
{
|
||||||
this._loadTemplate();
|
this._loadTemplate();
|
||||||
}
|
}
|
||||||
|
if(changedProperties.has("buttons"))
|
||||||
|
{
|
||||||
|
render(this._buttonsTemplate(), this);
|
||||||
|
this.requestUpdate();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_loadTemplate()
|
_loadTemplate()
|
||||||
@ -571,13 +654,14 @@ export class Et2Dialog extends Et2Widget(ScopedElementsMixin(SlotMixin(LionDialo
|
|||||||
{
|
{
|
||||||
this._template_widget.clear();
|
this._template_widget.clear();
|
||||||
}
|
}
|
||||||
|
this._contentNode.replaceChildren();
|
||||||
|
|
||||||
// Etemplate wants a content
|
// Etemplate wants a content
|
||||||
if(typeof this.__value.content === "undefined")
|
if(typeof this.__value.content === "undefined")
|
||||||
{
|
{
|
||||||
this.__value.content = {};
|
this.__value.content = {};
|
||||||
}
|
}
|
||||||
this._template_widget = new etemplate2(this._overlayContentNode._contentNode);
|
this._template_widget = new etemplate2(this._contentNode);
|
||||||
|
|
||||||
// Fire an event so consumers can do their thing - etemplate will fire its own load event when its done
|
// Fire an event so consumers can do their thing - etemplate will fire its own load event when its done
|
||||||
if(!this.dispatchEvent(new CustomEvent("before-load", {
|
if(!this.dispatchEvent(new CustomEvent("before-load", {
|
||||||
@ -631,83 +715,103 @@ export class Et2Dialog extends Et2Widget(ScopedElementsMixin(SlotMixin(LionDialo
|
|||||||
this._template_widget.DOMContainer.setAttribute('id', this.__template.replace(/^(.*\/)?([^/]+?)(\.xet)?(\?.*)?$/, '$2').replace(/\./g, '-'));
|
this._template_widget.DOMContainer.setAttribute('id', this.__template.replace(/^(.*\/)?([^/]+?)(\.xet)?(\?.*)?$/, '$2').replace(/\./g, '-'));
|
||||||
|
|
||||||
// Look for buttons after load
|
// Look for buttons after load
|
||||||
this._overlayContentNode._contentNode.addEventListener("load", this._adoptTemplateButtons);
|
this._contentNode.addEventListener("load", this._adoptTemplateButtons);
|
||||||
}
|
|
||||||
|
|
||||||
render()
|
// Default autofocus to first input if autofocus is not set
|
||||||
{
|
this._contentNode.addEventListener("load", this._setDefaultAutofocus);
|
||||||
return this._overlayTemplate();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
// Need to update to pick up changes
|
||||||
* Don't allow any children here, pass them on to the content node
|
this.requestUpdate();
|
||||||
*
|
|
||||||
* @param {HTMLElement} node
|
|
||||||
* @returns {any}
|
|
||||||
*/
|
|
||||||
appendChild(node : HTMLElement)
|
|
||||||
{
|
|
||||||
return this._overlayContentNode.appendChild(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defining this overlay as a templates from OverlayMixin
|
|
||||||
* this is our source to give as .contentNode to OverlayController.
|
|
||||||
* @protected
|
|
||||||
*/
|
|
||||||
protected _overlayTemplate()
|
|
||||||
{
|
|
||||||
return html`
|
|
||||||
<div id="overlay-content-node-wrapper">
|
|
||||||
<et2-dialog-overlay-frame class="dialog__overlay-frame"
|
|
||||||
.width=${this.width}
|
|
||||||
.height=${this.height}
|
|
||||||
._dialog=${this}
|
|
||||||
.buttons=${this._getButtons()}>
|
|
||||||
<span slot="heading">${this.title}</span>
|
|
||||||
${this._contentTemplate()}
|
|
||||||
|
|
||||||
</et2-dialog-overlay-frame>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @override Configures OverlayMixin
|
|
||||||
*/
|
|
||||||
get _overlayContentNode()
|
|
||||||
{
|
|
||||||
if(this._cachedOverlayContentNode)
|
|
||||||
{
|
|
||||||
return this._cachedOverlayContentNode;
|
|
||||||
}
|
|
||||||
this._cachedOverlayContentNode = /** @type {HTMLElement} */ (
|
|
||||||
/** @type {ShadowRoot} */ (this.shadowRoot).querySelector('.dialog__overlay-frame')
|
|
||||||
);
|
|
||||||
return this._cachedOverlayContentNode;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_contentTemplate()
|
_contentTemplate()
|
||||||
{
|
{
|
||||||
if(this.__template)
|
/**
|
||||||
|
* Classes for dialog type options
|
||||||
|
*/
|
||||||
|
const _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"
|
||||||
|
];
|
||||||
|
let icon = this.icon || this.egw().image(_dialogTypes[this.dialogType] || "") || "";
|
||||||
|
let type = _dialogTypes[this.dialogType];
|
||||||
|
let classes = {
|
||||||
|
dialog_content: true,
|
||||||
|
"dialog--has_message": this.message,
|
||||||
|
"dialog--has_template": this.__template
|
||||||
|
};
|
||||||
|
if(type)
|
||||||
{
|
{
|
||||||
return html`
|
classes[type] = true;
|
||||||
<div slot="content"></div>`;
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
// Add in styles set via property
|
||||||
|
let styles = {};
|
||||||
|
if(this.width)
|
||||||
{
|
{
|
||||||
return html`
|
styles["--width"] = this.width;
|
||||||
<et2-dialog-content slot="content" ?icon=${this.icon}
|
|
||||||
.dialog_type=${this.dialog_type}
|
|
||||||
.value=${this.value}
|
|
||||||
>
|
|
||||||
${this.message}
|
|
||||||
</et2-dialog-content>
|
|
||||||
`;
|
|
||||||
}
|
}
|
||||||
|
if(this.height)
|
||||||
|
{
|
||||||
|
styles.height = "--height: " + this.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div class=${classMap(classes)} style="${styleMap(styles)}">
|
||||||
|
${this.__template ? "" :
|
||||||
|
html` <img class="dialog_icon" src=${icon}/>
|
||||||
|
<slot>${this.message}</slot>`
|
||||||
|
}
|
||||||
|
|
||||||
|
</div>`;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_getButtons()
|
_buttonsTemplate()
|
||||||
|
{
|
||||||
|
if(!this.buttons)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let buttons = this._getButtons();
|
||||||
|
let hasDefault = false;
|
||||||
|
buttons.forEach((button) =>
|
||||||
|
{
|
||||||
|
if(button.default)
|
||||||
|
{
|
||||||
|
hasDefault = true;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Set button._parent here, otherwise button will have trouble finding our egw()
|
||||||
|
return html`${repeat(buttons, (button : DialogButton) => button.id, (button, index) =>
|
||||||
|
{
|
||||||
|
let isDefault = hasDefault && button.default || !hasDefault && index == 0;
|
||||||
|
return html`
|
||||||
|
<et2-button ._parent=${this} id=${button.id} button_id=${button.button_id}
|
||||||
|
label=${button.label}
|
||||||
|
slot="footer"
|
||||||
|
.image=${ifDefined(button.image)}
|
||||||
|
.noSubmit=${true}
|
||||||
|
?disabled=${button.disabled}
|
||||||
|
variant=${isDefault ? "primary" : "default"}
|
||||||
|
?outline=${isDefault}
|
||||||
|
align=${ifDefined(button.align)}>
|
||||||
|
</et2-button>
|
||||||
|
`
|
||||||
|
})}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
_getButtons() : DialogButton[]
|
||||||
{
|
{
|
||||||
if(Number.isInteger(this.buttons))
|
if(Number.isInteger(this.buttons))
|
||||||
{
|
{
|
||||||
@ -733,7 +837,7 @@ export class Et2Dialog extends Et2Widget(ScopedElementsMixin(SlotMixin(LionDialo
|
|||||||
_adoptTemplateButtons()
|
_adoptTemplateButtons()
|
||||||
{
|
{
|
||||||
// Check for something with buttons slot set
|
// Check for something with buttons slot set
|
||||||
let search_in = this._template_widget?.DOMContainer || this._overlayContentNode._contentNode;
|
let search_in = <HTMLElement>(this._template_widget?.DOMContainer || this._contentNode);
|
||||||
let template_buttons = [
|
let template_buttons = [
|
||||||
...search_in.querySelectorAll('[slot="buttons"]'),
|
...search_in.querySelectorAll('[slot="buttons"]'),
|
||||||
// Look for a dialog footer, which will contain several buttons and possible other widgets
|
// Look for a dialog footer, which will contain several buttons and possible other widgets
|
||||||
@ -745,8 +849,8 @@ export class Et2Dialog extends Et2Widget(ScopedElementsMixin(SlotMixin(LionDialo
|
|||||||
{
|
{
|
||||||
template_buttons.forEach((button) =>
|
template_buttons.forEach((button) =>
|
||||||
{
|
{
|
||||||
button.setAttribute("slot", "buttons");
|
button.setAttribute("slot", "footer");
|
||||||
this._overlayContentNode.appendChild(button);
|
this.appendChild(button);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
// do NOT submit dialog, if it has no etemplate_exec_id, it only gives and error on server-side
|
// do NOT submit dialog, if it has no etemplate_exec_id, it only gives and error on server-side
|
||||||
@ -761,36 +865,56 @@ export class Et2Dialog extends Et2Widget(ScopedElementsMixin(SlotMixin(LionDialo
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @override Configures OverlayMixin
|
* Set autofocus on first input element if nothing has autofocus
|
||||||
* @desc overrides default configuration options for this component
|
|
||||||
* @returns {Object}
|
|
||||||
*/
|
*/
|
||||||
_defineOverlayConfig()
|
_setDefaultAutofocus()
|
||||||
{
|
{
|
||||||
let not_modal = {
|
const autofocused = this.querySelector("[autofocus]");
|
||||||
hasBackdrop: false,
|
if(autofocused)
|
||||||
preventsScroll: false,
|
{
|
||||||
trapsKeyboardFocus: false,
|
return;
|
||||||
}
|
}
|
||||||
return {
|
if(this.template)
|
||||||
...super._defineOverlayConfig(),
|
{
|
||||||
hidesOnEscape: this.hideOnEscape,
|
this.template.focusOnFirstInput();
|
||||||
...(this.modal ? {} : not_modal)
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Not a template, but maybe something?
|
||||||
|
const $input = jQuery('input:visible,et2-textbox:visible,et2-select-email:visible', this)
|
||||||
|
// Date fields open the calendar popup on focus
|
||||||
|
.not('.et2_date')
|
||||||
|
.filter(function()
|
||||||
|
{
|
||||||
|
// Skip inputs that are out of tab ordering
|
||||||
|
const $this = jQuery(this);
|
||||||
|
return !$this.attr('tabindex') || parseInt($this.attr('tabIndex')) >= 0;
|
||||||
|
}).first();
|
||||||
|
|
||||||
|
// mobile device, focus only if the field is empty (usually means new entry)
|
||||||
|
// should focus always for non-mobile one
|
||||||
|
if(egwIsMobile() && $input.val() == "" || !egwIsMobile())
|
||||||
|
{
|
||||||
|
$input.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_setupMoveResize(element)
|
get _contentNode() : HTMLElement
|
||||||
|
{
|
||||||
|
return this.querySelector('.dialog_content');
|
||||||
|
}
|
||||||
|
|
||||||
|
_setupMoveResize()
|
||||||
{
|
{
|
||||||
// Quick calculation of min size - dialog is made up of header, content & buttons
|
// Quick calculation of min size - dialog is made up of header, content & buttons
|
||||||
let minHeight = 0;
|
let minHeight = 0;
|
||||||
for(let e of element.shadowRoot.querySelector('.overlay').children)
|
for(let e of this.panel.children)
|
||||||
{
|
{
|
||||||
minHeight += e.getBoundingClientRect().height + parseFloat(getComputedStyle(e).marginTop) + parseFloat(getComputedStyle(e).marginBottom)
|
minHeight += e.getBoundingClientRect().height + parseFloat(getComputedStyle(e).marginTop) + parseFloat(getComputedStyle(e).marginBottom)
|
||||||
}
|
}
|
||||||
|
|
||||||
interact(element)
|
interact(this.panel)
|
||||||
.resizable({
|
.resizable({
|
||||||
edges: {bottom: true, right: true},
|
edges: {bottom: true, right: true},
|
||||||
listeners: {
|
listeners: {
|
||||||
@ -810,7 +934,8 @@ export class Et2Dialog extends Et2Widget(ScopedElementsMixin(SlotMixin(LionDialo
|
|||||||
})
|
})
|
||||||
|
|
||||||
.draggable({
|
.draggable({
|
||||||
allowFrom: ".overlay__heading",
|
allowFrom: ".dialog__header",
|
||||||
|
ignoreFrom: ".dialog__close",
|
||||||
listeners: {
|
listeners: {
|
||||||
move: this._onMoveResize
|
move: this._onMoveResize
|
||||||
},
|
},
|
||||||
|
@ -445,47 +445,50 @@ export function nm_open_popup(_action, _selected)
|
|||||||
var popup = document.body.querySelector("et2-dialog[id*='" + _action.id + "_popup']") || document.body.querySelector("#" + (uid || "") + "_" + _action.id + "_popup") || document.body.querySelector("[id*='" + _action.id + "_popup']");
|
var popup = document.body.querySelector("et2-dialog[id*='" + _action.id + "_popup']") || document.body.querySelector("#" + (uid || "") + "_" + _action.id + "_popup") || document.body.querySelector("[id*='" + _action.id + "_popup']");
|
||||||
if (popup && popup instanceof Et2Dialog)
|
if (popup && popup instanceof Et2Dialog)
|
||||||
{
|
{
|
||||||
popup.open();
|
popup.show();
|
||||||
}
|
}
|
||||||
else if (popup)
|
else if (popup)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
let dialog = new Et2Dialog();
|
let dialog = new Et2Dialog();
|
||||||
dialog.destroy_on_close = false;
|
dialog.destroyOnClose = false;
|
||||||
dialog.id = popup.id;
|
dialog.id = popup.id;
|
||||||
popup.setAttribute("id", "_" + popup.id);
|
popup.removeAttribute("id");
|
||||||
|
|
||||||
|
// Set title
|
||||||
|
let title = popup.querySelector(".promptheader")
|
||||||
|
if (title)
|
||||||
|
{
|
||||||
|
title.slot = "label"
|
||||||
|
dialog.appendChild(title);
|
||||||
|
}
|
||||||
|
popup.slot = "";
|
||||||
|
|
||||||
dialog.addEventListener("close", () =>
|
dialog.addEventListener("close", () =>
|
||||||
{
|
{
|
||||||
window.nm_popup_action = null;
|
window.nm_popup_action = null;
|
||||||
})
|
|
||||||
dialog.getUpdateComplete().then(() =>
|
|
||||||
{
|
|
||||||
let title = popup.querySelector(".promptheader")
|
|
||||||
if (title)
|
|
||||||
{
|
|
||||||
title.slot = "heading"
|
|
||||||
dialog.appendChild(title);
|
|
||||||
}
|
|
||||||
popup.slot = "content";
|
|
||||||
|
|
||||||
// Move buttons
|
|
||||||
popup.querySelectorAll('et2-button').forEach((button) =>
|
|
||||||
{
|
|
||||||
button.slot = "buttons";
|
|
||||||
let button_click = button.onclick;
|
|
||||||
button.onclick = (e) =>
|
|
||||||
{
|
|
||||||
window.nm_popup_action = button_click ? action : null;
|
|
||||||
window.nm_popup_ids = selected;
|
|
||||||
dialog.close();
|
|
||||||
|
|
||||||
return button_click?.apply(button, e.currentTarget);
|
|
||||||
};
|
|
||||||
dialog.appendChild(button);
|
|
||||||
})
|
|
||||||
dialog.appendChild(popup);
|
|
||||||
});
|
});
|
||||||
|
// Move buttons
|
||||||
|
popup.querySelectorAll('et2-button').forEach((button, index) =>
|
||||||
|
{
|
||||||
|
button.slot = "footer";
|
||||||
|
if (index == 0)
|
||||||
|
{
|
||||||
|
button.variant = "primary";
|
||||||
|
button.outline = true;
|
||||||
|
}
|
||||||
|
let button_click = button.onclick;
|
||||||
|
button.onclick = (e) =>
|
||||||
|
{
|
||||||
|
window.nm_popup_action = button_click ? action : null;
|
||||||
|
window.nm_popup_ids = selected;
|
||||||
|
dialog.hide();
|
||||||
|
|
||||||
|
return button_click?.apply(button, e.currentTarget);
|
||||||
|
};
|
||||||
|
dialog.appendChild(button);
|
||||||
|
})
|
||||||
|
dialog.appendChild(popup);
|
||||||
|
dialog.requestUpdate();
|
||||||
|
|
||||||
document.body.appendChild(dialog);
|
document.body.appendChild(dialog);
|
||||||
|
|
||||||
|
@ -88,25 +88,25 @@ export class et2_dialog extends Et2Dialog
|
|||||||
return super._getButtons();
|
return super._getButtons();
|
||||||
}
|
}
|
||||||
|
|
||||||
_onOpen()
|
handleOpen()
|
||||||
{
|
{
|
||||||
super._onOpen();
|
super.handleOpen();
|
||||||
|
|
||||||
// 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._cachedOverlayContentNode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_onClose(ev: PointerEvent)
|
handleClose(ev : PointerEvent)
|
||||||
{
|
{
|
||||||
// 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._cachedOverlayContentNode);
|
||||||
}
|
}
|
||||||
super._onClose(ev);
|
super.handleClose(ev);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -186,7 +186,7 @@ egw.extend('timer', egw.MODULE_GLOBAL, function()
|
|||||||
if (!dialog) return;
|
if (!dialog) return;
|
||||||
|
|
||||||
// disable not matching / available menu-items
|
// disable not matching / available menu-items
|
||||||
dialog._overlayContentNode.querySelectorAll('et2-button').forEach(button =>
|
dialog.querySelectorAll('et2-button').forEach(button =>
|
||||||
{
|
{
|
||||||
if (button.id.substring(0, 7) === 'overall')
|
if (button.id.substring(0, 7) === 'overall')
|
||||||
{
|
{
|
||||||
@ -261,10 +261,16 @@ egw.extend('timer', egw.MODULE_GLOBAL, function()
|
|||||||
// if dialog is open, it shows both timers
|
// if dialog is open, it shows both timers
|
||||||
if (dialog)
|
if (dialog)
|
||||||
{
|
{
|
||||||
const specific_timer = dialog._overlayContentNode.querySelector('div#_specific_timer');
|
const specific_timer = dialog.querySelector('div#_specific_timer');
|
||||||
const overall_timer = dialog._overlayContentNode.querySelector('div#_overall_timer');
|
const overall_timer = dialog.querySelector('div#_overall_timer');
|
||||||
if (specific_timer) updateTimer(specific_timer, specific);
|
if (specific_timer)
|
||||||
if (overall_timer) updateTimer(overall_timer, overall);
|
{
|
||||||
|
updateTimer(specific_timer, specific);
|
||||||
|
}
|
||||||
|
if (overall_timer)
|
||||||
|
{
|
||||||
|
updateTimer(overall_timer, overall);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -356,8 +362,8 @@ egw.extend('timer', egw.MODULE_GLOBAL, function()
|
|||||||
// if dialog is shown, update its timer(s) too
|
// if dialog is shown, update its timer(s) too
|
||||||
if (dialog)
|
if (dialog)
|
||||||
{
|
{
|
||||||
const specific_timer = dialog._overlayContentNode.querySelector('div#_specific_timer');
|
const specific_timer = dialog.querySelector('div#_specific_timer');
|
||||||
const overall_timer = dialog?._overlayContentNode.querySelector('div#_overall_timer');
|
const overall_timer = dialog?.querySelector('div#_overall_timer');
|
||||||
if (specific_timer && _timer === specific)
|
if (specific_timer && _timer === specific)
|
||||||
{
|
{
|
||||||
updateTimer(specific_timer, specific)
|
updateTimer(specific_timer, specific)
|
||||||
@ -489,8 +495,9 @@ egw.extend('timer', egw.MODULE_GLOBAL, function()
|
|||||||
};
|
};
|
||||||
|
|
||||||
// disable not matching / available menu-items
|
// disable not matching / available menu-items
|
||||||
dialog._overlayContentNode.querySelectorAll('et2-date-time-today').forEach(_widget => {
|
dialog.querySelectorAll('et2-date-time-today').forEach(_widget =>
|
||||||
const [,timer, action] = _widget.id.match(/times\[([^\]]+)\]\[([^\]]+)\]/);
|
{
|
||||||
|
const [, timer, action] = _widget.id.match(/times\[([^\]]+)\]\[([^\]]+)\]/);
|
||||||
_widget.value = times[timer][action];
|
_widget.value = times[timer][action];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -776,35 +776,18 @@ button.et2_timestamper:hover {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Dialog widget
|
* Dialog widget
|
||||||
* It uses jQueryUI, so this is just our little bits - icon on left
|
|
||||||
*/
|
*/
|
||||||
.ui-dialog-content .dialog_icon {
|
et2-dialog .dialog_icon {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
width: 2em;
|
width: 2em;
|
||||||
|
margin: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui-dialog-content {
|
et2-dialog .dialog--has_message {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui-dialog-content > div {
|
|
||||||
white-space: pre-wrap;
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* These change button alignment, but it seems the standard is right-aligned for
|
|
||||||
action buttons, left aligned for "extra" controls
|
|
||||||
.ui-dialog .ui-dialog-buttonpane {
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset {
|
|
||||||
float: none;
|
|
||||||
}
|
|
||||||
.ui-dialog .ui-dialog-buttonpane button {
|
|
||||||
float: none;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
/**
|
/**
|
||||||
* Custom field list
|
* Custom field list
|
||||||
*/
|
*/
|
||||||
|
Loading…
Reference in New Issue
Block a user