Skip to content

Commit 2cb6dee

Browse files
committed
fix(field): implement FormRegistrarPortalMixin
1 parent f8a3c54 commit 2cb6dee

File tree

3 files changed

+51
-6
lines changed

3 files changed

+51
-6
lines changed

packages/field/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ export { InteractionStateMixin } from './src/InteractionStateMixin.js'; // appli
66
export { LionField } from './src/LionField.js';
77
export { FormRegisteringMixin } from './src/FormRegisteringMixin.js';
88
export { FormRegistrarMixin } from './src/FormRegistrarMixin.js';
9+
export { FormRegistrarPortalMixin } from './src/FormRegistrarPortalMixin.js';

packages/field/src/FormRegistrarPortalMixin.js

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,16 @@ import { dedupeMixin } from '@lion/core';
22
import { formRegistrarManager } from './formRegistrarManager.js';
33

44
/**
5-
* This will forward
5+
* This allows to register fields within a form even though they are not within the same dom tree.
6+
* It does that by redispatching the event on the registration target.
7+
* Neither form or field need to know about the portal. It acts as if the field is part of the dom tree.
8+
*
9+
* @example
10+
* <my-form></my-form>
11+
* <my-portal .registrationTarget=${document.querySelector('my-form')}>
12+
* <my-field></my-field>
13+
* </my-portal>
14+
* // my-field will be registered within my-form
615
*/
716
export const FormRegistrarPortalMixin = dedupeMixin(
817
superclass =>
@@ -11,6 +20,7 @@ export const FormRegistrarPortalMixin = dedupeMixin(
1120
constructor() {
1221
super();
1322
this.formElements = [];
23+
this.registrationTarget = undefined;
1424
this.__readyForRegistration = false;
1525
this.registrationReady = new Promise(resolve => {
1626
this.__resolveRegistrationReady = resolve;
@@ -21,13 +31,16 @@ export const FormRegistrarPortalMixin = dedupeMixin(
2131
if (super.connectedCallback) {
2232
super.connectedCallback();
2333
}
34+
this.__checkRegistrationTarget();
35+
2436
formRegistrarManager.add(this);
37+
2538
this.__redispatchEventForFormRegistrarPortalMixin = ev => {
2639
ev.stopPropagation();
27-
// TODO: fire event with changed ev.target
28-
this.dispatchEvent(
40+
// TODO: change ev.target to original registering element
41+
this.registrationTarget.dispatchEvent(
2942
new CustomEvent('form-element-register', {
30-
detail: { element: ev.element },
43+
detail: { element: ev.detail.element },
3144
bubbles: true,
3245
}),
3346
);
@@ -43,6 +56,10 @@ export const FormRegistrarPortalMixin = dedupeMixin(
4356
super.disconnectedCallback();
4457
}
4558
formRegistrarManager.remove(this);
59+
this.removeEventListener(
60+
'form-element-register',
61+
this.__redispatchEventForFormRegistrarPortalMixin,
62+
);
4663
}
4764

4865
firstUpdated(changedProperties) {
@@ -51,5 +68,11 @@ export const FormRegistrarPortalMixin = dedupeMixin(
5168
this.__readyForRegistration = true;
5269
formRegistrarManager.becomesReady(this);
5370
}
71+
72+
__checkRegistrationTarget() {
73+
if (!this.registrationTarget) {
74+
throw new Error('A FormRegistrarPortal element requires a .registrationTarget');
75+
}
76+
}
5477
},
5578
);

packages/field/test-suites/FormRegistrationMixins.suite.js

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { expect, fixture, html, defineCE, unsafeStatic } from '@open-wc/testing';
22
import { LitElement } from '@lion/core';
3+
import sinon from 'sinon';
34

45
import { FormRegistrarMixin } from '../src/FormRegistrarMixin.js';
56
import { FormRegisteringMixin } from '../src/FormRegisteringMixin.js';
@@ -17,6 +18,7 @@ export const runRegistrationSuite = customConfig => {
1718
let parentTag;
1819
let childTag;
1920
let portalTag;
21+
let portalTagString;
2022

2123
before(async () => {
2224
if (!cfg.parentTagString) {
@@ -30,6 +32,7 @@ export const runRegistrationSuite = customConfig => {
3032
}
3133

3234
parentTag = unsafeStatic(cfg.parentTagString);
35+
portalTagString = cfg.portalTagString;
3336
childTag = unsafeStatic(cfg.childTagString);
3437
portalTag = unsafeStatic(cfg.portalTagString);
3538
});
@@ -126,8 +129,11 @@ export const runRegistrationSuite = customConfig => {
126129

127130
describe('FormRegistrarPortalMixin', () => {
128131
it('throws if there is no .registrationTarget', async () => {
129-
expect(async () => {
130-
await fixture(html`<${portalTag}></${portalTag}>`);
132+
// we test the private api directly as errors thrown from a web component are in a
133+
// different context and we can not catch them here
134+
const el = document.createElement(portalTagString);
135+
expect(() => {
136+
el.__checkRegistrationTarget();
131137
}).to.throw('A FormRegistrarPortal element requires a .registrationTarget');
132138
});
133139

@@ -162,6 +168,21 @@ export const runRegistrationSuite = customConfig => {
162168
expect(el.formElements.length).to.equal(1);
163169
});
164170

171+
// find a proper way to do this on polyfilled browsers
172+
it.skip('fires event "form-element-register" with the child as ev.target', async () => {
173+
const registerSpy = sinon.spy();
174+
const el = await fixture(
175+
html`<${parentTag} @form-element-register=${registerSpy}></${parentTag}>`,
176+
);
177+
const portal = await fixture(html`
178+
<${portalTag} .registrationTarget=${el}>
179+
<${childTag}></${childTag}>
180+
</${portalTag}>
181+
`);
182+
const childEl = portal.children[0];
183+
expect(registerSpy.args[2][0].target.tagName).to.equal(childEl.tagName);
184+
});
185+
165186
it('works for portals that have a delayed render', async () => {
166187
const delayedPortalString = defineCE(
167188
class extends FormRegistrarPortalMixin(LitElement) {

0 commit comments

Comments
 (0)