fix(form-core): registr. complete cb before initial interaction states
This commit is contained in:
parent
5354e1a0db
commit
38297d077f
7 changed files with 89 additions and 65 deletions
8
.changeset/cold-spies-press.md
Normal file
8
.changeset/cold-spies-press.md
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
---
|
||||||
|
'@lion/form-core': patch
|
||||||
|
'@lion/form-integrations': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
## Bug fixes
|
||||||
|
|
||||||
|
**form-core**: registrationComplete callback executed before initial interaction states are computed
|
||||||
|
|
@ -11,6 +11,12 @@ import { InteractionStateMixin } from '../InteractionStateMixin.js';
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* ChoiceGroupMixin applies on both Fields (listbox/select-rich/combobox) and FormGroups
|
||||||
|
* (radio-group, checkbox-group)
|
||||||
|
* TODO: Ideally, the ChoiceGroupMixin should not depend on InteractionStateMixin, which is only
|
||||||
|
* designed for usage with Fields, in other words: their interaction states are not derived from
|
||||||
|
* children events, like in FormGroups
|
||||||
|
*
|
||||||
* @type {ChoiceGroupMixin}
|
* @type {ChoiceGroupMixin}
|
||||||
* @param {import('@open-wc/dedupe-mixin').Constructor<import('@lion/core').LitElement>} superclass
|
* @param {import('@open-wc/dedupe-mixin').Constructor<import('@lion/core').LitElement>} superclass
|
||||||
*/
|
*/
|
||||||
|
|
@ -53,8 +59,8 @@ const ChoiceGroupMixinImplementation = superclass =>
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.__isInitialModelValue) {
|
if (this.__isInitialModelValue) {
|
||||||
this.__isInitialModelValue = false;
|
|
||||||
this.registrationComplete.then(() => {
|
this.registrationComplete.then(() => {
|
||||||
|
this.__isInitialModelValue = false;
|
||||||
this._setCheckedElements(value, checkCondition);
|
this._setCheckedElements(value, checkCondition);
|
||||||
this.requestUpdate('modelValue', this.__oldModelValue);
|
this.requestUpdate('modelValue', this.__oldModelValue);
|
||||||
});
|
});
|
||||||
|
|
@ -89,8 +95,8 @@ const ChoiceGroupMixinImplementation = superclass =>
|
||||||
const checkCondition = (el, val) => el.serializedValue.value === val;
|
const checkCondition = (el, val) => el.serializedValue.value === val;
|
||||||
|
|
||||||
if (this.__isInitialSerializedValue) {
|
if (this.__isInitialSerializedValue) {
|
||||||
this.__isInitialSerializedValue = false;
|
|
||||||
this.registrationComplete.then(() => {
|
this.registrationComplete.then(() => {
|
||||||
|
this.__isInitialSerializedValue = false;
|
||||||
this._setCheckedElements(value, checkCondition);
|
this._setCheckedElements(value, checkCondition);
|
||||||
this.requestUpdate('serializedValue');
|
this.requestUpdate('serializedValue');
|
||||||
});
|
});
|
||||||
|
|
@ -116,8 +122,8 @@ const ChoiceGroupMixinImplementation = superclass =>
|
||||||
const checkCondition = (el, val) => el.formattedValue === val;
|
const checkCondition = (el, val) => el.formattedValue === val;
|
||||||
|
|
||||||
if (this.__isInitialFormattedValue) {
|
if (this.__isInitialFormattedValue) {
|
||||||
this.__isInitialFormattedValue = false;
|
|
||||||
this.registrationComplete.then(() => {
|
this.registrationComplete.then(() => {
|
||||||
|
this.__isInitialFormattedValue = false;
|
||||||
this._setCheckedElements(value, checkCondition);
|
this._setCheckedElements(value, checkCondition);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -138,34 +144,10 @@ const ChoiceGroupMixinImplementation = superclass =>
|
||||||
this.__isInitialSerializedValue = true;
|
this.__isInitialSerializedValue = true;
|
||||||
/** @private */
|
/** @private */
|
||||||
this.__isInitialFormattedValue = true;
|
this.__isInitialFormattedValue = true;
|
||||||
/** @type {Promise<any> & {done?:boolean}} */
|
|
||||||
this.registrationComplete = new Promise((resolve, reject) => {
|
|
||||||
/** @private */
|
|
||||||
this.__resolveRegistrationComplete = resolve;
|
|
||||||
/** @private */
|
|
||||||
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() {
|
connectedCallback() {
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
// Double microtask queue to account for Webkit race condition
|
|
||||||
Promise.resolve().then(() =>
|
|
||||||
// @ts-ignore
|
|
||||||
Promise.resolve().then(() => this.__resolveRegistrationComplete()),
|
|
||||||
);
|
|
||||||
|
|
||||||
this.registrationComplete.then(() => {
|
this.registrationComplete.then(() => {
|
||||||
this.__isInitialModelValue = false;
|
this.__isInitialModelValue = false;
|
||||||
|
|
@ -174,6 +156,14 @@ const ChoiceGroupMixinImplementation = superclass =>
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @enhance FormRegistrarMixin
|
||||||
|
*/
|
||||||
|
_completeRegistration() {
|
||||||
|
// Double microtask queue to account for Webkit race condition
|
||||||
|
Promise.resolve().then(() => super._completeRegistration());
|
||||||
|
}
|
||||||
|
|
||||||
/** @param {import('@lion/core').PropertyValues} changedProperties */
|
/** @param {import('@lion/core').PropertyValues} changedProperties */
|
||||||
updated(changedProperties) {
|
updated(changedProperties) {
|
||||||
super.updated(changedProperties);
|
super.updated(changedProperties);
|
||||||
|
|
@ -185,18 +175,6 @@ const ChoiceGroupMixinImplementation = superclass =>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnectedCallback() {
|
|
||||||
super.disconnectedCallback();
|
|
||||||
|
|
||||||
if (this.registrationComplete.done === false) {
|
|
||||||
Promise.resolve().then(() => {
|
|
||||||
Promise.resolve().then(() => {
|
|
||||||
this.__rejectRegistrationComplete();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @override from FormRegistrarMixin
|
* @override from FormRegistrarMixin
|
||||||
* @param {FormControl} child
|
* @param {FormControl} child
|
||||||
|
|
|
||||||
|
|
@ -146,32 +146,13 @@ const FormGroupMixinImplementation = superclass =>
|
||||||
this.addEventListener('validate-performed', this.__onChildValidatePerformed);
|
this.addEventListener('validate-performed', this.__onChildValidatePerformed);
|
||||||
|
|
||||||
this.defaultValidators = [new FormElementsHaveNoError()];
|
this.defaultValidators = [new FormElementsHaveNoError()];
|
||||||
/** @type {Promise<any> & {done?:boolean}} */
|
|
||||||
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() {
|
connectedCallback() {
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
this.setAttribute('role', 'group');
|
this.setAttribute('role', 'group');
|
||||||
// @ts-ignore
|
|
||||||
Promise.resolve().then(() => this.__resolveRegistrationComplete());
|
|
||||||
|
|
||||||
this.registrationComplete.then(() => {
|
this.initComplete.then(() => {
|
||||||
this.__isInitialModelValue = false;
|
this.__isInitialModelValue = false;
|
||||||
this.__isInitialSerializedValue = false;
|
this.__isInitialSerializedValue = false;
|
||||||
this.__initInteractionStates();
|
this.__initInteractionStates();
|
||||||
|
|
@ -185,11 +166,6 @@ const FormGroupMixinImplementation = superclass =>
|
||||||
document.removeEventListener('click', this._checkForOutsideClick);
|
document.removeEventListener('click', this._checkForOutsideClick);
|
||||||
this.__hasActiveOutsideClickHandling = false;
|
this.__hasActiveOutsideClickHandling = false;
|
||||||
}
|
}
|
||||||
if (this.registrationComplete.done === false) {
|
|
||||||
Promise.resolve().then(() => {
|
|
||||||
this.__rejectRegistrationComplete();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
__initInteractionStates() {
|
__initInteractionStates() {
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,61 @@ const FormRegistrarMixinImplementation = superclass =>
|
||||||
'form-element-name-changed',
|
'form-element-name-changed',
|
||||||
/** @type {EventListenerOrEventListenerObject} */ (this._onRequestToChangeFormElementName),
|
/** @type {EventListenerOrEventListenerObject} */ (this._onRequestToChangeFormElementName),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* initComplete resolves after all pending initialization logic
|
||||||
|
* (for instance `<form-group .serializedValue=${{ child1: 'a', child2: 'b' }}>`)
|
||||||
|
* is executed
|
||||||
|
* @type {Promise<any>}
|
||||||
|
*/
|
||||||
|
this.initComplete = new Promise((resolve, reject) => {
|
||||||
|
this.__resolveInitComplete = resolve;
|
||||||
|
this.__rejectInitComplete = reject;
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* registrationComplete waits for all children formElements to have registered
|
||||||
|
* @type {Promise<any> & {done?:boolean}}
|
||||||
|
*/
|
||||||
|
this.registrationComplete = new Promise((resolve, reject) => {
|
||||||
|
this.__resolveRegistrationComplete = resolve;
|
||||||
|
this.__rejectRegistrationComplete = reject;
|
||||||
|
});
|
||||||
|
this.registrationComplete.done = false;
|
||||||
|
this.registrationComplete.then(
|
||||||
|
() => {
|
||||||
|
this.registrationComplete.done = true;
|
||||||
|
this.__resolveInitComplete(undefined);
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
this.registrationComplete.done = true;
|
||||||
|
this.__rejectInitComplete(undefined);
|
||||||
|
throw new Error(
|
||||||
|
'Registration could not finish. Please use await el.registrationComplete;',
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
super.connectedCallback();
|
||||||
|
this._completeRegistration();
|
||||||
|
}
|
||||||
|
|
||||||
|
_completeRegistration() {
|
||||||
|
Promise.resolve().then(() => this.__resolveRegistrationComplete(undefined));
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectedCallback() {
|
||||||
|
super.disconnectedCallback();
|
||||||
|
|
||||||
|
if (this.registrationComplete.done === false) {
|
||||||
|
Promise.resolve().then(() => {
|
||||||
|
Promise.resolve().then(() => {
|
||||||
|
this.__rejectRegistrationComplete();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,8 @@ export declare class FormRegistrarHost {
|
||||||
_onRequestToAddFormElement(e: CustomEvent): void;
|
_onRequestToAddFormElement(e: CustomEvent): void;
|
||||||
isRegisteredFormElement(el: FormControlHost): boolean;
|
isRegisteredFormElement(el: FormControlHost): boolean;
|
||||||
registrationComplete: Promise<boolean>;
|
registrationComplete: Promise<boolean>;
|
||||||
|
initComplete: Promise<boolean>;
|
||||||
|
protected _completeRegistration(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export declare function FormRegistrarImplementation<T extends Constructor<LitElement>>(
|
export declare function FormRegistrarImplementation<T extends Constructor<LitElement>>(
|
||||||
|
|
|
||||||
|
|
@ -81,6 +81,7 @@ describe('Form Integrations', () => {
|
||||||
></umbrella-form>`,
|
></umbrella-form>`,
|
||||||
));
|
));
|
||||||
|
|
||||||
|
await el._lionFormNode.initComplete;
|
||||||
expect(el._lionFormNode.dirty).to.be.false;
|
expect(el._lionFormNode.dirty).to.be.false;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,9 @@ export class UmbrellaForm extends LitElement {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} v
|
||||||
|
*/
|
||||||
set serializedValue(v) {
|
set serializedValue(v) {
|
||||||
this.__serializedValue = v;
|
this.__serializedValue = v;
|
||||||
}
|
}
|
||||||
|
|
@ -141,7 +144,8 @@ export class UmbrellaForm extends LitElement {
|
||||||
<lion-button
|
<lion-button
|
||||||
type="button"
|
type="button"
|
||||||
raised
|
raised
|
||||||
@click=${ev =>
|
@click=${(/** @type {Event} */ ev) =>
|
||||||
|
// @ts-ignore
|
||||||
ev.currentTarget.parentElement.parentElement.parentElement.resetGroup()}
|
ev.currentTarget.parentElement.parentElement.parentElement.resetGroup()}
|
||||||
>Reset</lion-button
|
>Reset</lion-button
|
||||||
>
|
>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue