diff --git a/api/js/etemplate/Et2InputWidget/Et2InputWidget.ts b/api/js/etemplate/Et2InputWidget/Et2InputWidget.ts index 9e1b72f5ee..258d4818c0 100644 --- a/api/js/etemplate/Et2InputWidget/Et2InputWidget.ts +++ b/api/js/etemplate/Et2InputWidget/Et2InputWidget.ts @@ -56,6 +56,8 @@ const Et2InputWidgetMixin = >(superclass : T) protected defaultValidators : Validator[]; // Promise used during validation protected validateComplete : Promise; + // Hold on to any server messages while the user edits + private _messagesHeldWhileFocused : Validator[]; protected isSlComponent = false; @@ -65,24 +67,27 @@ const Et2InputWidgetMixin = >(superclass : T) return [ ...super.styles, css` - /* Allow actually disabled inputs */ - :host([disabled]) { - display: initial; - } + /* Allow actually disabled inputs */ - /* Needed so required can show through */ - ::slotted(input), input { - background-color: transparent; - } + :host([disabled]) { + display: initial; + } - /* Used to allow auto-sizing on slotted inputs */ - .input-group__container > .input-group__input ::slotted(.form-control) { - width: 100%; - } + /* Needed so required can show through */ - .form-field__feedback { - position: relative; - } + ::slotted(input), input { + background-color: transparent; + } + + /* Used to allow auto-sizing on slotted inputs */ + + .input-group__container > .input-group__input ::slotted(.form-control) { + width: 100%; + } + + .form-field__feedback { + position: relative; + } ` ]; } @@ -162,10 +167,14 @@ const Et2InputWidgetMixin = >(superclass : T) this.validators = []; this.defaultValidators = []; + this._messagesHeldWhileFocused = []; this.__readonly = false; this.isSlComponent = typeof (this).handleChange === 'function'; + + this.handleFocus = this.handleFocus.bind(this); + this.handleBlur = this.handleBlur.bind(this); } connectedCallback() @@ -177,12 +186,17 @@ const Et2InputWidgetMixin = >(superclass : T) { this.addEventListener(this.isSlComponent ? 'sl-change' : 'change', this._oldChange); }); + this.addEventListener("focus", this.handleFocus); + this.addEventListener("blur", this.handleBlur); } disconnectedCallback() { super.disconnectedCallback(); this.removeEventListener(this.isSlComponent ? 'sl-change' : 'change', this._oldChange); + + this.removeEventListener("focus", this.handleFocus); + this.removeEventListener("blur", this.handleBlur); } /** @@ -236,6 +250,41 @@ const Et2InputWidgetMixin = >(superclass : T) return true; } + /** + * When input receives focus, clear any validation errors. + * + * If the value is the same on blur, we'll put them back + * The ones from the server (ManualMessage) can interfere with submitting. + * @param {FocusEvent} _ev + */ + handleFocus(_ev : FocusEvent) + { + if(this._messagesHeldWhileFocused.length > 0) + { + return; + } + this._oldValue = this.value; + + // Collect any ManualMessages + this._messagesHeldWhileFocused = (this.validators || []).filter((validator) => (validator instanceof ManualMessage)); + + this.set_validation_error(false); + } + + /** + * If the value is unchanged, put any held validation messages back + * @param {FocusEvent} _ev + */ + handleBlur(_ev : FocusEvent) + { + if(this._messagesHeldWhileFocused.length > 0 && this.value == this._oldValue) + { + this.validators = this.validators.concat(this._messagesHeldWhileFocused); + this._messagesHeldWhileFocused = []; + this.validate(); + } + } + set_value(new_value) { this.value = new_value; diff --git a/api/js/etemplate/Et2Select/Et2Select.ts b/api/js/etemplate/Et2Select/Et2Select.ts index 3292b2d170..815e08f27f 100644 --- a/api/js/etemplate/Et2Select/Et2Select.ts +++ b/api/js/etemplate/Et2Select/Et2Select.ts @@ -402,6 +402,19 @@ export class Et2Select extends Et2WithSearchMixin(Et2WidgetWithSelect) ${this.emptyLabel}`; } + /** + * Override Et2InputWidget blur handler to avoid doing our blur stuff when internal controls blur + * + * @param {FocusEvent} ev + */ + handleBlur(ev : FocusEvent) + { + if(ev.target == this) + { + super.handleBlur(ev); + } + } + /** * Tag used for rendering options * Used for finding & filtering options, they're created by the mixed-in class