Skip to content

Commit a1f1a51

Browse files
committed
Merge branch 'release/15.0'
2 parents ce25a42 + da73567 commit a1f1a51

File tree

9 files changed

+139
-19
lines changed

9 files changed

+139
-19
lines changed

src/libs/context-api/consume/context-consumer.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { UmbContextProvider } from '../provide/context-provider.js';
22
import { UmbContextToken } from '../token/context-token.js';
33
import { UmbContextConsumer } from './context-consumer.js';
44
import type { UmbContextRequestEventImplementation } from './context-request.event.js';
5-
import { UMB_CONTENT_REQUEST_EVENT_TYPE } from './context-request.event.js';
5+
import { UMB_CONTEXT_REQUEST_EVENT_TYPE } from './context-request.event.js';
66
import { expect, oneEvent } from '@open-wc/testing';
77

88
const testContextAlias = 'my-test-context';
@@ -38,13 +38,13 @@ describe('UmbContextConsumer', () => {
3838

3939
describe('events', () => {
4040
it('dispatches context request event when constructed', async () => {
41-
const listener = oneEvent(window, UMB_CONTENT_REQUEST_EVENT_TYPE);
41+
const listener = oneEvent(window, UMB_CONTEXT_REQUEST_EVENT_TYPE);
4242

4343
consumer.hostConnected();
4444

4545
const event = (await listener) as unknown as UmbContextRequestEventImplementation;
4646
expect(event).to.exist;
47-
expect(event.type).to.eq(UMB_CONTENT_REQUEST_EVENT_TYPE);
47+
expect(event.type).to.eq(UMB_CONTEXT_REQUEST_EVENT_TYPE);
4848
expect(event.contextAlias).to.eq(testContextAlias);
4949
consumer.hostDisconnected();
5050
});

src/libs/context-api/consume/context-request.event.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
export const UMB_CONTENT_REQUEST_EVENT_TYPE = 'umb:context-request';
1+
export const UMB_CONTEXT_REQUEST_EVENT_TYPE = 'umb:context-request';
2+
/**
3+
* @deprecated use UMB_CONTEXT_REQUEST_EVENT_TYPE
4+
* This will be removed in Umbraco 17
5+
*/
6+
export const UMB_CONTENT_REQUEST_EVENT_TYPE = UMB_CONTEXT_REQUEST_EVENT_TYPE;
27
export const UMB_DEBUG_CONTEXT_EVENT_TYPE = 'umb:debug-contexts';
38

49
export type UmbContextCallback<T> = (instance: T) => void;
@@ -29,7 +34,7 @@ export class UmbContextRequestEventImplementation<ResultType = unknown>
2934
public readonly callback: (context: ResultType) => boolean,
3035
public readonly stopAtContextMatch: boolean = true,
3136
) {
32-
super(UMB_CONTENT_REQUEST_EVENT_TYPE, { bubbles: true, composed: true, cancelable: true });
37+
super(UMB_CONTEXT_REQUEST_EVENT_TYPE, { bubbles: true, composed: true, cancelable: true });
3338
}
3439

3540
clone() {
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import type { UmbContextToken } from '../token/index.js';
2+
import { UmbContextBoundary } from './context-boundary.js';
3+
import type { UmbControllerHost, UmbController } from '@umbraco-cms/backoffice/controller-api';
4+
5+
export class UmbContextBoundaryController extends UmbContextBoundary implements UmbController {
6+
#host: UmbControllerHost;
7+
#controllerAlias: string;
8+
9+
public get controllerAlias(): string {
10+
return this.#controllerAlias;
11+
}
12+
13+
constructor(host: UmbControllerHost, contextAlias: string | UmbContextToken<any>) {
14+
super(host.getHostElement(), contextAlias);
15+
this.#host = host;
16+
// 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)
17+
// 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.
18+
this.#controllerAlias = 'umbContextBoundary_' + contextAlias.toString();
19+
20+
// If this API is already provided with this alias? Then we do not want to register this controller:
21+
const existingControllers = host.getUmbControllers((x) => x.controllerAlias === this.controllerAlias);
22+
if (existingControllers.length > 0) {
23+
// 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.
24+
// Back out, this instance is already provided, by another controller.
25+
throw new Error(
26+
`Context API: The context boundary of '${this.controllerAlias}' is already provided by another Context Provider Controller.`,
27+
);
28+
} else {
29+
host.addUmbController(this);
30+
}
31+
}
32+
33+
public override destroy(): void {
34+
if (this.#host) {
35+
this.#host.removeUmbController(this);
36+
(this.#host as unknown) = undefined;
37+
}
38+
super.destroy();
39+
}
40+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import type { UmbContextRequestEvent } from '../consume/context-request.event.js';
2+
import type { UmbContextToken } from '../token/index.js';
3+
import { UMB_CONTEXT_REQUEST_EVENT_TYPE } from '../consume/context-request.event.js';
4+
import { UmbContextProvideEventImplementation } from './context-provide.event.js';
5+
6+
/**
7+
* @class UmbContextBoundary
8+
*/
9+
export class UmbContextBoundary {
10+
#eventTarget: EventTarget;
11+
12+
#contextAlias: string;
13+
14+
/**
15+
* Creates an instance of UmbContextBoundary.
16+
* @param {EventTarget} eventTarget - the host element for this context provider
17+
* @param {string | UmbContextToken} contextIdentifier - a string or token to identify the context
18+
* @param {*} instance - the instance to provide
19+
* @memberof UmbContextBoundary
20+
*/
21+
constructor(eventTarget: EventTarget, contextIdentifier: string | UmbContextToken<any>) {
22+
this.#eventTarget = eventTarget;
23+
24+
const idSplit = contextIdentifier.toString().split('#');
25+
this.#contextAlias = idSplit[0];
26+
27+
this.#eventTarget.addEventListener(UMB_CONTEXT_REQUEST_EVENT_TYPE, this.#handleContextRequest);
28+
}
29+
30+
/**
31+
* @private
32+
* @param {UmbContextRequestEvent} event - the event to handle
33+
* @memberof UmbContextBoundary
34+
*/
35+
#handleContextRequest = ((event: UmbContextRequestEvent): void => {
36+
if (event.contextAlias !== this.#contextAlias) return;
37+
38+
if (event.stopAtContextMatch) {
39+
// 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();`
40+
event.stopPropagation();
41+
}
42+
}) as EventListener;
43+
44+
/**
45+
* @memberof UmbContextBoundary
46+
*/
47+
public hostConnected(): void {
48+
//this.hostElement.addEventListener(UMB_CONTENT_REQUEST_EVENT_TYPE, this.#handleContextRequest);
49+
this.#eventTarget.dispatchEvent(new UmbContextProvideEventImplementation(this.#contextAlias));
50+
}
51+
52+
/**
53+
* @memberof UmbContextBoundary
54+
*/
55+
public hostDisconnected(): void {}
56+
57+
destroy(): void {
58+
(this.#eventTarget as unknown) = undefined;
59+
}
60+
}

src/libs/context-api/provide/context-provider.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { UmbContextRequestEvent } from '../consume/context-request.event.js';
22
import type { UmbContextToken } from '../token/index.js';
3-
import { UMB_CONTENT_REQUEST_EVENT_TYPE, UMB_DEBUG_CONTEXT_EVENT_TYPE } from '../consume/context-request.event.js';
3+
import { UMB_CONTEXT_REQUEST_EVENT_TYPE, UMB_DEBUG_CONTEXT_EVENT_TYPE } from '../consume/context-request.event.js';
44
import { UmbContextProvideEventImplementation } from './context-provide.event.js';
55

66
/**
@@ -41,12 +41,12 @@ export class UmbContextProvider<BaseType = unknown, ResultType extends BaseType
4141
this.#apiAlias = idSplit[1] ?? 'default';
4242
this.#instance = instance;
4343

44-
this.#eventTarget.addEventListener(UMB_CONTENT_REQUEST_EVENT_TYPE, this.#handleContextRequest);
44+
this.#eventTarget.addEventListener(UMB_CONTEXT_REQUEST_EVENT_TYPE, this.#handleContextRequest);
4545
}
4646

4747
/**
4848
* @private
49-
* @param {UmbContextRequestEvent} event
49+
* @param {UmbContextRequestEvent} event - the event to handle
5050
* @memberof UmbContextProvider
5151
*/
5252
#handleContextRequest = ((event: UmbContextRequestEvent): void => {

src/libs/context-api/provide/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
export * from './context-boundary.js';
2+
export * from './context-boundary.controller.js';
3+
export * from './context-provide.event.js';
14
export * from './context-provider.controller.js';
25
export * from './context-provider.js';
3-
export * from './context-provide.event.js';

src/packages/core/modal/component/modal.element.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,14 @@ import {
1313
type UUIModalDialogElement,
1414
type UUIModalSidebarElement,
1515
} from '@umbraco-cms/backoffice/external/uui';
16-
import type { UmbRouterSlotElement } from '@umbraco-cms/backoffice/router';
16+
import { UMB_ROUTE_CONTEXT, type UmbRouterSlotElement } from '@umbraco-cms/backoffice/router';
1717
import { createExtensionElement } from '@umbraco-cms/backoffice/extension-api';
1818
import type { UmbContextRequestEvent } from '@umbraco-cms/backoffice/context-api';
19-
import { UMB_CONTENT_REQUEST_EVENT_TYPE, UmbContextProvider } from '@umbraco-cms/backoffice/context-api';
19+
import {
20+
UMB_CONTEXT_REQUEST_EVENT_TYPE,
21+
UmbContextBoundary,
22+
UmbContextProvider,
23+
} from '@umbraco-cms/backoffice/context-api';
2024

2125
@customElement('umb-modal')
2226
export class UmbModalElement extends UmbLitElement {
@@ -41,7 +45,7 @@ export class UmbModalElement extends UmbLitElement {
4145
#innerElement = new UmbBasicState<HTMLElement | undefined>(undefined);
4246

4347
#modalExtensionObserver?: UmbObserverController<ManifestModal | undefined>;
44-
#modalRouterElement: UmbRouterSlotElement = document.createElement('umb-router-slot');
48+
#modalRouterElement?: HTMLDivElement | UmbRouterSlotElement;
4549

4650
#onClose = () => {
4751
this.element?.removeEventListener(UUIModalCloseEvent, this.#onClose);
@@ -59,7 +63,7 @@ export class UmbModalElement extends UmbLitElement {
5963

6064
// The following code is the context api proxy.
6165
// 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]
62-
this.element.addEventListener(UMB_CONTENT_REQUEST_EVENT_TYPE, ((event: UmbContextRequestEvent) => {
66+
this.element.addEventListener(UMB_CONTEXT_REQUEST_EVENT_TYPE, ((event: UmbContextRequestEvent) => {
6367
if (!this.#modalContext) return;
6468
// Note for this hack (The if-sentence): [NL]
6569
// 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,16 +89,24 @@ export class UmbModalElement extends UmbLitElement {
8589
*
8690
*/
8791
if (this.#modalContext.router) {
92+
this.#modalRouterElement = document.createElement('umb-router-slot');
8893
this.#modalRouterElement.routes = [
8994
{
9095
path: '',
9196
component: document.createElement('slot'),
9297
},
9398
];
9499
this.#modalRouterElement.parent = this.#modalContext.router;
100+
} else {
101+
this.#modalRouterElement = document.createElement('div');
102+
// 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.
103+
this.#modalRouterElement.style.position = 'relative';
104+
this.#modalRouterElement.style.height = '100%';
105+
new UmbContextBoundary(this.#modalRouterElement, UMB_ROUTE_CONTEXT).hostConnected();
95106
}
96107

97108
this.element.appendChild(this.#modalRouterElement);
109+
98110
this.#observeModal(this.#modalContext.alias.toString());
99111

100112
const provider = new UmbContextProvider(this.element, UMB_MODAL_CONTEXT, this.#modalContext);
@@ -151,14 +163,14 @@ export class UmbModalElement extends UmbLitElement {
151163
}
152164

153165
#appendInnerElement(element: HTMLElement) {
154-
this.#modalRouterElement.appendChild(element);
166+
this.#modalRouterElement!.appendChild(element);
155167
this.#innerElement.setValue(element);
156168
}
157169

158170
#removeInnerElement() {
159171
const innerElement = this.#innerElement.getValue();
160172
if (innerElement) {
161-
this.#modalRouterElement.removeChild(innerElement);
173+
this.#modalRouterElement!.removeChild(innerElement);
162174
this.#innerElement.setValue(undefined);
163175
}
164176
}

src/packages/core/router/modal-registration/modal-route-registration.controller.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -259,9 +259,9 @@ export class UmbModalRouteRegistrationController<
259259
}
260260

261261
public open(params: { [key: string]: string | number }, prepend?: string) {
262-
if (this.active) return;
262+
if (this.active || !this.#routeBuilder) return;
263263

264-
window.history.pushState({}, '', this.#routeBuilder?.(params) + (prepend ? `${prepend}` : ''));
264+
window.history.pushState({}, '', this.#routeBuilder(params) + (prepend ? `${prepend}` : ''));
265265
}
266266

267267
/**
@@ -277,6 +277,7 @@ export class UmbModalRouteRegistrationController<
277277
return this;
278278
}
279279
public _internal_setRouteBuilder(urlBuilder: UmbModalRouteBuilder) {
280+
if (!this.#routeContext) return;
280281
this.#routeBuilder = urlBuilder;
281282
this.#urlBuilderCallback?.(urlBuilder);
282283
}

src/packages/tiny-mce/components/input-tiny-mce/input-tiny-mce.defaults.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { UMB_CONTENT_REQUEST_EVENT_TYPE, type UmbContextRequestEvent } from '@umbraco-cms/backoffice/context-api';
1+
import { UMB_CONTEXT_REQUEST_EVENT_TYPE, type UmbContextRequestEvent } from '@umbraco-cms/backoffice/context-api';
22
import type { RawEditorOptions } from '@umbraco-cms/backoffice/external/tinymce';
33
import { UUIIconRequestEvent } from '@umbraco-cms/backoffice/external/uui';
44

@@ -34,7 +34,7 @@ export const defaultFallbackConfig: RawEditorOptions = {
3434
init_instance_callback: function (editor) {
3535
// The following code is the context api proxy. [NL]
3636
// 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]
37-
editor.dom.doc.addEventListener(UMB_CONTENT_REQUEST_EVENT_TYPE, ((event: UmbContextRequestEvent) => {
37+
editor.dom.doc.addEventListener(UMB_CONTEXT_REQUEST_EVENT_TYPE, ((event: UmbContextRequestEvent) => {
3838
if (!editor.iframeElement) return;
3939

4040
event.stopImmediatePropagation();

0 commit comments

Comments
 (0)