diff --git a/examples/modal-routed/dashboard.element.ts b/examples/modal-routed/dashboard.element.ts new file mode 100644 index 0000000000..967a4a7a88 --- /dev/null +++ b/examples/modal-routed/dashboard.element.ts @@ -0,0 +1,47 @@ +import { css, html, LitElement, customElement, state } from '@umbraco-cms/backoffice/external/lit'; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; +import { UmbElementMixin } from '@umbraco-cms/backoffice/element-api'; +import type { UmbRoute } from '@umbraco-cms/backoffice/router'; + +@customElement('umb-dashboard') +export class UmbDashboardElement extends UmbElementMixin(LitElement) { + @state() + private _routes: UmbRoute[] = [ + { + path: `/tab1`, + component: () => import('./tabs/tab1.element.js'), + }, + { + path: `/tab2`, + component: () => import('./tabs/tab2.element.js'), + }, + { + path: '', + redirectTo: 'tab1', + }, + ]; + + override render() { + return html` +
+ Dashboard 1 + +
+ +
+ `; + } + + static override styles = [UmbTextStyles, css``]; +} + +export default UmbDashboardElement; + +declare global { + interface HTMLElementTagNameMap { + 'umb-dashboard': UmbDashboardElement; + } +} diff --git a/examples/modal-routed/dashboard2.element.ts b/examples/modal-routed/dashboard2.element.ts new file mode 100644 index 0000000000..2a3f02e7bf --- /dev/null +++ b/examples/modal-routed/dashboard2.element.ts @@ -0,0 +1,34 @@ +import { css, html, LitElement, customElement } from '@umbraco-cms/backoffice/external/lit'; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; +import { UmbElementMixin } from '@umbraco-cms/backoffice/element-api'; + +@customElement('umb-dashboard2') +export class UmbDashboard2Element extends UmbElementMixin(LitElement) { + constructor() { + super(); + } + + override render() { + return html` +
+

Link to modal route

+

+ This page only shows how to link to the routed modal that is placed on a tab on the "Modal Dashboard". + Clicking this link will not load the slots inside the modal, however, going to the "Modal Dashboard", clicking + on tab 2 and opening the modal from there will work. +

+ Open Modal Route +
+ `; + } + + static override styles = [UmbTextStyles, css``]; +} + +export default UmbDashboard2Element; + +declare global { + interface HTMLElementTagNameMap { + 'umb-dashboard2': UmbDashboard2Element; + } +} diff --git a/examples/modal-routed/index.ts b/examples/modal-routed/index.ts new file mode 100644 index 0000000000..5aca09fe79 --- /dev/null +++ b/examples/modal-routed/index.ts @@ -0,0 +1,44 @@ +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" +// } +// } + +const dashboard: ManifestDashboard = { + type: 'dashboard', + name: 'Example Modal Dashboard', + alias: 'example.dashboard.dataset', + element: () => import('./dashboard.element.js'), + weight: 15000, + meta: { + label: 'Modal Dashboard', + pathname: 'example', + }, +}; + +const dashboard2: ManifestDashboard = { + type: 'dashboard', + name: 'Example Modal Dashboard2', + alias: 'example.dashboard.dataset2', + element: () => import('./dashboard2.element.js'), + weight: 15001, + meta: { + label: 'Link Dashboard', + pathname: 'example-2', + }, +}; + +const modal: ManifestModal = { + type: 'modal', + name: 'Example Modal', + alias: 'example.routed.modal', + element: () => import('./modal/example-modal.element.js'), +}; + +export const manifests = [dashboard, dashboard2, modal]; diff --git a/examples/modal-routed/modal/example-modal-token.ts b/examples/modal-routed/modal/example-modal-token.ts new file mode 100644 index 0000000000..1755a7ef3a --- /dev/null +++ b/examples/modal-routed/modal/example-modal-token.ts @@ -0,0 +1,13 @@ +import { UmbModalToken } from '@umbraco-cms/backoffice/modal'; + +export type Data = object; +export type ModalValue = object; + +export const EXAMPLE_ROUTED_MODAL = new UmbModalToken( + 'example.routed.modal', // this needs to match the alias of the modal registered in manifest.ts + { + modal: { + type: 'dialog', + }, + }, +); diff --git a/examples/modal-routed/modal/example-modal.element.ts b/examples/modal-routed/modal/example-modal.element.ts new file mode 100644 index 0000000000..e1f0f5bdd4 --- /dev/null +++ b/examples/modal-routed/modal/example-modal.element.ts @@ -0,0 +1,43 @@ +import { css, html, customElement, state } from '@umbraco-cms/backoffice/external/lit'; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; +import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal'; +import type { UmbRoute } from '@umbraco-cms/backoffice/router'; + +@customElement('umb-example-modal') +export class UmbExampleModal extends UmbModalBaseElement { + @state() + private _routes: UmbRoute[] = [ + { + path: `modalOverview`, + component: () => import('./steps/example-modal-step1.element.js'), + }, + { + path: `details`, + component: () => import('./steps/example-modal-step2.element.js'), + }, + { + path: '', + redirectTo: 'modalOverview', + }, + ]; + + override render() { + return html` +
+ umb-example modal element +
+ +
+ `; + } + + static override styles = [UmbTextStyles, css``]; +} + +export default UmbExampleModal; + +declare global { + interface HTMLElementTagNameMap { + 'umb-example-modal': UmbExampleModal; + } +} diff --git a/examples/modal-routed/modal/steps/example-modal-step1.element.ts b/examples/modal-routed/modal/steps/example-modal-step1.element.ts new file mode 100644 index 0000000000..9098546e6a --- /dev/null +++ b/examples/modal-routed/modal/steps/example-modal-step1.element.ts @@ -0,0 +1,20 @@ +import { css, html, customElement } from '@umbraco-cms/backoffice/external/lit'; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; +import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal'; + +@customElement('umb-example-modal-step1') +export class UmbExampleModalStep1 extends UmbModalBaseElement { + override render() { + return html`
example modal step1
`; + } + + static override styles = [UmbTextStyles, css``]; +} + +export default UmbExampleModalStep1; + +declare global { + interface HTMLElementTagNameMap { + 'umb-example-modal-step1': UmbExampleModalStep1; + } +} diff --git a/examples/modal-routed/modal/steps/example-modal-step2.element.ts b/examples/modal-routed/modal/steps/example-modal-step2.element.ts new file mode 100644 index 0000000000..ea5c711b25 --- /dev/null +++ b/examples/modal-routed/modal/steps/example-modal-step2.element.ts @@ -0,0 +1,20 @@ +import { html, customElement } from '@umbraco-cms/backoffice/external/lit'; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; +import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal'; + +@customElement('umb-example-modal-step2') +export class UmbExampleModalStep2 extends UmbModalBaseElement { + override render() { + return html`
example modal step2
`; + } + + static override styles = [UmbTextStyles]; +} + +export default UmbExampleModalStep2; + +declare global { + interface HTMLElementTagNameMap { + 'umb-example-modal-step2': UmbExampleModalStep2; + } +} diff --git a/examples/modal-routed/tabs/tab1.element.ts b/examples/modal-routed/tabs/tab1.element.ts new file mode 100644 index 0000000000..463b31824e --- /dev/null +++ b/examples/modal-routed/tabs/tab1.element.ts @@ -0,0 +1,31 @@ +import { css, html, LitElement, customElement, state } from '@umbraco-cms/backoffice/external/lit'; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; +import { UmbElementMixin } from '@umbraco-cms/backoffice/element-api'; + +@customElement('umb-dashboard-tab1') +export class UmbDashboardTab1Element extends UmbElementMixin(LitElement) { + @state() + _editLinkPath?: string; + + constructor() { + super(); + } + + override render() { + return html` +
+

