fix(field): implement FormRegistrarPortalMixin
This commit is contained in:
parent
f8a3c54f60
commit
2cb6dee6d9
3 changed files with 51 additions and 6 deletions
|
|
@ -6,3 +6,4 @@ export { InteractionStateMixin } from './src/InteractionStateMixin.js'; // appli
|
||||||
export { LionField } from './src/LionField.js';
|
export { LionField } from './src/LionField.js';
|
||||||
export { FormRegisteringMixin } from './src/FormRegisteringMixin.js';
|
export { FormRegisteringMixin } from './src/FormRegisteringMixin.js';
|
||||||
export { FormRegistrarMixin } from './src/FormRegistrarMixin.js';
|
export { FormRegistrarMixin } from './src/FormRegistrarMixin.js';
|
||||||
|
export { FormRegistrarPortalMixin } from './src/FormRegistrarPortalMixin.js';
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,16 @@ import { dedupeMixin } from '@lion/core';
|
||||||
import { formRegistrarManager } from './formRegistrarManager.js';
|
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-form></my-form>
|
||||||
|
* <my-portal .registrationTarget=${document.querySelector('my-form')}>
|
||||||
|
* <my-field></my-field>
|
||||||
|
* </my-portal>
|
||||||
|
* // my-field will be registered within my-form
|
||||||
*/
|
*/
|
||||||
export const FormRegistrarPortalMixin = dedupeMixin(
|
export const FormRegistrarPortalMixin = dedupeMixin(
|
||||||
superclass =>
|
superclass =>
|
||||||
|
|
@ -11,6 +20,7 @@ export const FormRegistrarPortalMixin = dedupeMixin(
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.formElements = [];
|
this.formElements = [];
|
||||||
|
this.registrationTarget = undefined;
|
||||||
this.__readyForRegistration = false;
|
this.__readyForRegistration = false;
|
||||||
this.registrationReady = new Promise(resolve => {
|
this.registrationReady = new Promise(resolve => {
|
||||||
this.__resolveRegistrationReady = resolve;
|
this.__resolveRegistrationReady = resolve;
|
||||||
|
|
@ -21,13 +31,16 @@ export const FormRegistrarPortalMixin = dedupeMixin(
|
||||||
if (super.connectedCallback) {
|
if (super.connectedCallback) {
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
}
|
}
|
||||||
|
this.__checkRegistrationTarget();
|
||||||
|
|
||||||
formRegistrarManager.add(this);
|
formRegistrarManager.add(this);
|
||||||
|
|
||||||
this.__redispatchEventForFormRegistrarPortalMixin = ev => {
|
this.__redispatchEventForFormRegistrarPortalMixin = ev => {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
// TODO: fire event with changed ev.target
|
// TODO: change ev.target to original registering element
|
||||||
this.dispatchEvent(
|
this.registrationTarget.dispatchEvent(
|
||||||
new CustomEvent('form-element-register', {
|
new CustomEvent('form-element-register', {
|
||||||
detail: { element: ev.element },
|
detail: { element: ev.detail.element },
|
||||||
bubbles: true,
|
bubbles: true,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
@ -43,6 +56,10 @@ export const FormRegistrarPortalMixin = dedupeMixin(
|
||||||
super.disconnectedCallback();
|
super.disconnectedCallback();
|
||||||
}
|
}
|
||||||
formRegistrarManager.remove(this);
|
formRegistrarManager.remove(this);
|
||||||
|
this.removeEventListener(
|
||||||
|
'form-element-register',
|
||||||
|
this.__redispatchEventForFormRegistrarPortalMixin,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
firstUpdated(changedProperties) {
|
firstUpdated(changedProperties) {
|
||||||
|
|
@ -51,5 +68,11 @@ export const FormRegistrarPortalMixin = dedupeMixin(
|
||||||
this.__readyForRegistration = true;
|
this.__readyForRegistration = true;
|
||||||
formRegistrarManager.becomesReady(this);
|
formRegistrarManager.becomesReady(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
__checkRegistrationTarget() {
|
||||||
|
if (!this.registrationTarget) {
|
||||||
|
throw new Error('A FormRegistrarPortal element requires a .registrationTarget');
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { expect, fixture, html, defineCE, unsafeStatic } from '@open-wc/testing';
|
import { expect, fixture, html, defineCE, unsafeStatic } from '@open-wc/testing';
|
||||||
import { LitElement } from '@lion/core';
|
import { LitElement } from '@lion/core';
|
||||||
|
import sinon from 'sinon';
|
||||||
|
|
||||||
import { FormRegistrarMixin } from '../src/FormRegistrarMixin.js';
|
import { FormRegistrarMixin } from '../src/FormRegistrarMixin.js';
|
||||||
import { FormRegisteringMixin } from '../src/FormRegisteringMixin.js';
|
import { FormRegisteringMixin } from '../src/FormRegisteringMixin.js';
|
||||||
|
|
@ -17,6 +18,7 @@ export const runRegistrationSuite = customConfig => {
|
||||||
let parentTag;
|
let parentTag;
|
||||||
let childTag;
|
let childTag;
|
||||||
let portalTag;
|
let portalTag;
|
||||||
|
let portalTagString;
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
if (!cfg.parentTagString) {
|
if (!cfg.parentTagString) {
|
||||||
|
|
@ -30,6 +32,7 @@ export const runRegistrationSuite = customConfig => {
|
||||||
}
|
}
|
||||||
|
|
||||||
parentTag = unsafeStatic(cfg.parentTagString);
|
parentTag = unsafeStatic(cfg.parentTagString);
|
||||||
|
portalTagString = cfg.portalTagString;
|
||||||
childTag = unsafeStatic(cfg.childTagString);
|
childTag = unsafeStatic(cfg.childTagString);
|
||||||
portalTag = unsafeStatic(cfg.portalTagString);
|
portalTag = unsafeStatic(cfg.portalTagString);
|
||||||
});
|
});
|
||||||
|
|
@ -126,8 +129,11 @@ export const runRegistrationSuite = customConfig => {
|
||||||
|
|
||||||
describe('FormRegistrarPortalMixin', () => {
|
describe('FormRegistrarPortalMixin', () => {
|
||||||
it('throws if there is no .registrationTarget', async () => {
|
it('throws if there is no .registrationTarget', async () => {
|
||||||
expect(async () => {
|
// we test the private api directly as errors thrown from a web component are in a
|
||||||
await fixture(html`<${portalTag}></${portalTag}>`);
|
// 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');
|
}).to.throw('A FormRegistrarPortal element requires a .registrationTarget');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -162,6 +168,21 @@ export const runRegistrationSuite = customConfig => {
|
||||||
expect(el.formElements.length).to.equal(1);
|
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 () => {
|
it('works for portals that have a delayed render', async () => {
|
||||||
const delayedPortalString = defineCE(
|
const delayedPortalString = defineCE(
|
||||||
class extends FormRegistrarPortalMixin(LitElement) {
|
class extends FormRegistrarPortalMixin(LitElement) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue