fix missing close animation on modals + add confirm modal layout
This commit is contained in:
@@ -4,7 +4,7 @@ import { customElement, state } from 'lit/decorators.js';
|
||||
import { repeat } from 'lit/directives/repeat.js';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { UmbContextConsumerMixin } from '../../core/context';
|
||||
import { UmbModalService } from '../../core/services/modal';
|
||||
import { UmbModalHandler, UmbModalService } from '../../core/services/modal';
|
||||
|
||||
@customElement('umb-backoffice-modal-container')
|
||||
export class UmbBackofficeModalContainer extends UmbContextConsumerMixin(LitElement) {
|
||||
@@ -18,7 +18,7 @@ export class UmbBackofficeModalContainer extends UmbContextConsumerMixin(LitElem
|
||||
];
|
||||
|
||||
@state()
|
||||
private _modals: any[] = [];
|
||||
private _modals: UmbModalHandler[] = [];
|
||||
|
||||
private _modalService?: UmbModalService;
|
||||
private _modalSubscription?: Subscription;
|
||||
@@ -29,9 +29,8 @@ export class UmbBackofficeModalContainer extends UmbContextConsumerMixin(LitElem
|
||||
this.consumeContext('umbModalService', (modalService: UmbModalService) => {
|
||||
this._modalService = modalService;
|
||||
this._modalSubscription?.unsubscribe();
|
||||
this._modalService?.modals.subscribe((modals: Array<any>) => {
|
||||
this._modalService?.modals.subscribe((modals: Array<UmbModalHandler>) => {
|
||||
this._modals = modals;
|
||||
console.log('modals', modals);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -43,7 +42,9 @@ export class UmbBackofficeModalContainer extends UmbContextConsumerMixin(LitElem
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<uui-modal-container> ${repeat(this._modals, (modal) => html`${modal.modal}`)} </uui-modal-container>
|
||||
<uui-modal-container>
|
||||
${repeat(this._modals, (modalHandler) => html`${modalHandler.element}`)})})}
|
||||
</uui-modal-container>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,14 +2,14 @@ import { css, html, LitElement } from 'lit';
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { customElement, state } from 'lit/decorators.js';
|
||||
|
||||
import { UmbContextConsumerMixin } from '../../core/context';
|
||||
import { UmbModalService } from '../../core/services/modal';
|
||||
|
||||
// TODO: remove these imports when they are part of UUI
|
||||
import '@umbraco-ui/uui-modal';
|
||||
import '@umbraco-ui/uui-modal-sidebar';
|
||||
import '@umbraco-ui/uui-modal-container';
|
||||
import '@umbraco-ui/uui-modal-dialog';
|
||||
import { UmbContextConsumerMixin } from '../../core/context';
|
||||
import { UmbModalService } from '../../core/services/modal';
|
||||
|
||||
import './modal-content-picker.element';
|
||||
|
||||
@customElement('umb-property-editor-content-picker')
|
||||
export class UmbPropertyEditorContentPicker extends UmbContextConsumerMixin(LitElement) {
|
||||
@@ -50,23 +50,34 @@ export class UmbPropertyEditorContentPicker extends UmbContextConsumerMixin(LitE
|
||||
}
|
||||
|
||||
private _open() {
|
||||
const modalHandler = this._modalService?.openSidebar('umb-modal-content-picker', { size: 'small' });
|
||||
modalHandler?.onClose.then((result) => {
|
||||
this._selectedContent = [...this._selectedContent, ...result];
|
||||
const modalHandler = this._modalService?.contentPicker({ multiple: true });
|
||||
modalHandler?.onClose.then(({ selection }: any) => {
|
||||
this._selectedContent = [...this._selectedContent, ...selection];
|
||||
this.requestUpdate('_selectedContent');
|
||||
});
|
||||
}
|
||||
|
||||
private _removeContent(index: number) {
|
||||
this._selectedContent.splice(index, 1);
|
||||
this.requestUpdate('_selectedContent');
|
||||
private _removeContent(index: number, content: any) {
|
||||
const modalHandler = this._modalService?.confirm({
|
||||
color: 'danger',
|
||||
headline: 'Remove',
|
||||
confirmLabel: 'Remove',
|
||||
content: html`Remove <strong>${content.name}</strong>?`,
|
||||
});
|
||||
|
||||
modalHandler?.onClose.then(({ confirmed }) => {
|
||||
if (confirmed) {
|
||||
this._selectedContent.splice(index, 1);
|
||||
this.requestUpdate('_selectedContent');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _renderContent(content: any, index: number) {
|
||||
return html`
|
||||
<uui-ref-node name=${content.name} detail=${content.id}>
|
||||
<uui-action-bar slot="actions">
|
||||
<uui-button @click=${() => this._removeContent(index)}>Remove</uui-button>
|
||||
<uui-button @click=${() => this._removeContent(index, content)}>Remove</uui-button>
|
||||
</uui-action-bar>
|
||||
</uui-ref-node>
|
||||
`;
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import { html, LitElement, TemplateResult } from 'lit';
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { customElement, property } from 'lit/decorators.js';
|
||||
import { UmbModalHandler } from '../../../modal';
|
||||
|
||||
export interface UmbModalConfirmData {
|
||||
headline: string;
|
||||
content: TemplateResult | string;
|
||||
color?: 'positive' | 'danger';
|
||||
confirmLabel?: string;
|
||||
}
|
||||
|
||||
@customElement('umb-modal-layout-confirm')
|
||||
export class UmbModelLayoutConfirmElement extends LitElement {
|
||||
static styles = [UUITextStyles];
|
||||
|
||||
@property({ attribute: false })
|
||||
modalHandler!: UmbModalHandler;
|
||||
|
||||
@property({ type: Object })
|
||||
data!: UmbModalConfirmData;
|
||||
|
||||
private _handleConfirm() {
|
||||
this.modalHandler.close({ confirmed: true });
|
||||
}
|
||||
|
||||
private _handleCancel() {
|
||||
this.modalHandler.close({ confirmed: false });
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<uui-dialog-layout class="uui-text" .headline=${this.data?.headline}>
|
||||
${this.data?.content}
|
||||
|
||||
<uui-button slot="actions" id="cancel" label="Cancel" @click="${this._handleCancel}">Cancel</uui-button>
|
||||
<uui-button
|
||||
slot="actions"
|
||||
id="confirm"
|
||||
color="${this.data?.color || 'positive'}"
|
||||
look="primary"
|
||||
label="${this.data?.confirmLabel || 'Confirm'}"
|
||||
@click=${this._handleConfirm}></uui-button>
|
||||
</uui-dialog-layout>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-modal-layout-confirm': UmbModelLayoutConfirmElement;
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,14 @@
|
||||
import { css, html, LitElement } from 'lit';
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { customElement, property, state } from 'lit/decorators.js';
|
||||
import { UmbModalHandler } from '../../core/services/modal';
|
||||
import { UmbModalHandler } from '../../../modal';
|
||||
|
||||
@customElement('umb-modal-content-picker')
|
||||
class UmbModalContentPicker extends LitElement {
|
||||
export interface UmbModalContentPickerData {
|
||||
multiple: boolean;
|
||||
}
|
||||
|
||||
@customElement('umb-modal-layout-content-picker')
|
||||
export class UmbModalContentPickerElement extends LitElement {
|
||||
static styles = [
|
||||
UUITextStyles,
|
||||
css`
|
||||
@@ -78,7 +82,7 @@ class UmbModalContentPicker extends LitElement {
|
||||
}
|
||||
|
||||
private _submit() {
|
||||
this.modalHandler?.close(this._selectedContent);
|
||||
this.modalHandler?.close({ selection: this._selectedContent });
|
||||
}
|
||||
|
||||
private _close() {
|
||||
@@ -115,6 +119,6 @@ class UmbModalContentPicker extends LitElement {
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-modal-content-picker': UmbModalContentPicker;
|
||||
'umb-modal-layout-content-picker': UmbModalContentPickerElement;
|
||||
}
|
||||
}
|
||||
@@ -1,42 +1,63 @@
|
||||
import { html, render } from 'lit';
|
||||
import { UUIDialogElement } from '@umbraco-ui/uui';
|
||||
import { UUIModalDialogElement } from '@umbraco-ui/uui-modal-dialog';
|
||||
import { UUIModalSidebarElement, UUIModalSidebarSize } from '@umbraco-ui/uui-modal-sidebar';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { UmbModalOptions } from './modal.service';
|
||||
|
||||
//TODO consider splitting this into two separate handlers
|
||||
export class UmbModalHandler {
|
||||
private _closeResolver: any;
|
||||
private _closePromise: any;
|
||||
|
||||
public element?: any;
|
||||
public element: UUIModalDialogElement | UUIModalSidebarElement;
|
||||
public key: string;
|
||||
public modal: any;
|
||||
public type: string;
|
||||
public size: UUIModalSidebarSize;
|
||||
|
||||
constructor(elementName: string, options: UmbModalOptions<unknown>) {
|
||||
this.key = uuidv4();
|
||||
|
||||
this.type = options.type || 'dialog';
|
||||
this.size = options.size || 'small';
|
||||
this.element = this._createElement(elementName, options);
|
||||
|
||||
constructor(elementName: string, modalElementName: string, modalOptions?: any) {
|
||||
this.key = Date.now().toString(); //TODO better key
|
||||
this._createLayoutElement(elementName, modalElementName, modalOptions);
|
||||
this._closePromise = new Promise((resolve) => {
|
||||
this._closeResolver = resolve;
|
||||
});
|
||||
}
|
||||
|
||||
private _createLayoutElement(elementName: string, modalElementName: string, modalOptions?: any) {
|
||||
this.modal = document.createElement(modalElementName);
|
||||
this.modal.addEventListener('close-end', () => {
|
||||
this._closeResolver();
|
||||
});
|
||||
private _createElement(elementName: string, options: UmbModalOptions<unknown>) {
|
||||
const layoutElement = this._createLayoutElement(elementName, options);
|
||||
return options.type === 'sidebar'
|
||||
? this._createSidebarElement(layoutElement)
|
||||
: this._createDialogElement(layoutElement);
|
||||
}
|
||||
|
||||
if (modalOptions) {
|
||||
// Apply modal options as attributes on the modal
|
||||
Object.keys(modalOptions).forEach((option) => {
|
||||
this.modal.setAttribute(option, modalOptions[option]);
|
||||
});
|
||||
}
|
||||
private _createSidebarElement(layoutElement: HTMLElement) {
|
||||
const sidebarElement = document.createElement('uui-modal-sidebar');
|
||||
sidebarElement.appendChild(layoutElement);
|
||||
sidebarElement.size = this.size;
|
||||
return sidebarElement;
|
||||
}
|
||||
|
||||
this.element = document.createElement(elementName);
|
||||
this.modal.appendChild(this.element);
|
||||
this.element.modalHandler = this;
|
||||
private _createDialogElement(layoutElement: HTMLElement) {
|
||||
const modalDialogElement = document.createElement('uui-modal-dialog');
|
||||
const dialogElement: UUIDialogElement = document.createElement('uui-dialog');
|
||||
modalDialogElement.appendChild(dialogElement);
|
||||
dialogElement.appendChild(layoutElement);
|
||||
return modalDialogElement;
|
||||
}
|
||||
|
||||
private _createLayoutElement(elementName: string, options: UmbModalOptions<unknown>) {
|
||||
const layoutElement: any = document.createElement(elementName);
|
||||
layoutElement.data = options.data;
|
||||
layoutElement.modalHandler = this;
|
||||
return layoutElement;
|
||||
}
|
||||
|
||||
public close(...args: any) {
|
||||
this._closeResolver(...args);
|
||||
this.element.close();
|
||||
}
|
||||
|
||||
public get onClose(): Promise<any> {
|
||||
|
||||
@@ -1,26 +1,48 @@
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
import { UmbModalHandler } from './';
|
||||
import { UmbModalConfirmData } from './layouts/confirm/modal-layout-confirm.element';
|
||||
import { UmbModalContentPickerData } from './layouts/content-picker/modal-layout-content-picker.element';
|
||||
import { UUIModalSidebarSize } from '@umbraco-ui/uui-modal-sidebar';
|
||||
|
||||
// TODO: lazy load
|
||||
import './layouts/confirm/modal-layout-confirm.element';
|
||||
import './layouts/content-picker/modal-layout-content-picker.element';
|
||||
|
||||
export type UmbModelType = 'dialog' | 'sidebar';
|
||||
|
||||
export interface UmbModalOptions<UmbModalData> {
|
||||
type?: UmbModelType;
|
||||
size?: UUIModalSidebarSize;
|
||||
data: UmbModalData;
|
||||
}
|
||||
|
||||
export class UmbModalService {
|
||||
private _modals: BehaviorSubject<Array<UmbModalHandler>> = new BehaviorSubject(<Array<UmbModalHandler>>[]);
|
||||
public readonly modals: Observable<Array<UmbModalHandler>> = this._modals.asObservable();
|
||||
|
||||
public openSidebar(elementName: string, modalOptions?: any): UmbModalHandler {
|
||||
return this._open(elementName, 'uui-modal-sidebar', modalOptions);
|
||||
public confirm(data: UmbModalConfirmData): UmbModalHandler {
|
||||
return this.open('umb-modal-layout-confirm', { data, type: 'dialog' });
|
||||
}
|
||||
|
||||
public openDialog(elementName: string, modalOptions?: any): UmbModalHandler {
|
||||
return this._open(elementName, 'uui-modal-dialog', modalOptions);
|
||||
public contentPicker(data: UmbModalContentPickerData): UmbModalHandler {
|
||||
return this.open('umb-modal-layout-content-picker', { data, type: 'sidebar', size: 'small' });
|
||||
}
|
||||
|
||||
private _open(elementName: string, modalElementName: string, modalOptions?: any): UmbModalHandler {
|
||||
const modalHandler = new UmbModalHandler(elementName, modalElementName, modalOptions);
|
||||
modalHandler.onClose.then(() => this._close(modalHandler));
|
||||
public open(elementName: string, options: UmbModalOptions<unknown>): UmbModalHandler {
|
||||
const modalHandler = new UmbModalHandler(elementName, options);
|
||||
|
||||
modalHandler.element.addEventListener('close-end', () => this._handleCloseEnd(modalHandler));
|
||||
|
||||
this._modals.next([...this._modals.getValue(), modalHandler]);
|
||||
return modalHandler;
|
||||
}
|
||||
|
||||
private _close(modalHandler: UmbModalHandler) {
|
||||
this._modals.next(this._modals.getValue().filter((modal) => modal.key !== modalHandler.key));
|
||||
private _close(key: string) {
|
||||
this._modals.next(this._modals.getValue().filter((modal) => modal.key !== key));
|
||||
}
|
||||
|
||||
private _handleCloseEnd(modalHandler: UmbModalHandler) {
|
||||
modalHandler.element.removeEventListener('close-end', () => this._handleCloseEnd(modalHandler));
|
||||
this._close(modalHandler.key);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user