tab 1

+
+ `; + } + + static override styles = [UmbTextStyles, css``]; +} + +export default UmbDashboardTab1Element; + +declare global { + interface UmbDashboardTab1Element { + 'umb-dashboard-tab1': UmbDashboardTab1Element; + } +} diff --git a/examples/modal-routed/tabs/tab2.element.ts b/examples/modal-routed/tabs/tab2.element.ts new file mode 100644 index 0000000000..66346df3de --- /dev/null +++ b/examples/modal-routed/tabs/tab2.element.ts @@ -0,0 +1,55 @@ +import { EXAMPLE_ROUTED_MODAL } from '../modal/example-modal-token.js'; +import { css, html, LitElement, customElement, state } from '@umbraco-cms/backoffice/external/lit'; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; +import { UmbElementMixin } from '@umbraco-cms/backoffice/element-api'; +import { UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/router'; + +@customElement('umb-dashboard-tab2') +export class UmbDashboardTab2Element extends UmbElementMixin(LitElement) { + #workspaceModal?: UmbModalRouteRegistrationController< + typeof EXAMPLE_ROUTED_MODAL.DATA, + typeof EXAMPLE_ROUTED_MODAL.VALUE + >; + + @state() + _editLinkPath?: string; + + constructor() { + super(); + + // Using workspace modal context + this.#workspaceModal?.destroy(); + this.#workspaceModal = new UmbModalRouteRegistrationController(this, EXAMPLE_ROUTED_MODAL) + .addAdditionalPath('view/:entityKey') + .onSetup(() => { + return { + data: {}, + value: {}, + }; + }) + .observeRouteBuilder((routeBuilder) => { + this._editLinkPath = routeBuilder({ entityKey: 'abc123' }); + }); + } + + override render() { + return html` +
+

