Skip to content

Commit

Permalink
Merge pull request #1507 from enkelmedia/1506-custom-modal
Browse files Browse the repository at this point in the history
Feature/Proposal: Added support for element factory for modal manager context
  • Loading branch information
nielslyngsoe authored Nov 7, 2024
2 parents 0616a43 + 3f5bc93 commit 2273af3
Show file tree
Hide file tree
Showing 11 changed files with 244 additions and 45 deletions.
46 changes: 46 additions & 0 deletions examples/custom-modal/example-custom-modal-dashboard.element.ts
Original file line number Diff line number Diff line change
@@ -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;
}
}
50 changes: 50 additions & 0 deletions examples/custom-modal/example-custom-modal-element.element.ts
Original file line number Diff line number Diff line change
@@ -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;
}
}
19 changes: 19 additions & 0 deletions examples/custom-modal/example-modal-token.ts
Original file line number Diff line number Diff line change
@@ -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'),
}
});
51 changes: 51 additions & 0 deletions examples/custom-modal/example-modal-view.element.ts
Original file line number Diff line number Diff line change
@@ -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 changes: 29 additions & 0 deletions examples/custom-modal/index.ts
Original file line number Diff line number Diff line change
@@ -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];
13 changes: 2 additions & 11 deletions examples/modal-routed/index.ts
Original file line number Diff line number Diff line change
@@ -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',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;

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

Expand Down
38 changes: 18 additions & 20 deletions src/packages/core/modal/component/modal.element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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);

Expand All @@ -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);
Expand Down Expand Up @@ -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();
}

Expand Down
10 changes: 8 additions & 2 deletions src/packages/core/modal/context/modal-manager.context.ts
Original file line number Diff line number Diff line change
@@ -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
*/
Expand Down
Loading

0 comments on commit 2273af3

Please sign in to comment.