Merge branch 'main' into v15/feature/content-workspace-base

This commit is contained in:
Mads Rasmussen
2024-11-07 10:30:18 +01:00
9 changed files with 139 additions and 19 deletions

View File

@@ -2,7 +2,7 @@ import { UmbContextProvider } from '../provide/context-provider.js';
import { UmbContextToken } from '../token/context-token.js';
import { UmbContextConsumer } from './context-consumer.js';
import type { UmbContextRequestEventImplementation } from './context-request.event.js';
import { UMB_CONTENT_REQUEST_EVENT_TYPE } from './context-request.event.js';
import { UMB_CONTEXT_REQUEST_EVENT_TYPE } from './context-request.event.js';
import { expect, oneEvent } from '@open-wc/testing';
const testContextAlias = 'my-test-context';
@@ -38,13 +38,13 @@ describe('UmbContextConsumer', () => {
describe('events', () => {
it('dispatches context request event when constructed', async () => {
const listener = oneEvent(window, UMB_CONTENT_REQUEST_EVENT_TYPE);
const listener = oneEvent(window, UMB_CONTEXT_REQUEST_EVENT_TYPE);
consumer.hostConnected();
const event = (await listener) as unknown as UmbContextRequestEventImplementation;
expect(event).to.exist;
expect(event.type).to.eq(UMB_CONTENT_REQUEST_EVENT_TYPE);
expect(event.type).to.eq(UMB_CONTEXT_REQUEST_EVENT_TYPE);
expect(event.contextAlias).to.eq(testContextAlias);
consumer.hostDisconnected();
});

View File

@@ -1,4 +1,9 @@
export const UMB_CONTENT_REQUEST_EVENT_TYPE = 'umb:context-request';
export const UMB_CONTEXT_REQUEST_EVENT_TYPE = 'umb:context-request';
/**
* @deprecated use UMB_CONTEXT_REQUEST_EVENT_TYPE
* This will be removed in Umbraco 17
*/
export const UMB_CONTENT_REQUEST_EVENT_TYPE = UMB_CONTEXT_REQUEST_EVENT_TYPE;
export const UMB_DEBUG_CONTEXT_EVENT_TYPE = 'umb:debug-contexts';
export type UmbContextCallback<T> = (instance: T) => void;
@@ -29,7 +34,7 @@ export class UmbContextRequestEventImplementation<ResultType = unknown>
public readonly callback: (context: ResultType) => boolean,
public readonly stopAtContextMatch: boolean = true,
) {
super(UMB_CONTENT_REQUEST_EVENT_TYPE, { bubbles: true, composed: true, cancelable: true });
super(UMB_CONTEXT_REQUEST_EVENT_TYPE, { bubbles: true, composed: true, cancelable: true });
}
clone() {

View File

@@ -0,0 +1,40 @@
import type { UmbContextToken } from '../token/index.js';
import { UmbContextBoundary } from './context-boundary.js';
import type { UmbControllerHost, UmbController } from '@umbraco-cms/backoffice/controller-api';
export class UmbContextBoundaryController extends UmbContextBoundary implements UmbController {
#host: UmbControllerHost;
#controllerAlias: string;
public get controllerAlias(): string {
return this.#controllerAlias;
}
constructor(host: UmbControllerHost, contextAlias: string | UmbContextToken<any>) {
super(host.getHostElement(), contextAlias);
this.#host = host;
// Makes the controllerAlias unique for this instance, this enables multiple Contexts to be provided under the same name. (This only makes sense cause of Context Token Discriminators)
// This does mean that if someone provides a context with the same name, but with a different instance, it will not override the previous instance. But its good since it enables extensions to provide contexts at the same scope of other contexts.
this.#controllerAlias = 'umbContextBoundary_' + contextAlias.toString();
// If this API is already provided with this alias? Then we do not want to register this controller:
const existingControllers = host.getUmbControllers((x) => x.controllerAlias === this.controllerAlias);
if (existingControllers.length > 0) {
// This just an additional awareness feature to make devs Aware, the alternative would be adding it anyway, but that would destroy existing controller of this alias.
// Back out, this instance is already provided, by another controller.
throw new Error(
`Context API: The context boundary of '${this.controllerAlias}' is already provided by another Context Provider Controller.`,
);
} else {
host.addUmbController(this);
}
}
public override destroy(): void {
if (this.#host) {
this.#host.removeUmbController(this);
(this.#host as unknown) = undefined;
}
super.destroy();
}
}

View File

@@ -0,0 +1,60 @@
import type { UmbContextRequestEvent } from '../consume/context-request.event.js';
import type { UmbContextToken } from '../token/index.js';
import { UMB_CONTEXT_REQUEST_EVENT_TYPE } from '../consume/context-request.event.js';
import { UmbContextProvideEventImplementation } from './context-provide.event.js';
/**
* @class UmbContextBoundary
*/
export class UmbContextBoundary {
#eventTarget: EventTarget;
#contextAlias: string;
/**
* Creates an instance of UmbContextBoundary.
* @param {EventTarget} eventTarget - the host element for this context provider
* @param {string | UmbContextToken} contextIdentifier - a string or token to identify the context
* @param {*} instance - the instance to provide
* @memberof UmbContextBoundary
*/
constructor(eventTarget: EventTarget, contextIdentifier: string | UmbContextToken<any>) {
this.#eventTarget = eventTarget;
const idSplit = contextIdentifier.toString().split('#');
this.#contextAlias = idSplit[0];
this.#eventTarget.addEventListener(UMB_CONTEXT_REQUEST_EVENT_TYPE, this.#handleContextRequest);
}
/**
* @private
* @param {UmbContextRequestEvent} event - the event to handle
* @memberof UmbContextBoundary
*/
#handleContextRequest = ((event: UmbContextRequestEvent): void => {
if (event.contextAlias !== this.#contextAlias) return;
if (event.stopAtContextMatch) {
// Since the alias matches, we will stop it from bubbling further up. But we still allow it to ask the other Contexts of the element. Hence not calling `event.stopImmediatePropagation();`
event.stopPropagation();
}
}) as EventListener;
/**
* @memberof UmbContextBoundary
*/
public hostConnected(): void {
//this.hostElement.addEventListener(UMB_CONTENT_REQUEST_EVENT_TYPE, this.#handleContextRequest);
this.#eventTarget.dispatchEvent(new UmbContextProvideEventImplementation(this.#contextAlias));
}
/**
* @memberof UmbContextBoundary
*/
public hostDisconnected(): void {}
destroy(): void {
(this.#eventTarget as unknown) = undefined;
}
}

View File

@@ -1,6 +1,6 @@
import type { UmbContextRequestEvent } from '../consume/context-request.event.js';
import type { UmbContextToken } from '../token/index.js';
import { UMB_CONTENT_REQUEST_EVENT_TYPE, UMB_DEBUG_CONTEXT_EVENT_TYPE } from '../consume/context-request.event.js';
import { UMB_CONTEXT_REQUEST_EVENT_TYPE, UMB_DEBUG_CONTEXT_EVENT_TYPE } from '../consume/context-request.event.js';
import { UmbContextProvideEventImplementation } from './context-provide.event.js';
/**
@@ -41,12 +41,12 @@ export class UmbContextProvider<BaseType = unknown, ResultType extends BaseType
this.#apiAlias = idSplit[1] ?? 'default';
this.#instance = instance;
this.#eventTarget.addEventListener(UMB_CONTENT_REQUEST_EVENT_TYPE, this.#handleContextRequest);
this.#eventTarget.addEventListener(UMB_CONTEXT_REQUEST_EVENT_TYPE, this.#handleContextRequest);
}
/**
* @private
* @param {UmbContextRequestEvent} event
* @param {UmbContextRequestEvent} event - the event to handle
* @memberof UmbContextProvider
*/
#handleContextRequest = ((event: UmbContextRequestEvent): void => {

View File

@@ -1,3 +1,5 @@
export * from './context-boundary.js';
export * from './context-boundary.controller.js';
export * from './context-provide.event.js';
export * from './context-provider.controller.js';
export * from './context-provider.js';
export * from './context-provide.event.js';

View File

@@ -13,10 +13,14 @@ import {
type UUIModalDialogElement,
type UUIModalSidebarElement,
} from '@umbraco-cms/backoffice/external/uui';
import type { UmbRouterSlotElement } from '@umbraco-cms/backoffice/router';
import { UMB_ROUTE_CONTEXT, type UmbRouterSlotElement } from '@umbraco-cms/backoffice/router';
import { createExtensionElement } from '@umbraco-cms/backoffice/extension-api';
import type { UmbContextRequestEvent } from '@umbraco-cms/backoffice/context-api';
import { UMB_CONTENT_REQUEST_EVENT_TYPE, UmbContextProvider } from '@umbraco-cms/backoffice/context-api';
import {
UMB_CONTEXT_REQUEST_EVENT_TYPE,
UmbContextBoundary,
UmbContextProvider,
} from '@umbraco-cms/backoffice/context-api';
@customElement('umb-modal')
export class UmbModalElement extends UmbLitElement {
@@ -41,7 +45,7 @@ export class UmbModalElement extends UmbLitElement {
#innerElement = new UmbBasicState<HTMLElement | undefined>(undefined);
#modalExtensionObserver?: UmbObserverController<ManifestModal | undefined>;
#modalRouterElement: UmbRouterSlotElement = document.createElement('umb-router-slot');
#modalRouterElement?: HTMLDivElement | UmbRouterSlotElement;
#onClose = () => {
this.element?.removeEventListener(UUIModalCloseEvent, this.#onClose);
@@ -59,7 +63,7 @@ export class UmbModalElement extends UmbLitElement {
// The following code is the context api proxy.
// It re-dispatches the context api request event to the origin target of this modal, in other words the element that initiated the modal. [NL]
this.element.addEventListener(UMB_CONTENT_REQUEST_EVENT_TYPE, ((event: UmbContextRequestEvent) => {
this.element.addEventListener(UMB_CONTEXT_REQUEST_EVENT_TYPE, ((event: UmbContextRequestEvent) => {
if (!this.#modalContext) return;
// Note for this hack (The if-sentence): [NL]
// We do not currently have a good enough control to ensure that the proxy is last, meaning if another context is provided at this element, it might respond after the proxy event has been dispatched.
@@ -85,6 +89,7 @@ export class UmbModalElement extends UmbLitElement {
*
*/
if (this.#modalContext.router) {
this.#modalRouterElement = document.createElement('umb-router-slot');
this.#modalRouterElement.routes = [
{
path: '',
@@ -92,9 +97,16 @@ export class UmbModalElement extends UmbLitElement {
},
];
this.#modalRouterElement.parent = this.#modalContext.router;
} else {
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.position = 'relative';
this.#modalRouterElement.style.height = '100%';
new UmbContextBoundary(this.#modalRouterElement, UMB_ROUTE_CONTEXT).hostConnected();
}
this.element.appendChild(this.#modalRouterElement);
this.#observeModal(this.#modalContext.alias.toString());
const provider = new UmbContextProvider(this.element, UMB_MODAL_CONTEXT, this.#modalContext);
@@ -151,14 +163,14 @@ export class UmbModalElement extends UmbLitElement {
}
#appendInnerElement(element: HTMLElement) {
this.#modalRouterElement.appendChild(element);
this.#modalRouterElement!.appendChild(element);
this.#innerElement.setValue(element);
}
#removeInnerElement() {
const innerElement = this.#innerElement.getValue();
if (innerElement) {
this.#modalRouterElement.removeChild(innerElement);
this.#modalRouterElement!.removeChild(innerElement);
this.#innerElement.setValue(undefined);
}
}

View File

@@ -259,9 +259,9 @@ export class UmbModalRouteRegistrationController<
}
public open(params: { [key: string]: string | number }, prepend?: string) {
if (this.active) return;
if (this.active || !this.#routeBuilder) return;
window.history.pushState({}, '', this.#routeBuilder?.(params) + (prepend ? `${prepend}` : ''));
window.history.pushState({}, '', this.#routeBuilder(params) + (prepend ? `${prepend}` : ''));
}
/**
@@ -277,6 +277,7 @@ export class UmbModalRouteRegistrationController<
return this;
}
public _internal_setRouteBuilder(urlBuilder: UmbModalRouteBuilder) {
if (!this.#routeContext) return;
this.#routeBuilder = urlBuilder;
this.#urlBuilderCallback?.(urlBuilder);
}

View File

@@ -1,4 +1,4 @@
import { UMB_CONTENT_REQUEST_EVENT_TYPE, type UmbContextRequestEvent } from '@umbraco-cms/backoffice/context-api';
import { UMB_CONTEXT_REQUEST_EVENT_TYPE, type UmbContextRequestEvent } from '@umbraco-cms/backoffice/context-api';
import type { RawEditorOptions } from '@umbraco-cms/backoffice/external/tinymce';
import { UUIIconRequestEvent } from '@umbraco-cms/backoffice/external/uui';
@@ -34,7 +34,7 @@ export const defaultFallbackConfig: RawEditorOptions = {
init_instance_callback: function (editor) {
// The following code is the context api proxy. [NL]
// It re-dispatches the context api request event to the origin target of this modal, in other words the element that initiated the modal. [NL]
editor.dom.doc.addEventListener(UMB_CONTENT_REQUEST_EVENT_TYPE, ((event: UmbContextRequestEvent) => {
editor.dom.doc.addEventListener(UMB_CONTEXT_REQUEST_EVENT_TYPE, ((event: UmbContextRequestEvent) => {
if (!editor.iframeElement) return;
event.stopImmediatePropagation();