tab 2

+

This element hosts the UmbModalRouteRegistrationController

+ + Open modal +
+ `; + } + + static override styles = [UmbTextStyles, css``]; +} + +export default UmbDashboardTab2Element; + +declare global { + interface UmbDashboardTab2Element { + 'umb-dashboard-tab2': UmbDashboardTab2Element; + } +} diff --git a/src/external/router-slot/router-slot.ts b/src/external/router-slot/router-slot.ts index 06fddd335b..b93152ba63 100644 --- a/src/external/router-slot/router-slot.ts +++ b/src/external/router-slot/router-slot.ts @@ -162,9 +162,11 @@ export class RouterSlot extends HTMLElement implements IRouter this._setParent(null); } } - if (this.parent && this.parent.match !== null && this.match === null) { + if (this.parent) { requestAnimationFrame(() => { - this.render(); + if (this.parent && this.parent.match !== null && this.match === null) { + this.render(); + } }); } } diff --git a/src/packages/core/router/route.context.ts b/src/packages/core/router/route.context.ts index 7fda170903..f8f15291d4 100644 --- a/src/packages/core/router/route.context.ts +++ b/src/packages/core/router/route.context.ts @@ -123,7 +123,7 @@ export class UmbRouteContext extends UmbContextBase { if (this.#activeModalPath) { // If if there is a modal using the old path. const activeModal = this.#modalRegistrations.find((registration) => { - return registration.generateModalPath() === this.#activeModalPath; + return '/' + registration.generateModalPath() === this.#activeModalPath; }); if (activeModal) { this.#modalContext?.close(activeModal.key); diff --git a/src/packages/core/router/router-slot.element.ts b/src/packages/core/router/router-slot.element.ts index 6b45549d73..a78f0e2ac6 100644 --- a/src/packages/core/router/router-slot.element.ts +++ b/src/packages/core/router/router-slot.element.ts @@ -94,9 +94,7 @@ export class UmbRouterSlotElement extends UmbLitElement { protected override firstUpdated(_changedProperties: PropertyValueMap | Map): void { super.firstUpdated(_changedProperties); - this._routerPath = this._constructAbsoluteRouterPath(); - this.#routeContext._internal_routerGotBasePath(this._routerPath); - this.dispatchEvent(new UmbRouterSlotInitEvent()); + this._updateRouterPath(); } protected _updateRouterPath() { @@ -124,7 +122,7 @@ export class UmbRouterSlotElement extends UmbLitElement { this.dispatchEvent(new UmbRouterSlotChangeEvent()); } } else if (event.detail.slot === this.#modalRouter) { - const newActiveModalLocalPath = this.#modalRouter.match?.fragments.consumed ?? ''; + const newActiveModalLocalPath = this.#modalRouter.match?.route.path ?? ''; this.#routeContext._internal_modalRouterChanged(newActiveModalLocalPath); } };