diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/modal/component/modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/modal/component/modal.element.ts index e9067ba9cb08..f052798a472f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/modal/component/modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/modal/component/modal.element.ts @@ -15,7 +15,7 @@ import { type UUIModalSidebarElement, type UUIModalSidebarSize, } from '@umbraco-cms/backoffice/external/uui'; -import { UMB_ROUTE_CONTEXT, type UmbRouterSlotElement } from '@umbraco-cms/backoffice/router'; +import type { UmbRouterSlotElement } from '@umbraco-cms/backoffice/router'; import { createExtensionElement, loadManifestElement } from '@umbraco-cms/backoffice/extension-api'; import { UmbContextBoundary, UmbContextProvider } from '@umbraco-cms/backoffice/context-api'; import { UmbContextProxyController } from '@umbraco-cms/backoffice/context-proxy'; @@ -93,7 +93,10 @@ export class UmbModalElement extends UmbLitElement { this.#modalRouterElement = document.createElement('div'); // Notice inline styling here is used cause the element is not appended into this elements shadowDom but outside and there by gets into the element via a slot. this.#modalRouterElement.style.display = 'contents'; - new UmbContextBoundary(this.#modalRouterElement, UMB_ROUTE_CONTEXT).hostConnected(); + // Create a context boundary to prevent route context requests from bubbling up through the modal + if (this.#modalContext.routeContextToken) { + new UmbContextBoundary(this.#modalRouterElement, this.#modalContext.routeContextToken).hostConnected(); + } } this.element.appendChild(this.#modalRouterElement); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/modal/context/modal.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/modal/context/modal.context.ts index fa2a78d9c368..92d3dbf99daf 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/modal/context/modal.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/modal/context/modal.context.ts @@ -7,10 +7,10 @@ import { umbDeepMerge } from '@umbraco-cms/backoffice/utils'; import { UmbId } from '@umbraco-cms/backoffice/id'; import { UmbObjectState, UmbStringState } from '@umbraco-cms/backoffice/observable-api'; import { UmbViewController } from '@umbraco-cms/backoffice/view'; -import { UMB_ROUTE_CONTEXT } from '@umbraco-cms/backoffice/router'; import type { ElementLoaderProperty } from '@umbraco-cms/backoffice/extension-api'; import type { IRouterSlot } from '@umbraco-cms/backoffice/router'; import type { UmbDeepPartialObject } from '@umbraco-cms/backoffice/utils'; +import type { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; export interface UmbModalRejectReason { type: string; @@ -26,6 +26,7 @@ export type UmbModalContextClassArgs< data?: ModalAliasTypeAsToken['DATA']; value?: ModalAliasTypeAsToken['VALUE']; modal?: UmbModalConfig; + routeContextToken?: UmbContextToken; }; // TODO: consider splitting this into two separate handlers @@ -55,6 +56,7 @@ export class UmbModalContext< public readonly backdropBackground?: string; public readonly router: IRouterSlot | null = null; public readonly alias: string | UmbModalToken; + public readonly routeContextToken?: UmbContextToken; #value; public readonly value; @@ -73,6 +75,7 @@ export class UmbModalContext< this.key = args.modal?.key || UmbId.new(); this.router = args.router ?? null; this.alias = modalAlias; + this.routeContextToken = args.routeContextToken; this.view = new UmbViewController(this, modalAlias.toString()); @@ -127,6 +130,8 @@ export class UmbModalContext< // eslint-disable-next-line @typescript-eslint/naming-convention async _internal_removeCurrentModal() { + // Use dynamic import to avoid circular dependency + const { UMB_ROUTE_CONTEXT } = await import('@umbraco-cms/backoffice/router'); const routeContext = await this.getContext(UMB_ROUTE_CONTEXT); routeContext?._internal_removeModalPath(this.#activeModalPath); } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/router/modal-registration/modal-route-registration.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/router/modal-registration/modal-route-registration.controller.ts index 5886dccdd4a3..6b597e624652 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/router/modal-registration/modal-route-registration.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/router/modal-registration/modal-route-registration.controller.ts @@ -1,8 +1,8 @@ import type { IRouterSlot, Params } from '../router-slot/index.js'; import { UMB_ROUTE_PATH_ADDENDUM_CONTEXT } from '../contexts/route-path-addendum.context-token.js'; -import { UMB_ROUTE_CONTEXT } from '../route/route.context.js'; +import { UMB_ROUTE_CONTEXT } from '../route/route.context-token.js'; import { encodeFolderName } from '../encode-folder-name.function.js'; -import type { UmbModalRouteRegistration } from './modal-route-registration.interface.js'; +import type { UmbModalRouteRegistration, UmbModalRouteSetupArgs } from './modal-route-registration.interface.js'; import type { UmbModalConfig, UmbModalContext, @@ -14,6 +14,7 @@ import type { UmbControllerAlias, UmbControllerHost } from '@umbraco-cms/backoff import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; import { UmbId } from '@umbraco-cms/backoffice/id'; import type { UmbDeepPartialObject } from '@umbraco-cms/backoffice/utils'; +import type { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; export type UmbModalRouteBuilder = (params: { [key: string]: string | number } | null) => string; @@ -327,20 +328,21 @@ export class UmbModalRouteRegistrationController< this.#modalContext = undefined; }; - async routeSetup(router: IRouterSlot, modalManagerContext: UmbModalManagerContext, params: Params) { + async routeSetup(args: UmbModalRouteSetupArgs) { // If already open, don't do anything: if (this.active) return; - const modalData = this.#onSetupCallback ? await this.#onSetupCallback(params) : undefined; + const modalData = this.#onSetupCallback ? await this.#onSetupCallback(args.params) : undefined; if (modalData !== false) { - const args = { + const modalArgs = { modal: {}, ...modalData, - router, + router: args.router, + routeContextToken: args.routeContextToken, } as UmbModalContextClassArgs>; - args.modal!.key = this.#key; + modalArgs.modal!.key = this.#key; - this.#modalContext = modalManagerContext.open(this, this.#modalAlias, args); + this.#modalContext = args.modalManagerContext.open(this, this.#modalAlias, modalArgs); this.#modalContext.onSubmit().then(this.#onSubmit, this.#onReject); return this.#modalContext; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/router/modal-registration/modal-route-registration.interface.ts b/src/Umbraco.Web.UI.Client/src/packages/core/router/modal-registration/modal-route-registration.interface.ts index d3dbbf09b98b..6baaebdfe770 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/router/modal-registration/modal-route-registration.interface.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/router/modal-registration/modal-route-registration.interface.ts @@ -1,6 +1,18 @@ import type { IRouterSlot, Params } from '../router-slot/index.js'; import type { UmbModalRouteBuilder } from './modal-route-registration.controller.js'; -import type { UmbModalContext, UmbModalManagerContext, UmbModalToken } from '@umbraco-cms/backoffice/modal'; +import type { + UmbModalContext, + UmbModalManagerContext, + UmbModalToken, +} from '@umbraco-cms/backoffice/modal'; +import type { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; + +export type UmbModalRouteSetupArgs = { + router: IRouterSlot; + modalManagerContext: UmbModalManagerContext; + params: Params; + routeContextToken: UmbContextToken; +}; export interface UmbModalRouteRegistration< UmbModalTokenData extends { [key: string]: any } = { [key: string]: any }, @@ -15,9 +27,7 @@ export interface UmbModalRouteRegistration< open(params: { [key: string]: string | number }, prepend?: string): void; routeSetup( - router: IRouterSlot, - modalManagerContext: UmbModalManagerContext, - params: Params, + args: UmbModalRouteSetupArgs, ): Promise>; // eslint-disable-next-line @typescript-eslint/naming-convention diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/router/route/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/router/route/index.ts index 4dd43b97488d..3c3793b8f623 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/router/route/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/router/route/index.ts @@ -1,5 +1,6 @@ export * from './forbidden/route-forbidden.element.js'; export * from './not-found/route-not-found.element.js'; +export * from './route.context-token.js'; export * from './route.context.js'; export * from './router-slot-change.event.js'; export * from './router-slot-init.event.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/router/route/route.context-token.ts b/src/Umbraco.Web.UI.Client/src/packages/core/router/route/route.context-token.ts new file mode 100644 index 000000000000..43c748df1b33 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/router/route/route.context-token.ts @@ -0,0 +1,4 @@ +import type { UmbRouteContext } from './route.context.js'; +import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; + +export const UMB_ROUTE_CONTEXT = new UmbContextToken('UmbRouterContext'); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/router/route/route.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/router/route/route.context.ts index ecbe3ad29d08..70f14d62f737 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/router/route/route.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/router/route/route.context.ts @@ -3,7 +3,7 @@ import type { IRouterSlot } from '../router-slot/index.js'; import type { UmbModalRouteRegistration } from '../modal-registration/modal-route-registration.interface.js'; import { umbGenerateRoutePathBuilder } from '../generate-route-path-builder.function.js'; import type { UmbRoute } from './route.interface.js'; -import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; +import { UMB_ROUTE_CONTEXT } from './route.context-token.js'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { UmbContextBase } from '@umbraco-cms/backoffice/class-api'; import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; @@ -66,11 +66,12 @@ export class UmbRouteContext extends UmbContextBase { component: EmptyDiv, setup: async (component, info) => { if (!this.#modalContext) return; - const modalContext = await modalRegistration.routeSetup( - this.#modalRouter, - this.#modalContext, - info.match.params, - ); + const modalContext = await modalRegistration.routeSetup({ + router: this.#modalRouter, + modalManagerContext: this.#modalContext, + params: info.match.params, + routeContextToken: UMB_ROUTE_CONTEXT, + }); if (modalContext) { modalContext._internal_setCurrentModalPath(info.match.fragments.consumed); } @@ -178,5 +179,3 @@ export class UmbRouteContext extends UmbContextBase { this._internal_modalRouterChanged(undefined); } } - -export const UMB_ROUTE_CONTEXT = new UmbContextToken('UmbRouterContext'); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-item/tree-item-base/tree-item-element-base.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-item/tree-item-base/tree-item-element-base.ts index aaced243ab24..359c14f664bc 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-item/tree-item-base/tree-item-element-base.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-item/tree-item-base/tree-item-element-base.ts @@ -136,7 +136,9 @@ export abstract class UmbTreeItemElementBase< .loading=${this._isLoading} .hasChildren=${this._hasChildren} .showChildren=${this._isOpen} - .caretLabel=${this._isOpen ? this.localize.term('visuallyHiddenTexts_collapseChildItems') + ' ' + this._label: this.localize.term('visuallyHiddenTexts_expandChildItems') + ' ' + this._label} + .caretLabel=${this._isOpen + ? this.localize.term('visuallyHiddenTexts_collapseChildItems') + ' ' + this._label + : this.localize.term('visuallyHiddenTexts_expandChildItems') + ' ' + this._label} label=${this._label} href="${ifDefined(this._isSelectableContext ? undefined : this._href)}"> ${this.renderIconContainer()} ${this.renderLabel()} ${this.#renderActions()} ${this.#renderChildItems()} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/kinds/default/default-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/kinds/default/default-workspace.context.ts index 6c3e0185bf90..d5fae1e42e5d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/kinds/default/default-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/kinds/default/default-workspace.context.ts @@ -1,10 +1,10 @@ import { UMB_WORKSPACE_CONTEXT } from '../../workspace.context-token.js'; import type { UmbWorkspaceContext } from '../../workspace-context.interface.js'; +import type { ManifestWorkspaceDefaultKind } from './types.js'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { UmbContextBase } from '@umbraco-cms/backoffice/class-api'; import { UmbEntityContext, type UmbEntityUnique } from '@umbraco-cms/backoffice/entity'; import { UmbViewContext } from '@umbraco-cms/backoffice/view'; -import type { ManifestWorkspaceDefaultKind } from './types.js'; export class UmbDefaultWorkspaceContext extends UmbContextBase implements UmbWorkspaceContext { public workspaceAlias!: string;