@lion/form-core
Advanced tools
Comparing version 0.2.6 to 0.3.0
@@ -6,2 +6,13 @@ # Change Log | ||
# [0.3.0](https://github.com/ing-bank/lion/compare/@lion/form-core@0.2.6...@lion/form-core@0.3.0) (2020-07-27) | ||
### Features | ||
* synchronous form registration system ([8698f73](https://github.com/ing-bank/lion/commit/8698f734186eb88c4669bbadf8d5ae461f1c27f5)) | ||
## [0.2.6](https://github.com/ing-bank/lion/compare/@lion/form-core@0.2.5...@lion/form-core@0.2.6) (2020-07-16) | ||
@@ -8,0 +19,0 @@ |
{ | ||
"name": "@lion/form-core", | ||
"version": "0.2.6", | ||
"version": "0.3.0", | ||
"description": "Form-core contains all essential building blocks for creating form fields and fieldsets", | ||
@@ -46,3 +46,3 @@ "license": "MIT", | ||
}, | ||
"gitHead": "48b145fc22a4a4206a40e657bd4f729c541d0270" | ||
"gitHead": "bb4190d5212d570c0572c77a5946db250ad402da" | ||
} |
@@ -33,3 +33,10 @@ import { dedupeMixin } from '@lion/core'; | ||
set modelValue(value) { | ||
this._setCheckedElements(value, (el, val) => el.modelValue.value === val); | ||
if (this.__isInitialModelValue) { | ||
this.__isInitialModelValue = false; | ||
this.registrationComplete.then(() => { | ||
this._setCheckedElements(value, (el, val) => el.modelValue.value === val); | ||
}); | ||
} else { | ||
this._setCheckedElements(value, (el, val) => el.modelValue.value === val); | ||
} | ||
} | ||
@@ -54,3 +61,10 @@ | ||
set serializedValue(value) { | ||
this._setCheckedElements(value, (el, val) => el.serializedValue.value === val); | ||
if (this.__isInitialSerializedValue) { | ||
this.__isInitialSerializedValue = false; | ||
this.registrationComplete.then(() => { | ||
this._setCheckedElements(value, (el, val) => el.serializedValue.value === val); | ||
}); | ||
} else { | ||
this._setCheckedElements(value, (el, val) => el.serializedValue.value === val); | ||
} | ||
} | ||
@@ -62,4 +76,47 @@ | ||
this._repropagationRole = 'choice-group'; // configures event propagation logic of FormControlMixin | ||
this.__isInitialModelValue = true; | ||
this.__isInitialSerializedValue = true; | ||
this.registrationComplete = new Promise((resolve, reject) => { | ||
this.__resolveRegistrationComplete = resolve; | ||
this.__rejectRegistrationComplete = reject; | ||
}); | ||
this.registrationComplete.done = false; | ||
this.registrationComplete.then( | ||
() => { | ||
this.registrationComplete.done = true; | ||
}, | ||
() => { | ||
this.registrationComplete.done = true; | ||
throw new Error( | ||
'Registration could not finish. Please use await el.registrationComplete;', | ||
); | ||
}, | ||
); | ||
} | ||
connectedCallback() { | ||
super.connectedCallback(); | ||
this.__registrationCompleteTimer = setTimeout(() => { | ||
this.__resolveRegistrationComplete(); | ||
}); | ||
this.registrationComplete.then(() => { | ||
this.__isInitialModelValue = false; | ||
this.__isInitialSerializedValue = false; | ||
}); | ||
} | ||
disconnectedCallback() { | ||
if (super.disconnectedCallback) { | ||
super.disconnectedCallback(); | ||
} | ||
clearTimeout(this.__registrationCompleteTimer); | ||
if (this.registrationComplete.done === false) { | ||
this.__rejectRegistrationComplete(); | ||
} | ||
} | ||
/** | ||
@@ -75,2 +132,11 @@ * @override from FormRegistrarMixin | ||
/** | ||
* @override from FormControlMixin | ||
*/ | ||
_triggerInitialModelValueChangedEvent() { | ||
this.registrationComplete.then(() => { | ||
this.__dispatchInitialModelValueChangedEvent(); | ||
}); | ||
} | ||
/** | ||
* @override | ||
@@ -136,7 +202,3 @@ */ | ||
async _setCheckedElements(value, check) { | ||
if (!this.__readyForRegistration) { | ||
await this.registrationReady; | ||
} | ||
_setCheckedElements(value, check) { | ||
for (let i = 0; i < this.formElements.length; i += 1) { | ||
@@ -143,0 +205,0 @@ if (this.multipleChoice) { |
@@ -79,3 +79,10 @@ import { dedupeMixin, html, SlotMixin } from '@lion/core'; | ||
set modelValue(values) { | ||
this._setValueMapForAllFormElements('modelValue', values); | ||
if (this.__isInitialModelValue) { | ||
this.__isInitialModelValue = false; | ||
this.registrationComplete.then(() => { | ||
this._setValueMapForAllFormElements('modelValue', values); | ||
}); | ||
} else { | ||
this._setValueMapForAllFormElements('modelValue', values); | ||
} | ||
} | ||
@@ -88,3 +95,10 @@ | ||
set serializedValue(values) { | ||
this._setValueMapForAllFormElements('serializedValue', values); | ||
if (this.__isInitialSerializedValue) { | ||
this.__isInitialSerializedValue = false; | ||
this.registrationComplete.then(() => { | ||
this._setValueMapForAllFormElements('serializedValue', values); | ||
}); | ||
} else { | ||
this._setValueMapForAllFormElements('serializedValue', values); | ||
} | ||
} | ||
@@ -112,2 +126,4 @@ | ||
this.__addedSubValidators = false; | ||
this.__isInitialModelValue = true; | ||
this.__isInitialSerializedValue = true; | ||
@@ -122,13 +138,40 @@ this._checkForOutsideClick = this._checkForOutsideClick.bind(this); | ||
this.defaultValidators = [new FormElementsHaveNoError()]; | ||
this.registrationComplete = new Promise((resolve, reject) => { | ||
this.__resolveRegistrationComplete = resolve; | ||
this.__rejectRegistrationComplete = reject; | ||
}); | ||
this.registrationComplete.done = false; | ||
this.registrationComplete.then( | ||
() => { | ||
this.registrationComplete.done = true; | ||
}, | ||
() => { | ||
this.registrationComplete.done = true; | ||
throw new Error( | ||
'Registration could not finish. Please use await el.registrationComplete;', | ||
); | ||
}, | ||
); | ||
} | ||
connectedCallback() { | ||
// eslint-disable-next-line wc/guard-super-call | ||
super.connectedCallback(); | ||
this.setAttribute('role', 'group'); | ||
this.__initInteractionStates(); | ||
this.__registrationCompleteTimer = setTimeout(() => { | ||
this.__resolveRegistrationComplete(); | ||
}); | ||
this.registrationComplete.then(() => { | ||
this.__isInitialModelValue = false; | ||
this.__isInitialSerializedValue = false; | ||
this.__initInteractionStates(); | ||
}); | ||
} | ||
disconnectedCallback() { | ||
super.disconnectedCallback(); // eslint-disable-line wc/guard-super-call | ||
if (super.disconnectedCallback) { | ||
super.disconnectedCallback(); | ||
} | ||
@@ -139,8 +182,9 @@ if (this.__hasActiveOutsideClickHandling) { | ||
} | ||
clearTimeout(this.__registrationCompleteTimer); | ||
if (this.registrationComplete.done === false) { | ||
this.__rejectRegistrationComplete(); | ||
} | ||
} | ||
async __initInteractionStates() { | ||
if (!this.registrationHasCompleted) { | ||
await this.registrationComplete; | ||
} | ||
__initInteractionStates() { | ||
this.formElements.forEach(el => { | ||
@@ -153,2 +197,11 @@ if (typeof el.initInteractionState === 'function') { | ||
/** | ||
* @override from FormControlMixin | ||
*/ | ||
_triggerInitialModelValueChangedEvent() { | ||
this.registrationComplete.then(() => { | ||
this.__dispatchInitialModelValueChangedEvent(); | ||
}); | ||
} | ||
updated(changedProperties) { | ||
@@ -278,6 +331,3 @@ super.updated(changedProperties); | ||
async _setValueForAllFormElements(property, value) { | ||
if (!this.__readyForRegistration) { | ||
await this.registrationReady; | ||
} | ||
_setValueForAllFormElements(property, value) { | ||
this.formElements.forEach(el => { | ||
@@ -288,7 +338,3 @@ el[property] = value; // eslint-disable-line no-param-reassign | ||
async _setValueMapForAllFormElements(property, values) { | ||
if (!this.__readyForRegistration) { | ||
await this.registrationReady; | ||
} | ||
_setValueMapForAllFormElements(property, values) { | ||
if (values && typeof values === 'object') { | ||
@@ -295,0 +341,0 @@ Object.keys(values).forEach(name => { |
@@ -177,4 +177,9 @@ import { css, dedupeMixin, html, nothing, SlotMixin } from '@lion/core'; | ||
this._enhanceLightDomA11y(); | ||
this._triggerInitialModelValueChangedEvent(); | ||
} | ||
_triggerInitialModelValueChangedEvent() { | ||
this.__dispatchInitialModelValueChangedEvent(); | ||
} | ||
_enhanceLightDomClasses() { | ||
@@ -594,8 +599,3 @@ if (this._inputNode) { | ||
firstUpdated(changedProperties) { | ||
super.firstUpdated(changedProperties); | ||
this.__dispatchInitialModelValueChangedEvent(); | ||
} | ||
async __dispatchInitialModelValueChangedEvent() { | ||
__dispatchInitialModelValueChangedEvent() { | ||
// When we are not a fieldset / choice-group, we don't need to wait for our children | ||
@@ -607,3 +607,2 @@ // to send a unified event | ||
await this.registrationComplete; | ||
// Initially we don't repropagate model-value-changed events coming | ||
@@ -610,0 +609,0 @@ // from children. On firstUpdated we re-dispatch this event to maintain |
import { dedupeMixin } from '@lion/core'; | ||
import { formRegistrarManager } from './formRegistrarManager.js'; | ||
@@ -16,7 +15,2 @@ /** | ||
class FormRegisteringMixin extends superclass { | ||
constructor() { | ||
super(); | ||
this.__boundDispatchRegistration = this._dispatchRegistration.bind(this); | ||
} | ||
connectedCallback() { | ||
@@ -26,24 +20,2 @@ if (super.connectedCallback) { | ||
} | ||
this.__setupRegistrationHook(); | ||
} | ||
disconnectedCallback() { | ||
if (super.disconnectedCallback) { | ||
super.disconnectedCallback(); | ||
} | ||
this._unregisterFormElement(); | ||
} | ||
__setupRegistrationHook() { | ||
if (formRegistrarManager.ready) { | ||
this._dispatchRegistration(); | ||
} else { | ||
formRegistrarManager.addEventListener( | ||
'all-forms-open-for-registration', | ||
this.__boundDispatchRegistration, | ||
); | ||
} | ||
} | ||
_dispatchRegistration() { | ||
this.dispatchEvent( | ||
@@ -55,9 +27,8 @@ new CustomEvent('form-element-register', { | ||
); | ||
formRegistrarManager.removeEventListener( | ||
'all-forms-open-for-registration', | ||
this.__boundDispatchRegistration, | ||
); | ||
} | ||
_unregisterFormElement() { | ||
disconnectedCallback() { | ||
if (super.disconnectedCallback) { | ||
super.disconnectedCallback(); | ||
} | ||
if (this.__parentFormGroup) { | ||
@@ -64,0 +35,0 @@ this.__parentFormGroup.removeFormElement(this); |
// eslint-disable-next-line max-classes-per-file | ||
import { dedupeMixin } from '@lion/core'; | ||
import { FormRegisteringMixin } from './FormRegisteringMixin.js'; | ||
import { formRegistrarManager } from './formRegistrarManager.js'; | ||
import { FormControlsCollection } from './FormControlsCollection.js'; | ||
@@ -34,3 +33,3 @@ | ||
*/ | ||
_isFormOrFieldset: Boolean, | ||
_isFormOrFieldset: { type: Boolean }, | ||
}; | ||
@@ -45,11 +44,2 @@ } | ||
this.__readyForRegistration = false; | ||
this.__hasBeenRendered = false; | ||
this.registrationReady = new Promise(resolve => { | ||
this.__resolveRegistrationReady = resolve; | ||
}); | ||
this.registrationComplete = new Promise(resolve => { | ||
this.__resolveRegistrationComplete = resolve; | ||
}); | ||
this._onRequestToAddFormElement = this._onRequestToAddFormElement.bind(this); | ||
@@ -59,19 +49,2 @@ this.addEventListener('form-element-register', this._onRequestToAddFormElement); | ||
connectedCallback() { | ||
if (super.connectedCallback) { | ||
super.connectedCallback(); | ||
} | ||
formRegistrarManager.add(this); | ||
if (this.__hasBeenRendered) { | ||
formRegistrarManager.becomesReady(); | ||
} | ||
} | ||
disconnectedCallback() { | ||
if (super.disconnectedCallback) { | ||
super.disconnectedCallback(); | ||
} | ||
formRegistrarManager.remove(this); | ||
} | ||
isRegisteredFormElement(el) { | ||
@@ -81,20 +54,2 @@ return this.formElements.some(exitingEl => exitingEl === el); | ||
firstUpdated(changedProperties) { | ||
super.firstUpdated(changedProperties); | ||
this.__resolveRegistrationReady(); | ||
this.__readyForRegistration = true; | ||
// After we allow our children to register, we need to wait one tick before they | ||
// all sent their 'form-element-register' event. | ||
// TODO: allow developer to delay this moment, similar to LitElement.performUpdate can be | ||
// delayed. | ||
setTimeout(() => { | ||
this.registrationHasCompleted = true; | ||
this.__resolveRegistrationComplete(); | ||
}); | ||
formRegistrarManager.becomesReady(); | ||
this.__hasBeenRendered = true; | ||
} | ||
addFormElement(child, indexToInsertAt) { | ||
@@ -101,0 +56,0 @@ // This is a way to let the child element (a lion-fieldset or lion-field) know, about its parent |
import { dedupeMixin } from '@lion/core'; | ||
import { formRegistrarManager } from './formRegistrarManager.js'; | ||
@@ -22,30 +21,6 @@ /** | ||
super(); | ||
this.formElements = []; | ||
this.registrationTarget = undefined; | ||
this.__hasBeenRendered = false; | ||
this.__readyForRegistration = false; | ||
this.registrationReady = new Promise(resolve => { | ||
this.__resolveRegistrationReady = resolve; | ||
}); | ||
} | ||
connectedCallback() { | ||
if (super.connectedCallback) { | ||
super.connectedCallback(); | ||
} | ||
formRegistrarManager.add(this); | ||
if (this.__hasBeenRendered) { | ||
formRegistrarManager.becomesReady(); | ||
} | ||
this.__redispatchEventForFormRegistrarPortalMixin = ev => { | ||
ev.stopPropagation(); | ||
this.registrationTarget.dispatchEvent( | ||
new CustomEvent('form-element-register', { | ||
detail: { element: ev.detail.element }, | ||
bubbles: true, | ||
}), | ||
); | ||
}; | ||
this.__redispatchEventForFormRegistrarPortalMixin = this.__redispatchEventForFormRegistrarPortalMixin.bind( | ||
this, | ||
); | ||
this.addEventListener( | ||
@@ -57,28 +32,15 @@ 'form-element-register', | ||
disconnectedCallback() { | ||
if (super.disconnectedCallback) { | ||
super.disconnectedCallback(); | ||
} | ||
formRegistrarManager.remove(this); | ||
this.removeEventListener( | ||
'form-element-register', | ||
this.__redispatchEventForFormRegistrarPortalMixin, | ||
); | ||
} | ||
firstUpdated(changedProperties) { | ||
this.__checkRegistrationTarget(); | ||
super.firstUpdated(changedProperties); | ||
this.__resolveRegistrationReady(); | ||
this.__readyForRegistration = true; | ||
formRegistrarManager.becomesReady(this); | ||
this.__hasBeenRendered = true; | ||
} | ||
__checkRegistrationTarget() { | ||
__redispatchEventForFormRegistrarPortalMixin(ev) { | ||
ev.stopPropagation(); | ||
if (!this.registrationTarget) { | ||
throw new Error('A FormRegistrarPortal element requires a .registrationTarget'); | ||
} | ||
this.registrationTarget.dispatchEvent( | ||
new CustomEvent('form-element-register', { | ||
detail: { element: ev.detail.element }, | ||
bubbles: true, | ||
}), | ||
); | ||
} | ||
}, | ||
); |
@@ -1,2 +0,1 @@ | ||
export { formFixture } from './test-helpers/formFixture.js'; | ||
export { | ||
@@ -3,0 +2,0 @@ AlwaysInvalid, |
import { html, LitElement } from '@lion/core'; | ||
import { formFixture as fixture } from '@lion/form-core/test-helpers.js'; | ||
import '@lion/fieldset/lion-fieldset.js'; | ||
import { LionInput } from '@lion/input'; | ||
import { FormGroupMixin, Required } from '@lion/form-core'; | ||
import { expect, nextFrame } from '@open-wc/testing'; | ||
import { expect, fixture } from '@open-wc/testing'; | ||
import { ChoiceGroupMixin } from '../../src/choice-group/ChoiceGroupMixin.js'; | ||
@@ -35,3 +34,2 @@ import { ChoiceInputMixin } from '../../src/choice-group/ChoiceInputMixin.js'; | ||
`); | ||
await nextFrame(); | ||
expect(el.modelValue).to.equal('female'); | ||
@@ -52,3 +50,2 @@ el.formElements[0].checked = true; | ||
`); | ||
await nextFrame(); | ||
const invalidChild = await fixture(html` | ||
@@ -72,3 +69,3 @@ <choice-group-input .modelValue=${'Lara'}></choice-group-input> | ||
`); | ||
await nextFrame(); | ||
await el.registrationComplete; | ||
@@ -93,3 +90,3 @@ expect(el.formElements[0].name).to.equal('gender'); | ||
`); | ||
await nextFrame(); | ||
const invalidChild = await fixture(html` | ||
@@ -114,7 +111,4 @@ <choice-group-input name="foo" .choiceValue=${'male'}></choice-group-input> | ||
`); | ||
await el.registrationComplete; | ||
await nextFrame(); | ||
await el.registrationReady; | ||
await el.updateComplete; | ||
expect(el.modelValue).to.equal('other'); | ||
@@ -132,4 +126,6 @@ expect(el.formElements[2].checked).to.be.true; | ||
`); | ||
await el.registrationComplete; | ||
expect(el.serializedValue).to.equal('other'); | ||
expect(el.formElements[2].checked).to.be.true; | ||
}); | ||
@@ -146,3 +142,2 @@ | ||
`); | ||
await nextFrame(); | ||
@@ -161,3 +156,2 @@ expect(el.modelValue).to.equal(date); | ||
`); | ||
await nextFrame(); | ||
@@ -181,3 +175,4 @@ expect(el.modelValue).to.equal(0); | ||
`); | ||
await nextFrame(); | ||
await el.registrationComplete; | ||
expect(el.modelValue).to.equal('female'); | ||
@@ -202,3 +197,4 @@ el.modelValue = 'other'; | ||
`); | ||
await nextFrame(); | ||
await el.registrationComplete; | ||
counter = 0; // reset after setup which may result in different results | ||
@@ -266,3 +262,2 @@ | ||
`); | ||
await nextFrame(); | ||
@@ -281,3 +276,3 @@ expect(el.serializedValue).to.deep.equal(''); | ||
`); | ||
await nextFrame(); | ||
expect(el.modelValue).to.eql(['female']); | ||
@@ -299,5 +294,3 @@ el.formElements[0].checked = true; | ||
await nextFrame(); | ||
await el.registrationReady; | ||
await el.updateComplete; | ||
await el.registrationComplete; | ||
el.modelValue = ['male', 'other']; | ||
@@ -318,5 +311,3 @@ expect(el.modelValue).to.eql(['male', 'other']); | ||
await nextFrame(); | ||
await el.registrationReady; | ||
await el.updateComplete; | ||
await el.registrationComplete; | ||
expect(el.modelValue).to.eql(['male', 'other']); | ||
@@ -345,4 +336,2 @@ expect(el.formElements[0].checked).to.be.true; | ||
await nextFrame(); | ||
await el.registrationReady; | ||
await el.updateComplete; | ||
@@ -349,0 +338,0 @@ expect(el.serializedValue).to.eql({ |
@@ -1,5 +0,4 @@ | ||
import { expect, html, defineCE, unsafeStatic } from '@open-wc/testing'; | ||
import { expect, html, defineCE, unsafeStatic, fixture } from '@open-wc/testing'; | ||
import { LitElement, SlotMixin } from '@lion/core'; | ||
import sinon from 'sinon'; | ||
import { formFixture as fixture } from '../test-helpers/formFixture.js'; | ||
import { FormControlMixin } from '../src/FormControlMixin.js'; | ||
@@ -222,2 +221,4 @@ import { FormRegistrarMixin } from '../src/registration/FormRegistrarMixin.js'; | ||
const fieldsetEl = formEl.querySelector('[name=fieldset]'); | ||
await formEl.registrationComplete; | ||
await fieldsetEl.registrationComplete; | ||
@@ -224,0 +225,0 @@ expect(fieldsetSpy.callCount).to.equal(1); |
248310
60
5890