From 02ed092697f1f99bfbf4027866deec330e6ead1c Mon Sep 17 00:00:00 2001 From: Markus Johansson Date: Sun, 31 Mar 2024 03:52:22 +0200 Subject: [PATCH 1/7] #1506 Added support for element factory for modal manager context --- .../example-custom-modal-element.element.ts | 41 ++++++++++++++ examples/custom-modal/example-modal-token.ts | 28 ++++++++++ .../example-modal-view.element.ts | 53 +++++++++++++++++++ examples/custom-modal/index.ts | 11 ++++ .../core/modal/component/modal.element.ts | 6 ++- .../modal/context/modal-manager.context.ts | 7 ++- .../core/modal/context/modal.context.ts | 7 ++- 7 files changed, 148 insertions(+), 5 deletions(-) create mode 100644 examples/custom-modal/example-custom-modal-element.element.ts create mode 100644 examples/custom-modal/example-modal-token.ts create mode 100644 examples/custom-modal/example-modal-view.element.ts create mode 100644 examples/custom-modal/index.ts diff --git a/examples/custom-modal/example-custom-modal-element.element.ts b/examples/custom-modal/example-custom-modal-element.element.ts new file mode 100644 index 0000000000..f4f7fd9ac2 --- /dev/null +++ b/examples/custom-modal/example-custom-modal-element.element.ts @@ -0,0 +1,41 @@ +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 { + render() { + return html` + + + + `; + } + + static styles = [ + ...UUIModalElement.styles, + css` + dialog { + width:100%; + height:100%; + max-width: 100%; + max-height: 100%; + } + :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); + } + `, + ]; +} + +declare global { + interface HTMLElementTagNameMap { + 'example-modal-element': UmbExampleCustomModalElement; + } +} diff --git a/examples/custom-modal/example-modal-token.ts b/examples/custom-modal/example-modal-token.ts new file mode 100644 index 0000000000..14ebfe5273 --- /dev/null +++ b/examples/custom-modal/example-modal-token.ts @@ -0,0 +1,28 @@ +import type { UUIDialogElement } from "@umbraco-cms/backoffice/external/uui"; +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', + elementFactory : ()=> { + // returning the custom modal element + const modalDialogElement = document.createElement('example-modal-element'); + const dialogElement: UUIDialogElement = document.createElement('uui-dialog'); + modalDialogElement.appendChild(dialogElement); + return modalDialogElement; + } + } + +}); diff --git a/examples/custom-modal/example-modal-view.element.ts b/examples/custom-modal/example-modal-view.element.ts new file mode 100644 index 0000000000..d29e894cd0 --- /dev/null +++ b/examples/custom-modal/example-modal-view.element.ts @@ -0,0 +1,53 @@ +import type { DemoModalData, DemoModalResult } 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; + + onClickDone(){ + this.modalContext?.submit(); + } + + render() { + return html` + + `; + } + + static styles = [ + css` + :host { + background: #eaeaea; + display: block; + box-sizing:border-box; + } + + #modal { + box-sizing:border-box; + } + + p { + margin:0; + padding:0; + } + + `, + ]; +} + +export default UmbPocModalViewElement + +declare global { + interface HTMLElementTagNameMap { + 'example-modal-view': UmbExampleModalViewElement; + } +} \ No newline at end of file diff --git a/examples/custom-modal/index.ts b/examples/custom-modal/index.ts new file mode 100644 index 0000000000..045d644e68 --- /dev/null +++ b/examples/custom-modal/index.ts @@ -0,0 +1,11 @@ +import type { ManifestModal } from '@umbraco-cms/backoffice/extension-registry'; + +export const demoModals: Array = [ + { + type: 'modal', + name: 'Example Custom Modal Element', + alias: 'example.modal.custom.element', + js: () => import('./example-modal-view.element.js'), + }, +]; +export default [...demoModals]; diff --git a/src/packages/core/modal/component/modal.element.ts b/src/packages/core/modal/component/modal.element.ts index b5a431d7cc..952fe9af74 100644 --- a/src/packages/core/modal/component/modal.element.ts +++ b/src/packages/core/modal/component/modal.element.ts @@ -9,6 +9,7 @@ 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, @@ -36,7 +37,7 @@ export class UmbModalElement extends UmbLitElement { this.#createModalElement(); } - public element?: UUIModalDialogElement | UUIModalSidebarElement; + public element?: UUIModalDialogElement | UUIModalSidebarElement | UUIModalElement; #innerElement = new UmbBasicState(undefined); @@ -102,6 +103,9 @@ export class UmbModalElement extends UmbLitElement { } #createContainerElement() { + if(this.#modalContext!.type == 'custom' && this.#modalContext?.elementFactory) + return this.#modalContext.elementFactory(); + return this.#modalContext!.type === 'sidebar' ? this.#createSidebarElement() : this.#createDialogElement(); } diff --git a/src/packages/core/modal/context/modal-manager.context.ts b/src/packages/core/modal/context/modal-manager.context.ts index d923e06b0b..f47ef021bc 100644 --- a/src/packages/core/modal/context/modal-manager.context.ts +++ b/src/packages/core/modal/context/modal-manager.context.ts @@ -1,17 +1,20 @@ 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'; -export type UmbModalType = 'dialog' | 'sidebar'; +export type UmbModalType = 'dialog' | 'sidebar' | 'custom'; export interface UmbModalConfig { key?: string; type?: UmbModalType; size?: UUIModalSidebarSize; + + /** When type is custom and factory is provided the returned element will be used as the modal element */ + elementFactory? : () => UUIModalElement; } export class UmbModalManagerContext extends UmbContextBase { diff --git a/src/packages/core/modal/context/modal.context.ts b/src/packages/core/modal/context/modal.context.ts index a22c2dd674..a756ad12cf 100644 --- a/src/packages/core/modal/context/modal.context.ts +++ b/src/packages/core/modal/context/modal.context.ts @@ -2,7 +2,7 @@ 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'; @@ -32,6 +32,7 @@ export class UmbModalContext UUIModalElement; public readonly router: IRouterSlot | null = null; public readonly alias: string | UmbModalToken; @@ -51,11 +52,13 @@ export class UmbModalContext Date: Sun, 31 Mar 2024 03:56:28 +0200 Subject: [PATCH 2/7] #1506 Fixed typos --- examples/custom-modal/example-modal-view.element.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/custom-modal/example-modal-view.element.ts b/examples/custom-modal/example-modal-view.element.ts index d29e894cd0..f61c65fac5 100644 --- a/examples/custom-modal/example-modal-view.element.ts +++ b/examples/custom-modal/example-modal-view.element.ts @@ -1,4 +1,4 @@ -import type { DemoModalData, DemoModalResult } from './example-modal-token.js'; +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'; @@ -8,7 +8,7 @@ import './example-custom-modal-element.element.js'; export class UmbExampleModalViewElement extends UmbLitElement { @property({ attribute: false }) - public modalContext?: UmbModalContext; + public modalContext?: UmbModalContext; onClickDone(){ this.modalContext?.submit(); @@ -44,7 +44,7 @@ export class UmbExampleModalViewElement extends UmbLitElement { ]; } -export default UmbPocModalViewElement +export default UmbExampleModalViewElement declare global { interface HTMLElementTagNameMap { From 95b6aa5dcf056e2a533ca7af2cbf0a4dcac6043c Mon Sep 17 00:00:00 2001 From: Markus Johansson Date: Mon, 14 Oct 2024 17:30:10 +0200 Subject: [PATCH 3/7] Cleanup, worked on example and added comments --- .../example-custom-modal-dashboard.element.ts | 15 +++++++-------- examples/custom-modal/example-modal-token.ts | 10 ---------- examples/custom-modal/index.ts | 14 ++++++++++---- .../core/modal/context/modal-manager.context.ts | 2 +- src/packages/core/modal/context/modal.context.ts | 6 ++++-- 5 files changed, 22 insertions(+), 25 deletions(-) diff --git a/examples/custom-modal/example-custom-modal-dashboard.element.ts b/examples/custom-modal/example-custom-modal-dashboard.element.ts index 0f63156871..1606e9aab2 100644 --- a/examples/custom-modal/example-custom-modal-dashboard.element.ts +++ b/examples/custom-modal/example-custom-modal-dashboard.element.ts @@ -1,7 +1,7 @@ import { EXAMPLE_MODAL_TOKEN, type ExampleModalData, type ExampleModalResult } from './example-modal-token.js'; -import { css, html, customElement, property } from '@umbraco-cms/backoffice/external/lit'; +import { css, html, customElement } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { UMB_MODAL_MANAGER_CONTEXT, type UmbModalContext } from '@umbraco-cms/backoffice/modal'; +import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; import './example-custom-modal-element.element.js'; @customElement('example-custom-modal-dashboard') @@ -9,9 +9,6 @@ export class UmbExampleCustomModalDashboardElement extends UmbLitElement { #modalManagerContext? : typeof UMB_MODAL_MANAGER_CONTEXT.TYPE; - /** - * - */ constructor() { super(); this.consumeContext(UMB_MODAL_MANAGER_CONTEXT,(instance)=>{ @@ -27,14 +24,16 @@ export class UmbExampleCustomModalDashboardElement extends UmbLitElement { return html`

Open the custom modal

- Open Modal + Open Modal
`; } static override styles = [css` - - + :host{ + display:block; + padding:20px; + } `]; } diff --git a/examples/custom-modal/example-modal-token.ts b/examples/custom-modal/example-modal-token.ts index 5251b983d1..84a643f051 100644 --- a/examples/custom-modal/example-modal-token.ts +++ b/examples/custom-modal/example-modal-token.ts @@ -1,4 +1,3 @@ -import type { UUIDialogElement } from "@umbraco-cms/backoffice/external/uui"; import { UmbModalToken } from "@umbraco-cms/backoffice/modal"; export interface ExampleModalData { @@ -13,17 +12,8 @@ export const EXAMPLE_MODAL_TOKEN = new UmbModalToken< ExampleModalData, ExampleModalResult >('example.modal.custom.element', { - modal : { type : 'custom', - // element : ()=> { - // // returning the custom modal element - // const modalDialogElement = document.createElement('example-modal-element'); - // const dialogElement: UUIDialogElement = document.createElement('uui-dialog'); - // modalDialogElement.appendChild(dialogElement); - // return modalDialogElement; - // } element: () => import('./example-custom-modal-element.element.js'), } - }); diff --git a/examples/custom-modal/index.ts b/examples/custom-modal/index.ts index 31e7d958c6..2ddf119f38 100644 --- a/examples/custom-modal/index.ts +++ b/examples/custom-modal/index.ts @@ -2,10 +2,10 @@ 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'), + type: 'modal', + name: 'Example Custom Modal Element', + alias: 'example.modal.custom.element', + js: () => import('./example-modal-view.element.js'), } const demoModalsDashboard : ManifestDashboard = { @@ -18,6 +18,12 @@ const demoModalsDashboard : ManifestDashboard = { label: 'Custom Modal', pathname: 'custom-modal', }, + conditions : [ + { + alias: 'Umb.Condition.SectionAlias', + match: 'Umb.Section.Content' + } + ] } export default [demoModal,demoModalsDashboard]; diff --git a/src/packages/core/modal/context/modal-manager.context.ts b/src/packages/core/modal/context/modal-manager.context.ts index c603ab249b..41c35e2f3f 100644 --- a/src/packages/core/modal/context/modal-manager.context.ts +++ b/src/packages/core/modal/context/modal-manager.context.ts @@ -15,7 +15,7 @@ export interface UmbModalConfig { size?: UUIModalSidebarSize; /** - * Used to provide a custom modal element + * Used to provide a custom modal element to replace the default uui-modal-dialog or uui-modal-sidebar */ element?: ElementLoaderProperty; diff --git a/src/packages/core/modal/context/modal.context.ts b/src/packages/core/modal/context/modal.context.ts index 00299c32e7..c4ff23d8d9 100644 --- a/src/packages/core/modal/context/modal.context.ts +++ b/src/packages/core/modal/context/modal.context.ts @@ -7,7 +7,7 @@ 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 { loadManifestElement, type ElementLoaderProperty } from '@umbraco-cms/backoffice/extension-api'; +import { type ElementLoaderProperty } from '@umbraco-cms/backoffice/extension-api'; export interface UmbModalRejectReason { type: string; @@ -15,7 +15,9 @@ export interface UmbModalRejectReason { export type UmbModalContextClassArgs< ModalAliasType extends string | UmbModalToken, - ModalAliasTypeAsToken extends UmbModalToken = ModalAliasType extends UmbModalToken ? ModalAliasType : UmbModalToken, + ModalAliasTypeAsToken extends UmbModalToken = ModalAliasType extends UmbModalToken + ? ModalAliasType + : UmbModalToken, > = { router?: IRouterSlot | null; data?: ModalAliasTypeAsToken['DATA']; From 8a3422acbaf51732e2331ef9a0031a43885312f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Thu, 7 Nov 2024 21:45:40 +0100 Subject: [PATCH 4/7] fx imports --- examples/modal-routed/index.ts | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/examples/modal-routed/index.ts b/examples/modal-routed/index.ts index 5aca09fe79..3b2f6ee704 100644 --- a/examples/modal-routed/index.ts +++ b/examples/modal-routed/index.ts @@ -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', From 715d11a2ce42165f3d85c4bfa5a185a4bd9b39a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Thu, 7 Nov 2024 21:51:13 +0100 Subject: [PATCH 5/7] refactor to use init method --- .../backoffice-modal-container.element.ts | 22 +++++++++---------- .../core/modal/component/modal.element.ts | 22 +++++++------------ 2 files changed, 18 insertions(+), 26 deletions(-) diff --git a/src/packages/core/components/backoffice-modal-container/backoffice-modal-container.element.ts b/src/packages/core/components/backoffice-modal-container/backoffice-modal-container.element.ts index ab8ae573a9..3bd005e7c7 100644 --- a/src/packages/core/components/backoffice-modal-container/backoffice-modal-container.element.ts +++ b/src/packages/core/components/backoffice-modal-container/backoffice-modal-container.element.ts @@ -13,7 +13,7 @@ export class UmbBackofficeModalContainerElement extends UmbLitElement { @state() _modals: Array = []; - @property({ reflect: true, attribute: 'fill-background' }) + @property({ type: Boolean, reflect: true, attribute: 'fill-background' }) fillBackground = false; private _modalManager?: UmbModalManagerContext; @@ -58,28 +58,26 @@ export class UmbBackofficeModalContainerElement extends UmbLitElement { return; } - this._modals.forEach(async (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); - await modalElement.createModalElement(); + modalElement.element?.addEventListener('close-end', this.#onCloseEnd.bind(this, modalContext.key)); + modalContext.addEventListener('umb:destroy', this.#onCloseEnd.bind(this, modalContext.key)); - modalElement.element?.addEventListener('close-end', this.#onCloseEnd.bind(this, modal.key)); - modal.addEventListener('umb:destroy', this.#onCloseEnd.bind(this, modal.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'); }); } diff --git a/src/packages/core/modal/component/modal.element.ts b/src/packages/core/modal/component/modal.element.ts index a192488d3a..8df92878c7 100644 --- a/src/packages/core/modal/component/modal.element.ts +++ b/src/packages/core/modal/component/modal.element.ts @@ -26,18 +26,6 @@ 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; - } - } public element?: UUIModalDialogElement | UUIModalSidebarElement | UUIModalElement; @@ -51,8 +39,14 @@ export class UmbModalElement extends UmbLitElement { this.#modalContext?.reject({ type: 'close' }); }; - async 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 = await this.#createContainerElement(); From 2c98de78377c19ed2a90e4939c38cb62270b5d55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Thu, 7 Nov 2024 21:51:35 +0100 Subject: [PATCH 6/7] use fillBackground prop --- .../backoffice-modal-container.element.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/packages/core/components/backoffice-modal-container/backoffice-modal-container.element.ts b/src/packages/core/components/backoffice-modal-container/backoffice-modal-container.element.ts index 3bd005e7c7..61cd3892a0 100644 --- a/src/packages/core/components/backoffice-modal-container/backoffice-modal-container.element.ts +++ b/src/packages/core/components/backoffice-modal-container/backoffice-modal-container.element.ts @@ -41,7 +41,7 @@ export class UmbBackofficeModalContainerElement extends UmbLitElement { * @param modals */ #createModalElements(modals: Array) { - this.removeAttribute('fill-background'); + this.fillBackground = false; const oldValue = this._modals; this._modals = modals; From 3f5bc9382a3583d7851fb8029220d44aaeee044c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Thu, 7 Nov 2024 22:15:10 +0100 Subject: [PATCH 7/7] fix closing nested routers modals --- src/packages/core/router/route.context.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/packages/core/router/route.context.ts b/src/packages/core/router/route.context.ts index f8f15291d4..5e176c8dcf 100644 --- a/src/packages/core/router/route.context.ts +++ b/src/packages/core/router/route.context.ts @@ -150,6 +150,11 @@ export class UmbRouteContext extends UmbContextBase { modalRegistration._internal_setRouteBuilder(urlBuilder); }; + + override hostDisconnected(): void { + super.hostDisconnected(); + this._internal_modalRouterChanged(undefined); + } } export const UMB_ROUTE_CONTEXT = new UmbContextToken('UmbRouterContext');