Merge pull request #1507 from enkelmedia/1506-custom-modal
Feature/Proposal: Added support for element factory for modal manager context
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
import { EXAMPLE_MODAL_TOKEN, type ExampleModalData, type ExampleModalResult } from './example-modal-token.js';
|
||||
import { css, html, customElement } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
|
||||
import './example-custom-modal-element.element.js';
|
||||
|
||||
@customElement('example-custom-modal-dashboard')
|
||||
export class UmbExampleCustomModalDashboardElement extends UmbLitElement {
|
||||
|
||||
#modalManagerContext? : typeof UMB_MODAL_MANAGER_CONTEXT.TYPE;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.consumeContext(UMB_MODAL_MANAGER_CONTEXT,(instance)=>{
|
||||
this.#modalManagerContext = instance;
|
||||
})
|
||||
}
|
||||
|
||||
#onOpenModal(){
|
||||
this.#modalManagerContext?.open(this,EXAMPLE_MODAL_TOKEN,{})
|
||||
}
|
||||
|
||||
override render() {
|
||||
return html`
|
||||
<uui-box>
|
||||
<p>Open the custom modal</p>
|
||||
<uui-button look="primary" @click=${this.#onOpenModal}>Open Modal</uui-button>
|
||||
</uui-box>
|
||||
`;
|
||||
}
|
||||
|
||||
static override styles = [css`
|
||||
:host{
|
||||
display:block;
|
||||
padding:20px;
|
||||
}
|
||||
`];
|
||||
}
|
||||
|
||||
export default UmbExampleCustomModalDashboardElement
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'example-custom-modal-dashboard': UmbExampleCustomModalDashboardElement;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import { css, html } from "@umbraco-cms/backoffice/external/lit";
|
||||
import { defineElement, UUIModalElement } from "@umbraco-cms/backoffice/external/uui";
|
||||
|
||||
/**
|
||||
* This class defines a custom design for the modal it self, in the same was as
|
||||
* UUIModalSidebarElement and UUIModalDialogElement.
|
||||
*/
|
||||
@defineElement('example-modal-element')
|
||||
export class UmbExampleCustomModalElement extends UUIModalElement {
|
||||
override render() {
|
||||
return html`
|
||||
<dialog>
|
||||
<h2>Custom Modal-wrapper</h2>
|
||||
<slot></slot>
|
||||
</dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
static override styles = [
|
||||
...UUIModalElement.styles,
|
||||
css`
|
||||
dialog {
|
||||
width:100%;
|
||||
height:100%;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
top:0;
|
||||
left:0;
|
||||
right:0;
|
||||
bottom:0;
|
||||
background:#fff;
|
||||
}
|
||||
:host([index='0']) dialog {
|
||||
box-shadow: var(--uui-shadow-depth-5);
|
||||
}
|
||||
:host(:not([index='0'])) dialog {
|
||||
outline: 1px solid rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
export default UmbExampleCustomModalElement;
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'example-modal-element': UmbExampleCustomModalElement;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { UmbModalToken } from "@umbraco-cms/backoffice/modal";
|
||||
|
||||
export interface ExampleModalData {
|
||||
unique: string | null;
|
||||
}
|
||||
|
||||
export interface ExampleModalResult {
|
||||
text : string;
|
||||
}
|
||||
|
||||
export const EXAMPLE_MODAL_TOKEN = new UmbModalToken<
|
||||
ExampleModalData,
|
||||
ExampleModalResult
|
||||
>('example.modal.custom.element', {
|
||||
modal : {
|
||||
type : 'custom',
|
||||
element: () => import('./example-custom-modal-element.element.js'),
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,51 @@
|
||||
import type { ExampleModalData, ExampleModalResult } from './example-modal-token.js';
|
||||
import { css, html, customElement, property } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import type { UmbModalContext } from '@umbraco-cms/backoffice/modal';
|
||||
import './example-custom-modal-element.element.js';
|
||||
|
||||
@customElement('example-modal-view')
|
||||
export class UmbExampleModalViewElement extends UmbLitElement {
|
||||
|
||||
@property({ attribute: false })
|
||||
public modalContext?: UmbModalContext<ExampleModalData, ExampleModalResult>;
|
||||
|
||||
onClickDone(){
|
||||
this.modalContext?.submit();
|
||||
}
|
||||
|
||||
override render() {
|
||||
return html`
|
||||
<div id="modal">
|
||||
<p>Example content of custom modal element</p>
|
||||
<uui-button look="primary" label="Submit modal" @click=${() => this.onClickDone()}></uui-button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
static override styles = [css`
|
||||
:host {
|
||||
background: #eaeaea;
|
||||
display: block;
|
||||
box-sizing:border-box;
|
||||
}
|
||||
|
||||
#modal {
|
||||
box-sizing:border-box;
|
||||
}
|
||||
|
||||
p {
|
||||
margin:0;
|
||||
padding:0;
|
||||
}
|
||||
|
||||
`];
|
||||
}
|
||||
|
||||
export default UmbExampleModalViewElement
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'example-modal-view': UmbExampleModalViewElement;
|
||||
}
|
||||
}
|
||||
29
src/Umbraco.Web.UI.Client/examples/custom-modal/index.ts
Normal file
29
src/Umbraco.Web.UI.Client/examples/custom-modal/index.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import type { ManifestDashboard } from '@umbraco-cms/backoffice/dashboard';
|
||||
import type { ManifestModal } from '@umbraco-cms/backoffice/modal';
|
||||
|
||||
const demoModal : ManifestModal = {
|
||||
type: 'modal',
|
||||
name: 'Example Custom Modal Element',
|
||||
alias: 'example.modal.custom.element',
|
||||
js: () => import('./example-modal-view.element.js'),
|
||||
}
|
||||
|
||||
const demoModalsDashboard : ManifestDashboard = {
|
||||
type: 'dashboard',
|
||||
name: 'Example Custom Modal Dashboard',
|
||||
alias: 'example.dashboard.custom.modal.element',
|
||||
element: () => import('./example-custom-modal-dashboard.element.js'),
|
||||
weight: 900,
|
||||
meta: {
|
||||
label: 'Custom Modal',
|
||||
pathname: 'custom-modal',
|
||||
},
|
||||
conditions : [
|
||||
{
|
||||
alias: 'Umb.Condition.SectionAlias',
|
||||
match: 'Umb.Section.Content'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export default [demoModal,demoModalsDashboard];
|
||||
@@ -1,14 +1,5 @@
|
||||
import type { ManifestDashboard, ManifestModal } from '@umbraco-cms/backoffice/extension-registry';
|
||||
|
||||
// const section : ManifestSection = {
|
||||
// type: "section",
|
||||
// alias: 'demo.section',
|
||||
// name: "Demo Section",
|
||||
// meta: {
|
||||
// label: "Demo",
|
||||
// pathname: "demo"
|
||||
// }
|
||||
// }
|
||||
import type { ManifestDashboard } from '@umbraco-cms/backoffice/dashboard';
|
||||
import type { ManifestModal } from '@umbraco-cms/backoffice/modal';
|
||||
|
||||
const dashboard: ManifestDashboard = {
|
||||
type: 'dashboard',
|
||||
|
||||
@@ -13,7 +13,7 @@ export class UmbBackofficeModalContainerElement extends UmbLitElement {
|
||||
@state()
|
||||
_modals: Array<UmbModalContext> = [];
|
||||
|
||||
@property({ reflect: true, attribute: 'fill-background' })
|
||||
@property({ type: Boolean, reflect: true, attribute: 'fill-background' })
|
||||
fillBackground = false;
|
||||
|
||||
private _modalManager?: UmbModalManagerContext;
|
||||
@@ -41,7 +41,7 @@ export class UmbBackofficeModalContainerElement extends UmbLitElement {
|
||||
* @param modals
|
||||
*/
|
||||
#createModalElements(modals: Array<UmbModalContext>) {
|
||||
this.removeAttribute('fill-background');
|
||||
this.fillBackground = false;
|
||||
const oldValue = this._modals;
|
||||
this._modals = modals;
|
||||
|
||||
@@ -58,26 +58,26 @@ export class UmbBackofficeModalContainerElement extends UmbLitElement {
|
||||
return;
|
||||
}
|
||||
|
||||
this._modals.forEach((modal) => {
|
||||
if (this._modalElementMap.has(modal.key)) return;
|
||||
this._modals.forEach(async (modalContext) => {
|
||||
if (this._modalElementMap.has(modalContext.key)) return;
|
||||
|
||||
const modalElement = new UmbModalElement();
|
||||
modalElement.modalContext = modal;
|
||||
await modalElement.init(modalContext);
|
||||
|
||||
modalElement.element?.addEventListener('close-end', this.#onCloseEnd.bind(this, modal.key));
|
||||
modal.addEventListener('umb:destroy', this.#onCloseEnd.bind(this, modal.key));
|
||||
modalElement.element?.addEventListener('close-end', this.#onCloseEnd.bind(this, modalContext.key));
|
||||
modalContext.addEventListener('umb:destroy', this.#onCloseEnd.bind(this, modalContext.key));
|
||||
|
||||
this._modalElementMap.set(modal.key, modalElement);
|
||||
this._modalElementMap.set(modalContext.key, modalElement);
|
||||
|
||||
// If any of the modals are fillBackground, set the fillBackground property to true
|
||||
if (modal.backdropBackground) {
|
||||
if (modalContext.backdropBackground) {
|
||||
this.fillBackground = true;
|
||||
this.shadowRoot
|
||||
?.getElementById('container')
|
||||
?.style.setProperty('--backdrop-background', modal.backdropBackground);
|
||||
?.style.setProperty('--backdrop-background', modalContext.backdropBackground);
|
||||
}
|
||||
|
||||
this.requestUpdate();
|
||||
this.requestUpdate('_modalElementMap');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -9,12 +9,13 @@ import { html, customElement } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbBasicState, type UmbObserverController } from '@umbraco-cms/backoffice/observable-api';
|
||||
import {
|
||||
UUIModalCloseEvent,
|
||||
type UUIModalElement,
|
||||
type UUIDialogElement,
|
||||
type UUIModalDialogElement,
|
||||
type UUIModalSidebarElement,
|
||||
} from '@umbraco-cms/backoffice/external/uui';
|
||||
import { UMB_ROUTE_CONTEXT, type UmbRouterSlotElement } from '@umbraco-cms/backoffice/router';
|
||||
import { createExtensionElement } from '@umbraco-cms/backoffice/extension-api';
|
||||
import { createExtensionElement, loadManifestElement } from '@umbraco-cms/backoffice/extension-api';
|
||||
import type { UmbContextRequestEvent } from '@umbraco-cms/backoffice/context-api';
|
||||
import {
|
||||
UMB_CONTEXT_REQUEST_EVENT_TYPE,
|
||||
@@ -25,22 +26,8 @@ import {
|
||||
@customElement('umb-modal')
|
||||
export class UmbModalElement extends UmbLitElement {
|
||||
#modalContext?: UmbModalContext;
|
||||
public get modalContext(): UmbModalContext | undefined {
|
||||
return this.#modalContext;
|
||||
}
|
||||
public set modalContext(value: UmbModalContext | undefined) {
|
||||
if (this.#modalContext === value) return;
|
||||
this.#modalContext = value;
|
||||
|
||||
if (!value) {
|
||||
this.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
this.#createModalElement();
|
||||
}
|
||||
|
||||
public element?: UUIModalDialogElement | UUIModalSidebarElement;
|
||||
public element?: UUIModalDialogElement | UUIModalSidebarElement | UUIModalElement;
|
||||
|
||||
#innerElement = new UmbBasicState<HTMLElement | undefined>(undefined);
|
||||
|
||||
@@ -52,11 +39,17 @@ export class UmbModalElement extends UmbLitElement {
|
||||
this.#modalContext?.reject({ type: 'close' });
|
||||
};
|
||||
|
||||
#createModalElement() {
|
||||
if (!this.#modalContext) return;
|
||||
async init(modalContext: UmbModalContext | undefined) {
|
||||
if (this.#modalContext === modalContext) return;
|
||||
this.#modalContext = modalContext;
|
||||
|
||||
if (!this.#modalContext) {
|
||||
this.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
this.#modalContext.addEventListener('umb:destroy', this.#onContextDestroy);
|
||||
this.element = this.#createContainerElement();
|
||||
this.element = await this.#createContainerElement();
|
||||
|
||||
// Makes sure that the modal triggers the reject of the context promise when it is closed by pressing escape.
|
||||
this.element.addEventListener(UUIModalCloseEvent, this.#onClose);
|
||||
@@ -113,7 +106,12 @@ export class UmbModalElement extends UmbLitElement {
|
||||
provider.hostConnected();
|
||||
}
|
||||
|
||||
#createContainerElement() {
|
||||
async #createContainerElement() {
|
||||
if (this.#modalContext!.type == 'custom' && this.#modalContext?.element) {
|
||||
const customWrapperElementCtor = await loadManifestElement(this.#modalContext.element);
|
||||
return new customWrapperElementCtor!();
|
||||
}
|
||||
|
||||
return this.#modalContext!.type === 'sidebar' ? this.#createSidebarElement() : this.#createDialogElement();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,18 +1,24 @@
|
||||
import type { UmbModalToken } from '../token/modal-token.js';
|
||||
import { UmbModalContext, type UmbModalContextClassArgs } from './modal.context.js';
|
||||
import type { UUIModalSidebarSize } from '@umbraco-cms/backoffice/external/uui';
|
||||
import type { UUIModalElement, UUIModalSidebarSize } from '@umbraco-cms/backoffice/external/uui';
|
||||
import { UmbBasicState, appendToFrozenArray } from '@umbraco-cms/backoffice/observable-api';
|
||||
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
|
||||
import { UmbContextBase } from '@umbraco-cms/backoffice/class-api';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
import type { ElementLoaderProperty } from '@umbraco-cms/backoffice/extension-api';
|
||||
|
||||
export type UmbModalType = 'dialog' | 'sidebar';
|
||||
export type UmbModalType = 'dialog' | 'sidebar' | 'custom';
|
||||
|
||||
export interface UmbModalConfig {
|
||||
key?: string;
|
||||
type?: UmbModalType;
|
||||
size?: UUIModalSidebarSize;
|
||||
|
||||
/**
|
||||
* Used to provide a custom modal element to replace the default uui-modal-dialog or uui-modal-sidebar
|
||||
*/
|
||||
element?: ElementLoaderProperty<UUIModalElement>;
|
||||
|
||||
/**
|
||||
* Set the background property of the modal backdrop
|
||||
*/
|
||||
|
||||
@@ -2,11 +2,12 @@ import { UmbModalToken } from '../token/modal-token.js';
|
||||
import type { UmbModalConfig, UmbModalType } from './modal-manager.context.js';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
import type { IRouterSlot } from '@umbraco-cms/backoffice/external/router-slot';
|
||||
import type { UUIModalSidebarSize } from '@umbraco-cms/backoffice/external/uui';
|
||||
import type { UUIModalElement, UUIModalSidebarSize } from '@umbraco-cms/backoffice/external/uui';
|
||||
import { UmbId } from '@umbraco-cms/backoffice/id';
|
||||
import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api';
|
||||
import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
|
||||
import { type UmbDeepPartialObject, umbDeepMerge } from '@umbraco-cms/backoffice/utils';
|
||||
import { type ElementLoaderProperty } from '@umbraco-cms/backoffice/extension-api';
|
||||
|
||||
export interface UmbModalRejectReason {
|
||||
type: string;
|
||||
@@ -38,6 +39,7 @@ export class UmbModalContext<
|
||||
public readonly data: ModalData;
|
||||
public readonly type: UmbModalType = 'dialog';
|
||||
public readonly size: UUIModalSidebarSize = 'small';
|
||||
public element?: ElementLoaderProperty<UUIModalElement>;
|
||||
public readonly backdropBackground?: string;
|
||||
public readonly router: IRouterSlot | null = null;
|
||||
public readonly alias: string | UmbModalToken<ModalData, ModalValue>;
|
||||
@@ -58,11 +60,13 @@ export class UmbModalContext<
|
||||
if (this.alias instanceof UmbModalToken) {
|
||||
this.type = this.alias.getDefaultModal()?.type || this.type;
|
||||
this.size = this.alias.getDefaultModal()?.size || this.size;
|
||||
this.element = this.alias.getDefaultModal()?.element || this.element;
|
||||
this.backdropBackground = this.alias.getDefaultModal()?.backdropBackground || this.backdropBackground;
|
||||
}
|
||||
|
||||
this.type = args.modal?.type || this.type;
|
||||
this.size = args.modal?.size || this.size;
|
||||
this.element = args.modal?.element || this.element;
|
||||
this.backdropBackground = args.modal?.backdropBackground || this.backdropBackground;
|
||||
|
||||
const defaultData = this.alias instanceof UmbModalToken ? this.alias.getDefaultData() : undefined;
|
||||
|
||||
@@ -150,6 +150,11 @@ export class UmbRouteContext extends UmbContextBase<UmbRouteContext> {
|
||||
|
||||
modalRegistration._internal_setRouteBuilder(urlBuilder);
|
||||
};
|
||||
|
||||
override hostDisconnected(): void {
|
||||
super.hostDisconnected();
|
||||
this._internal_modalRouterChanged(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
export const UMB_ROUTE_CONTEXT = new UmbContextToken<UmbRouteContext>('UmbRouterContext');
|
||||
|
||||
Reference in New Issue
Block a user