diff --git a/packages/field/index.js b/packages/field/index.js
index 3c35e3206..3dff79439 100644
--- a/packages/field/index.js
+++ b/packages/field/index.js
@@ -6,3 +6,4 @@ export { InteractionStateMixin } from './src/InteractionStateMixin.js'; // appli
export { LionField } from './src/LionField.js';
export { FormRegisteringMixin } from './src/FormRegisteringMixin.js';
export { FormRegistrarMixin } from './src/FormRegistrarMixin.js';
+export { FormRegistrarPortalMixin } from './src/FormRegistrarPortalMixin.js';
diff --git a/packages/field/src/FormRegistrarPortalMixin.js b/packages/field/src/FormRegistrarPortalMixin.js
index f0e800c56..1f9e31c2e 100644
--- a/packages/field/src/FormRegistrarPortalMixin.js
+++ b/packages/field/src/FormRegistrarPortalMixin.js
@@ -2,7 +2,16 @@ import { dedupeMixin } from '@lion/core';
import { formRegistrarManager } from './formRegistrarManager.js';
/**
- * This will forward
+ * This allows to register fields within a form even though they are not within the same dom tree.
+ * It does that by redispatching the event on the registration target.
+ * Neither form or field need to know about the portal. It acts as if the field is part of the dom tree.
+ *
+ * @example
+ *
+ *
+ *
+ *
+ * // my-field will be registered within my-form
*/
export const FormRegistrarPortalMixin = dedupeMixin(
superclass =>
@@ -11,6 +20,7 @@ export const FormRegistrarPortalMixin = dedupeMixin(
constructor() {
super();
this.formElements = [];
+ this.registrationTarget = undefined;
this.__readyForRegistration = false;
this.registrationReady = new Promise(resolve => {
this.__resolveRegistrationReady = resolve;
@@ -21,13 +31,16 @@ export const FormRegistrarPortalMixin = dedupeMixin(
if (super.connectedCallback) {
super.connectedCallback();
}
+ this.__checkRegistrationTarget();
+
formRegistrarManager.add(this);
+
this.__redispatchEventForFormRegistrarPortalMixin = ev => {
ev.stopPropagation();
- // TODO: fire event with changed ev.target
- this.dispatchEvent(
+ // TODO: change ev.target to original registering element
+ this.registrationTarget.dispatchEvent(
new CustomEvent('form-element-register', {
- detail: { element: ev.element },
+ detail: { element: ev.detail.element },
bubbles: true,
}),
);
@@ -43,6 +56,10 @@ export const FormRegistrarPortalMixin = dedupeMixin(
super.disconnectedCallback();
}
formRegistrarManager.remove(this);
+ this.removeEventListener(
+ 'form-element-register',
+ this.__redispatchEventForFormRegistrarPortalMixin,
+ );
}
firstUpdated(changedProperties) {
@@ -51,5 +68,11 @@ export const FormRegistrarPortalMixin = dedupeMixin(
this.__readyForRegistration = true;
formRegistrarManager.becomesReady(this);
}
+
+ __checkRegistrationTarget() {
+ if (!this.registrationTarget) {
+ throw new Error('A FormRegistrarPortal element requires a .registrationTarget');
+ }
+ }
},
);
diff --git a/packages/field/test-suites/FormRegistrationMixins.suite.js b/packages/field/test-suites/FormRegistrationMixins.suite.js
index cfbc97b91..ab2b9ac12 100644
--- a/packages/field/test-suites/FormRegistrationMixins.suite.js
+++ b/packages/field/test-suites/FormRegistrationMixins.suite.js
@@ -1,5 +1,6 @@
import { expect, fixture, html, defineCE, unsafeStatic } from '@open-wc/testing';
import { LitElement } from '@lion/core';
+import sinon from 'sinon';
import { FormRegistrarMixin } from '../src/FormRegistrarMixin.js';
import { FormRegisteringMixin } from '../src/FormRegisteringMixin.js';
@@ -17,6 +18,7 @@ export const runRegistrationSuite = customConfig => {
let parentTag;
let childTag;
let portalTag;
+ let portalTagString;
before(async () => {
if (!cfg.parentTagString) {
@@ -30,6 +32,7 @@ export const runRegistrationSuite = customConfig => {
}
parentTag = unsafeStatic(cfg.parentTagString);
+ portalTagString = cfg.portalTagString;
childTag = unsafeStatic(cfg.childTagString);
portalTag = unsafeStatic(cfg.portalTagString);
});
@@ -126,8 +129,11 @@ export const runRegistrationSuite = customConfig => {
describe('FormRegistrarPortalMixin', () => {
it('throws if there is no .registrationTarget', async () => {
- expect(async () => {
- await fixture(html`<${portalTag}>${portalTag}>`);
+ // we test the private api directly as errors thrown from a web component are in a
+ // different context and we can not catch them here
+ const el = document.createElement(portalTagString);
+ expect(() => {
+ el.__checkRegistrationTarget();
}).to.throw('A FormRegistrarPortal element requires a .registrationTarget');
});
@@ -162,6 +168,21 @@ export const runRegistrationSuite = customConfig => {
expect(el.formElements.length).to.equal(1);
});
+ // find a proper way to do this on polyfilled browsers
+ it.skip('fires event "form-element-register" with the child as ev.target', async () => {
+ const registerSpy = sinon.spy();
+ const el = await fixture(
+ html`<${parentTag} @form-element-register=${registerSpy}>${parentTag}>`,
+ );
+ const portal = await fixture(html`
+ <${portalTag} .registrationTarget=${el}>
+ <${childTag}>${childTag}>
+ ${portalTag}>
+ `);
+ const childEl = portal.children[0];
+ expect(registerSpy.args[2][0].target.tagName).to.equal(childEl.tagName);
+ });
+
it('works for portals that have a delayed render', async () => {
const delayedPortalString = defineCE(
class extends FormRegistrarPortalMixin(LitElement) {