From 2cb6dee6d9b1a5913a51cd9002a1f1023e81b014 Mon Sep 17 00:00:00 2001 From: Thomas Allmer Date: Fri, 23 Aug 2019 13:23:53 +0200 Subject: [PATCH] fix(field): implement FormRegistrarPortalMixin --- packages/field/index.js | 1 + .../field/src/FormRegistrarPortalMixin.js | 31 ++++++++++++++++--- .../FormRegistrationMixins.suite.js | 25 +++++++++++++-- 3 files changed, 51 insertions(+), 6 deletions(-) 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}>`); + // 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}>`, + ); + const portal = await fixture(html` + <${portalTag} .registrationTarget=${el}> + <${childTag}> + + `); + 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) {