Skip to content
This repository was archived by the owner on Nov 6, 2025. It is now read-only.

Commit 99bff60

Browse files
authored
Merge pull request #2513 from umbraco/v15/feature/grid-inline-editing-fixes
Feature: Block Grid inline editing + Extension Initializers change
2 parents e3c4177 + 85fe0c6 commit 99bff60

File tree

19 files changed

+293
-145
lines changed

19 files changed

+293
-145
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export class UmbContextConsumerController<BaseType = unknown, ResultType extends
1919
contextAlias: string | UmbContextToken<BaseType, ResultType>,
2020
callback?: UmbContextCallback<ResultType>,
2121
) {
22-
super(host.getHostElement(), contextAlias, callback);
22+
super(() => host.getHostElement(), contextAlias, callback);
2323
this.#host = host;
2424
host.addUmbController(this);
2525
}

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

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { expect, oneEvent } from '@open-wc/testing';
77

88
const testContextAlias = 'my-test-context';
99
const testContextAliasAndApiAlias = 'my-test-context#testApi';
10-
const testContextAliasAndNotExstingApiAlias = 'my-test-context#notExistingTestApi';
10+
const testContextAliasAndNotExistingApiAlias = 'my-test-context#notExistingTestApi';
1111

1212
class UmbTestContextConsumerClass {
1313
public prop: string = 'value from provider';
@@ -74,6 +74,47 @@ describe('UmbContextConsumer', () => {
7474
localConsumer.hostConnected();
7575
});
7676

77+
it('works with host as a method', (done) => {
78+
const provider = new UmbContextProvider(document.body, testContextAlias, new UmbTestContextConsumerClass());
79+
provider.hostConnected();
80+
81+
const element = document.createElement('div');
82+
document.body.appendChild(element);
83+
84+
const localConsumer = new UmbContextConsumer(
85+
() => element,
86+
testContextAlias,
87+
(_instance: UmbTestContextConsumerClass | undefined) => {
88+
if (_instance) {
89+
expect(_instance.prop).to.eq('value from provider');
90+
done();
91+
localConsumer.hostDisconnected();
92+
provider.hostDisconnected();
93+
}
94+
},
95+
);
96+
localConsumer.hostConnected();
97+
});
98+
99+
it('works with host method returning undefined', async () => {
100+
const element = undefined;
101+
102+
const localConsumer = new UmbContextConsumer(
103+
() => element,
104+
testContextAlias,
105+
(_instance: UmbTestContextConsumerClass | undefined) => {
106+
if (_instance) {
107+
expect.fail('Callback should not be called when never permitted');
108+
}
109+
},
110+
);
111+
localConsumer.hostConnected();
112+
113+
await Promise.resolve();
114+
115+
localConsumer.hostDisconnected();
116+
});
117+
77118
/*
78119
Unprovided feature is out commented currently. I'm not sure there is a use case. So lets leave the code around until we know for sure.
79120
it('acts to Context API disconnected', (done) => {
@@ -139,7 +180,7 @@ describe('UmbContextConsumer', () => {
139180
const element = document.createElement('div');
140181
document.body.appendChild(element);
141182

142-
const localConsumer = new UmbContextConsumer(element, testContextAliasAndNotExstingApiAlias, () => {
183+
const localConsumer = new UmbContextConsumer(element, testContextAliasAndNotExistingApiAlias, () => {
143184
expect(false).to.be.true;
144185
});
145186
localConsumer.hostConnected();

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

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ import { isUmbContextProvideEventType, UMB_CONTEXT_PROVIDE_EVENT_TYPE } from '..
33
import type { UmbContextCallback } from './context-request.event.js';
44
import { UmbContextRequestEventImplementation } from './context-request.event.js';
55

6+
type HostElementMethod = () => Element | undefined;
7+
68
/**
79
* @class UmbContextConsumer
810
*/
911
export class UmbContextConsumer<BaseType = unknown, ResultType extends BaseType = BaseType> {
10-
protected _host: Element;
12+
protected _retrieveHost: HostElementMethod;
1113

1214
#skipHost?: boolean;
1315
#stopAtContextMatch = true;
@@ -33,11 +35,15 @@ export class UmbContextConsumer<BaseType = unknown, ResultType extends BaseType
3335
* @memberof UmbContextConsumer
3436
*/
3537
constructor(
36-
host: Element,
38+
host: Element | HostElementMethod,
3739
contextIdentifier: string | UmbContextToken<BaseType, ResultType>,
3840
callback?: UmbContextCallback<ResultType>,
3941
) {
40-
this._host = host;
42+
if (typeof host === 'function') {
43+
this._retrieveHost = host;
44+
} else {
45+
this._retrieveHost = () => host;
46+
}
4147
const idSplit = contextIdentifier.toString().split('#');
4248
this.#contextAlias = idSplit[0];
4349
this.#apiAlias = idSplit[1] ?? 'default';
@@ -72,6 +78,10 @@ export class UmbContextConsumer<BaseType = unknown, ResultType extends BaseType
7278
if (this.#instance === instance) {
7379
return true;
7480
}
81+
82+
if (instance === undefined) {
83+
throw new Error('Not allowed to set context api instance to undefined.');
84+
}
7585
if (this.#discriminator) {
7686
// Notice if discriminator returns false, we do not want to setInstance.
7787
if (this.#discriminator(instance)) {
@@ -126,7 +136,7 @@ export class UmbContextConsumer<BaseType = unknown, ResultType extends BaseType
126136
this._onResponse,
127137
this.#stopAtContextMatch,
128138
);
129-
(this.#skipHost ? this._host.parentNode : this._host)?.dispatchEvent(event);
139+
(this.#skipHost ? this._retrieveHost()?.parentNode : this._retrieveHost())?.dispatchEvent(event);
130140
}
131141

132142
public hostConnected(): void {
@@ -172,7 +182,7 @@ export class UmbContextConsumer<BaseType = unknown, ResultType extends BaseType
172182

173183
public destroy(): void {
174184
this.hostDisconnected();
175-
this._host = undefined as any;
185+
this._retrieveHost = undefined as any;
176186
this.#callback = undefined;
177187
this.#promise = undefined;
178188
this.#promiseResolver = undefined;

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ export class UmbContextProvider<BaseType = unknown, ResultType extends BaseType
6868
* @memberof UmbContextProvider
6969
*/
7070
public hostConnected(): void {
71-
//this.hostElement.addEventListener(UMB_CONTENT_REQUEST_EVENT_TYPE, this.#handleContextRequest);
71+
//this.hostElement.addEventListener(UMB_CONTEXT_REQUEST_EVENT_TYPE, this.#handleContextRequest);
7272
this.#eventTarget.dispatchEvent(new UmbContextProvideEventImplementation(this.#contextAlias));
7373

7474
// Listen to our debug event 'umb:debug-contexts'
@@ -79,7 +79,7 @@ export class UmbContextProvider<BaseType = unknown, ResultType extends BaseType
7979
* @memberof UmbContextProvider
8080
*/
8181
public hostDisconnected(): void {
82-
//this.hostElement.removeEventListener(UMB_CONTENT_REQUEST_EVENT_TYPE, this.#handleContextRequest);
82+
//this.hostElement.removeEventListener(UMB_CONTEXT_REQUEST_EVENT_TYPE, this.#handleContextRequest);
8383
// Out-commented for now, but kept if we like to reintroduce this:
8484
//window.dispatchEvent(new UmbContextUnprovidedEventImplementation(this._contextAlias, this.#instance));
8585

@@ -103,6 +103,8 @@ export class UmbContextProvider<BaseType = unknown, ResultType extends BaseType
103103

104104
destroy(): void {
105105
this.hostDisconnected();
106+
// Note we are not removing the event listener in the hostDisconnected, therefor we do it here [NL].
107+
this.#eventTarget?.removeEventListener(UMB_CONTEXT_REQUEST_EVENT_TYPE, this.#handleContextRequest);
106108
// We want to call a destroy method on the instance, if it has one.
107109
(this.#instance as any)?.destroy?.();
108110
this.#instance = undefined;

src/libs/extension-api/controller/base-extension-initializer.controller.test.ts

Lines changed: 27 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,12 @@ class UmbTestConditionAlwaysInvalid extends UmbControllerBase implements UmbExte
5959

6060
describe('UmbBaseExtensionController', () => {
6161
describe('Manifest without conditions', () => {
62-
//let hostElement: UmbControllerHostElement;
62+
let hostElement: UmbControllerHostElement;
6363
let extensionRegistry: UmbExtensionRegistry<ManifestWithDynamicConditions>;
6464
let manifest: ManifestWithDynamicConditions;
6565

6666
beforeEach(async () => {
67-
//hostElement = await fixture(html`<umb-test-controller-host></umb-test-controller-host>`);
67+
hostElement = await fixture(html`<umb-test-controller-host></umb-test-controller-host>`);
6868
extensionRegistry = new UmbExtensionRegistry();
6969
manifest = {
7070
type: 'section',
@@ -74,7 +74,7 @@ describe('UmbBaseExtensionController', () => {
7474

7575
extensionRegistry.register(manifest);
7676
});
77-
/*
77+
7878
it('permits when there is no conditions', (done) => {
7979
const extensionController = new UmbTestExtensionController(
8080
hostElement,
@@ -92,16 +92,15 @@ describe('UmbBaseExtensionController', () => {
9292
},
9393
);
9494
});
95-
*/
9695
});
9796

9897
describe('Manifest with empty conditions', () => {
99-
//let hostElement: UmbControllerHostElement;
98+
let hostElement: UmbControllerHostElement;
10099
let extensionRegistry: UmbExtensionRegistry<ManifestWithDynamicConditions>;
101100
let manifest: ManifestWithDynamicConditions;
102101

103102
beforeEach(async () => {
104-
//hostElement = await fixture(html`<umb-test-controller-host></umb-test-controller-host>`);
103+
hostElement = await fixture(html`<umb-test-controller-host></umb-test-controller-host>`);
105104
extensionRegistry = new UmbExtensionRegistry();
106105
manifest = {
107106
type: 'section',
@@ -113,7 +112,6 @@ describe('UmbBaseExtensionController', () => {
113112
extensionRegistry.register(manifest);
114113
});
115114

116-
/*
117115
it('permits when there is empty conditions', (done) => {
118116
const extensionController = new UmbTestExtensionController(
119117
hostElement,
@@ -124,15 +122,14 @@ describe('UmbBaseExtensionController', () => {
124122
if (extensionController.permitted) {
125123
expect(extensionController?.manifest?.alias).to.eq('Umb.Test.Section.1');
126124

127-
// Also verifying that the promise gets resolved.
125+
// Also verifying that the promise gets resolved. [NL]
128126
extensionController.asPromise().then(() => {
129127
done();
130128
});
131129
}
132130
},
133131
);
134132
});
135-
*/
136133
});
137134

138135
describe('Manifest with valid conditions', () => {
@@ -225,14 +222,14 @@ describe('UmbBaseExtensionController', () => {
225222
if (isPermitted) {
226223
count++;
227224
if (count === 1) {
228-
// First time render, there is no conditions.
225+
// First time render, there is no conditions. [NL]
229226
expect(extensionController.manifest?.weight).to.be.equal(2);
230227
expect(extensionController.manifest?.conditions?.length).to.be.equal(1);
231228
} else if (count === 2) {
232-
// Second time render, there is conditions and weight is 22.
229+
// Second time render, there is conditions and weight is 22. [NL]
233230
expect(extensionController.manifest?.weight).to.be.equal(22);
234231
expect(extensionController.manifest?.conditions?.length).to.be.equal(1);
235-
// Check that the promise has been resolved for the first render to ensure timing is right.
232+
// Check that the promise has been resolved for the first render to ensure timing is right. [NL]
236233
expect(initialPromiseResolved).to.be.true;
237234
done();
238235
extensionController.destroy();
@@ -270,14 +267,14 @@ describe('UmbBaseExtensionController', () => {
270267
if (isPermitted) {
271268
count++;
272269
if (count === 1) {
273-
// First time render, there is no conditions.
270+
// First time render, there is no conditions. [NL]
274271
expect(extensionController.manifest?.weight).to.be.equal(3);
275272
expect(extensionController.manifest?.conditions?.length).to.be.equal(0);
276273
} else if (count === 2) {
277-
// Second time render, there is conditions and weight is 33.
274+
// Second time render, there is conditions and weight is 33. [NL]
278275
expect(extensionController.manifest?.weight).to.be.equal(33);
279276
expect(extensionController.manifest?.conditions?.length).to.be.equal(0);
280-
// Check that the promise has been resolved for the first render to ensure timing is right.
277+
// Check that the promise has been resolved for the first render to ensure timing is right. [NL]
281278
expect(initialPromiseResolved).to.be.true;
282279
done();
283280
extensionController.destroy();
@@ -315,14 +312,14 @@ describe('UmbBaseExtensionController', () => {
315312
if (isPermitted) {
316313
count++;
317314
if (count === 1) {
318-
// First time render, there is no conditions.
315+
// First time render, there is no conditions. [NL]
319316
expect(extensionController.manifest?.weight).to.be.equal(4);
320317
expect(extensionController.manifest?.conditions?.length).to.be.equal(0);
321318
} else if (count === 2) {
322-
// Second time render, there is conditions and weight is 33.
319+
// Second time render, there is conditions and weight is updated. [NL]
323320
expect(extensionController.manifest?.weight).to.be.equal(44);
324321
expect(extensionController.manifest?.conditions?.length).to.be.equal(1);
325-
// Check that the promise has been resolved for the first render to ensure timing is right.
322+
// Check that the promise has been resolved for the first render to ensure timing is right. [NL]
326323
expect(initialPromiseResolved).to.be.true;
327324
done();
328325
extensionController.destroy();
@@ -370,14 +367,14 @@ describe('UmbBaseExtensionController', () => {
370367
if (isPermitted) {
371368
count++;
372369
if (count === 1) {
373-
// First time render, there is no conditions.
370+
// First time render, there is no conditions. [NL]
374371
expect(extensionController.manifest?.weight).to.be.undefined;
375372
expect(extensionController.manifest?.conditions?.length).to.be.equal(0);
376373
} else if (count === 2) {
377-
// Second time render, there is a matching kind and then weight is 123.
374+
// Second time render, there is a matching kind and then weight is 123. [NL]
378375
expect(extensionController.manifest?.weight).to.be.equal(123);
379376
expect(extensionController.manifest?.conditions?.length).to.be.equal(0);
380-
// Check that the promise has been resolved for the first render to ensure timing is right.
377+
// Check that the promise has been resolved for the first render to ensure timing is right. [NL]
381378
expect(initialPromiseResolved).to.be.true;
382379
done();
383380
extensionController.destroy();
@@ -433,7 +430,7 @@ describe('UmbBaseExtensionController', () => {
433430
'Umb.Test.Section.1',
434431
() => {
435432
// This should not be called.
436-
expect(true).to.be.false;
433+
expect.fail('Callback should not be called when never permitted');
437434
},
438435
);
439436
Promise.resolve().then(() => {
@@ -451,7 +448,7 @@ describe('UmbBaseExtensionController', () => {
451448
'Umb.Test.Section.1',
452449
() => {
453450
// This should not be called.
454-
expect(true).to.be.false;
451+
expect.fail('Callback should not be called when never permitted');
455452
},
456453
);
457454

@@ -531,7 +528,7 @@ describe('UmbBaseExtensionController', () => {
531528
'Umb.Test.Section.1',
532529
async () => {
533530
count++;
534-
// We want the controller callback to first fire when conditions are initialized.
531+
// We want the controller callback to first fire when conditions are initialized. [NL]
535532
expect(extensionController.manifest?.conditions?.length).to.be.equal(1);
536533
expect(extensionController?.manifest?.alias).to.eq('Umb.Test.Section.1');
537534
if (count === 1) {
@@ -596,29 +593,30 @@ describe('UmbBaseExtensionController', () => {
596593
'Umb.Test.Section.1',
597594
async (isPermitted) => {
598595
count++;
599-
// We want the controller callback to first fire when conditions are initialized.
596+
// We want the controller callback to first fire when conditions are initialized. [NL]
600597
expect(extensionController.manifest?.conditions?.length).to.be.equal(2);
601598
expect(extensionController?.manifest?.alias).to.eq('Umb.Test.Section.1');
602599
if (count === 1) {
603600
expect(isPermitted).to.be.true;
604601
expect(extensionController?.permitted).to.be.true;
605-
// Hack to double check that its two conditions that make up the state:
602+
// Hack to double check that its two conditions that make up the state: [NL]
606603
expect(
607604
extensionController.getUmbControllers((controller) => (controller as any).permitted).length,
608605
).to.equal(2);
609606
} else if (count === 2) {
610607
expect(isPermitted).to.be.false;
611608
expect(extensionController?.permitted).to.be.false;
612-
// Hack to double check that its two conditions that make up the state, in this case its one, cause we already got the callback when one of the conditions changed. meaning in this split second one is still good:
609+
// Hack to double check that its two conditions that make up the state, in this case its one, cause we already got the callback when one of the conditions changed. meaning in this split second one is still good: [NL]
613610
expect(
614611
extensionController.getUmbControllers((controller) => (controller as any).permitted).length,
615612
).to.equal(1);
616613

617614
// Then we are done:
618615
extensionController.destroy(); // End this test.
619-
setTimeout(() => done(), 60); // Lets wait another round of the conditions approve/disapprove, just to see if the destroy stopped the conditions. (60ms, as that should be enough to test that another round does not happen.)
616+
setTimeout(() => done(), 60); // Lets wait another round of the conditions approve/disapprove, just to see if the destroy stopped the conditions. (60ms, as that should be enough to test that another round does not happen.) [NL]
620617
} else if (count === 5) {
621-
expect(false).to.be.true; // This should not be called.
618+
// This should not be called.
619+
expect.fail('Callback should not be called when never permitted');
622620
}
623621
},
624622
);

0 commit comments

Comments
 (0)