diff --git a/e2e/questions/text.spec.ts b/e2e/questions/text.spec.ts index e4bebca07e..4266b70b4a 100644 --- a/e2e/questions/text.spec.ts +++ b/e2e/questions/text.spec.ts @@ -1,7 +1,14 @@ +import { Page } from "playwright/test"; import { frameworks, url, initSurvey, getSurveyData, test, expect } from "../helper"; const title = "text"; - +async function fullQuestionRerender(page: Page, name: string): Promise { + await page.evaluate(([name]) => { + const q = window["survey"].getQuestionByName(name); + q.readOnly = true; + q.readOnly = false; + }, [name]); +} const json = { "pages": [ { @@ -73,5 +80,55 @@ frameworks.forEach((framework) => { expect(surveyResult.question2).toEqual("#000000"); expect(surveyResult.question3).toEqual(undefined); }); + test("Do not lost the value is being entered on re-rendering the question, Bug#10584", async ({ page }) => { + const locJSON = { + "elements": [ + { "type": "text", "name": "q1" }, + { "type": "text", "name": "q2", "maskType": "pattern", "maskSettings": { "pattern": "99-99" } }, + { "type": "text", "name": "q3", "inputType": "time" }, + { "type": "comment", "name": "q4" }] + }; + await initSurvey(page, framework, locJSON); + + const q1 = page.locator("input").first(); + await q1.focus(); + await q1.fill("Test"); + await fullQuestionRerender(page, "q1"); + await page.keyboard.type("A"); + await page.keyboard.press("Tab"); + /* + const q2 = page.locator("input").nth(1); + await q2.focus(); + await page.keyboard.type("1"); + await page.keyboard.type("2"); + await fullQuestionRerender(page, "q2"); + await page.keyboard.press("ArrowRight"); await page.keyboard.press("ArrowRight"); await page.keyboard.press("ArrowRight"); + await page.keyboard.type("3"); + await page.keyboard.type("4"); + await page.keyboard.press("Tab"); +*/ + const q3 = page.locator("input").nth(2); + await q3.focus(); + await page.keyboard.type("1"); + await page.keyboard.type("0"); + await fullQuestionRerender(page, "q3"); + await page.keyboard.type("2"); + await page.keyboard.type("5"); + await page.keyboard.type("P"); + await page.keyboard.press("Tab"); + + const q4 = page.locator("textarea").nth(0); + await q4.focus(); + await q4.fill("Test"); + await fullQuestionRerender(page, "q4"); + await page.keyboard.type("B"); + await page.keyboard.press("Tab"); + + const surveyResult = await getSurveyData(page); + expect(surveyResult.q1).toBe("TestA"); + // expect(surveyResult.q2).toBe("1234"); + expect(surveyResult.q3).toBe("22:25"); + expect(surveyResult.q4).toBe("TestB"); + }); }); }); \ No newline at end of file diff --git a/packages/survey-angular-ui/src/questions/text.component.ts b/packages/survey-angular-ui/src/questions/text.component.ts index e659f47da0..efc20f82f0 100644 --- a/packages/survey-angular-ui/src/questions/text.component.ts +++ b/packages/survey-angular-ui/src/questions/text.component.ts @@ -12,7 +12,7 @@ export class TextQuestionComponent extends QuestionAngular { @ViewChild("inputElement") inputElementRef!: ElementRef; get value(): string { - return this.model.inputValue ?? ""; + return this.model.renderedValue ?? ""; } blur(event: any): void { diff --git a/packages/survey-core/src/question.ts b/packages/survey-core/src/question.ts index c44309fdea..09aea05e54 100644 --- a/packages/survey-core/src/question.ts +++ b/packages/survey-core/src/question.ts @@ -1560,16 +1560,19 @@ export class Question extends SurveyElement return false; } public get isContainer(): boolean { return false; } + protected getValueFromEvent(event: any): any { + return event?.target?.value; + } public onCommentInput(event: any): void { if (this.isInputTextUpdate) { if (event.target) { - this.comment = event.target.value; + this.comment = this.getValueFromEvent(event); } } } public onCommentChange(event: any): void { - this.comment = event.target.value; - if (this.comment !== event.target.value) { + this.comment = this.getValueFromEvent(event); + if (this.comment !== this.getValueFromEvent(event)) { event.target.value = this.comment; } } @@ -3012,7 +3015,6 @@ export class Question extends SurveyElement return !!this.survey ? this.survey.isUpdateValueTextOnTyping : false; } get requireStrictCompare(): boolean { return false; } - getExpressionValue(val: any): any { return val; } private getDataLocNotification(): any { return this.isInputTextUpdate ? "text" : false; } diff --git a/packages/survey-core/src/question_baseselect.ts b/packages/survey-core/src/question_baseselect.ts index 895377afa9..8ad2fa3983 100644 --- a/packages/survey-core/src/question_baseselect.ts +++ b/packages/survey-core/src/question_baseselect.ts @@ -1874,7 +1874,7 @@ export class QuestionSelectBase extends Question implements IChoiceOwner { } private onOtherValueInput(item: ItemValue, event: any): void { if (this.isInputTextUpdate && event.target) { - this.setCommentValueCore(item, event.target.value); + this.setCommentValueCore(item, this.getValueFromEvent(event)); } } private onOtherValueChange(item: ItemValue, event: any): void { diff --git a/packages/survey-core/src/question_comment.ts b/packages/survey-core/src/question_comment.ts index 621aa640fd..980b5e94f0 100644 --- a/packages/survey-core/src/question_comment.ts +++ b/packages/survey-core/src/question_comment.ts @@ -50,7 +50,7 @@ export class QuestionCommentModel extends QuestionTextBase { ariaInvalid: () => this.a11y_input_ariaInvalid, ariaErrormessage: () => this.a11y_input_ariaErrormessage, getTextValue: () => { return this.value; }, - onTextAreaChange: (e) => { updateQuestionValue(e.target.value); }, + onTextAreaChange: (e) => { updateQuestionValue(this.getValueFromEvent(e)); }, onTextAreaInput: (event) => { this.onInput(event); }, onTextAreaKeyDown: (event) => { this.onKeyDown(event); }, onTextAreaFocus: (event) => { this.onFocus(event); }, @@ -143,9 +143,10 @@ export class QuestionCommentModel extends QuestionTextBase { this.element = undefined; } public onInput(event: any): void { + const val = this.getValueFromEvent(event); if (this.isInputTextUpdate) - this.value = event.target.value; - this.updateRemainingCharacterCounter(event.target.value); + this.value = val; + this.updateRemainingCharacterCounter(val); } protected onBlurCore(event: any): void { super.onBlurCore(event); diff --git a/packages/survey-core/src/question_text.ts b/packages/survey-core/src/question_text.ts index 21f65c2303..0eaf495383 100644 --- a/packages/survey-core/src/question_text.ts +++ b/packages/survey-core/src/question_text.ts @@ -367,33 +367,44 @@ export class QuestionTextModel extends QuestionTextBase { return isMinMaxType(this); } - @property() _inputValue: string; public get maskInstance(): IInputMask { return this.maskSettings; } + private setInputValue(val: string) { + this.setPropertyValue("inputValue", val); + this.setRenderedValue(val); + } + public get renderedValue(): string { + return this.maskTypeIsEmpty ? this.getPropertyValue("renderedValue") : this.inputValue; + } + public set renderedValue(val: string) { + this.inputValue = val; + } + protected setRenderedValue(val: string) { + this.setPropertyValue("renderedValue", val); + } public get inputValue(): string { - if (!this._inputValue && !this.maskTypeIsEmpty) return this.maskInstance.getMaskedValue(""); - return this._inputValue; + const res = this.getPropertyValue("inputValue"); + if (!res && !this.maskTypeIsEmpty) return this.maskInstance.getMaskedValue(""); + return res; } public set inputValue(val: string) { let value = val; - this._inputValue = val; + let inputVal = val; if (!this.maskTypeIsEmpty) { value = this.maskInstance.getUnmaskedValue(val); - this._inputValue = this.maskInstance.getMaskedValue(value); + inputVal = this.maskInstance.getMaskedValue(value); if (!!value && this.maskSettings.saveMaskedValue) { - value = this._inputValue; + value = inputVal; } } + this.setInputValue(inputVal); if (!Helpers.isTwoValueEquals(this.value, value, false, true)) { this.value = value; } } public getFilteredValue(): any { - return this.getExpressionValue(this.value); - } - //TODO remove this method in the future - getExpressionValue(val: any): any { + const val = this.value; if (!this.maskTypeIsEmpty && this.maskSettings.saveMaskedValue) return this.maskInstance.getUnmaskedValue(val); return val; @@ -411,14 +422,13 @@ export class QuestionTextModel extends QuestionTextBase { } private updateInputValue() { - const _value = this.value; - if (this.maskTypeIsEmpty) { - this._inputValue = _value; - } else if (this.maskSettings.saveMaskedValue) { - this._inputValue = (_value !== undefined && _value !== null) ? _value : this.maskInstance.getMaskedValue(""); - } else { - this._inputValue = this.maskInstance.getMaskedValue(_value); - } + this.setInputValue(this.getInputValueFromValue()); + } + private getInputValueFromValue(): string { + const res = this.value; + if (this.maskTypeIsEmpty) return res; + if (this.maskSettings.saveMaskedValue) return (res !== undefined && res !== null) ? res : this.maskInstance.getMaskedValue(""); + return this.maskInstance.getMaskedValue(res); } private hasToConvertToUTC(val: any): boolean { return settings.storeUtcDates && this.isDateTimeLocaleType() && !!val; @@ -678,7 +688,7 @@ export class QuestionTextModel extends QuestionTextBase { private _isWaitingForEnter = false; private updateValueOnEvent(event: any) { - const newValue = event.target.value; + const newValue = this.getValueFromEvent(event); if (!this.isTwoValueEquals(this.value, newValue)) { this.inputValue = newValue; } @@ -689,7 +699,7 @@ export class QuestionTextModel extends QuestionTextBase { this.updateValueOnEvent(event); }, 1); } - this.updateRemainingCharacterCounter(event.target.value); + this.updateRemainingCharacterCounter(this.getValueFromEvent(event)); }; public onKeyUp = (event: any) => { this.updateDateValidationMessage(event); @@ -703,7 +713,7 @@ export class QuestionTextModel extends QuestionTextBase { this.updateValueOnEvent(event); } } - this.updateRemainingCharacterCounter(event.target.value); + this.updateRemainingCharacterCounter(this.getValueFromEvent(event)); }; private updateDateValidationMessage(event: any): void { this.dateValidationMessage = this.isDateInputType && !!event.target ? event.target.validationMessage : undefined; @@ -725,6 +735,7 @@ export class QuestionTextModel extends QuestionTextBase { this.onTextKeyDownHandler(event); }; public onChange = (event: any): void => { + this.setRenderedValue(this.getValueFromEvent(event)); this.updateDateValidationMessage(event); const elementIsFocused = event.target === settings.environment.root.activeElement; if (elementIsFocused) { @@ -734,16 +745,16 @@ export class QuestionTextModel extends QuestionTextBase { } else { this.updateValueOnEvent(event); } - this.updateRemainingCharacterCounter(event.target.value); + this.updateRemainingCharacterCounter(this.getValueFromEvent(event)); }; protected onBlurCore(event: any): void { this.updateDateValidationMessage(event); this.updateValueOnEvent(event); - this.updateRemainingCharacterCounter(event.target.value); + this.updateRemainingCharacterCounter(this.getValueFromEvent(event)); super.onBlurCore(event); } protected onFocusCore(event: any): void { - this.updateRemainingCharacterCounter(event.target.value); + this.updateRemainingCharacterCounter(this.getValueFromEvent(event)); super.onFocusCore(event); } public afterRenderQuestionElement(el: HTMLElement) { diff --git a/packages/survey-core/tests/question_texttests.ts b/packages/survey-core/tests/question_texttests.ts index 3c61554752..8df09ec276 100644 --- a/packages/survey-core/tests/question_texttests.ts +++ b/packages/survey-core/tests/question_texttests.ts @@ -774,4 +774,21 @@ QUnit.test("Could not change the value from upper case into lower case, #10590", assert.equal(q.value, "TEST", "value is set to upper case"); q.inputValue = "test"; assert.equal(q.value, "test", "value is changed to lower case"); +}); +QUnit.test("text question, renderedValue property, Bug#10584", (assert) => { + const survey = new SurveyModel({ + elements: [ + { type: "text", name: "q1", defaultValue: "AB" } + ] + }); + const q = survey.getQuestionByName("q1"); + assert.equal(q.renderedValue, "AB", "renderedValue is correct"); + q.value = "CD"; + assert.equal(q.renderedValue, "CD", "renderedValue is correct, #2"); + q.inputValue = "EF"; + assert.equal(q.renderedValue, "EF", "renderedValue is correct, #3"); + q.onChange({ target: { value: "GH" } }); + assert.equal(q.renderedValue, "GH", "renderedValue is correct, #4"); + q.renderedValue = "IJ"; + assert.equal(q.inputValue, "IJ", "inputValue is correct, #5"); }); \ No newline at end of file diff --git a/packages/survey-react-ui/src/reactquestion_text.tsx b/packages/survey-react-ui/src/reactquestion_text.tsx index f6590bfb6d..d180cb1ff9 100644 --- a/packages/survey-react-ui/src/reactquestion_text.tsx +++ b/packages/survey-react-ui/src/reactquestion_text.tsx @@ -73,7 +73,7 @@ export class SurveyQuestionText extends SurveyQuestionUncontrolledElement< this.question.inputValue = newValue; } protected getValueCore(): any { - return this.question.inputValue; + return this.question.renderedValue; } private renderDataList(): React.JSX.Element | null { if (!this.question.dataListId) return null; diff --git a/packages/survey-vue3-ui/src/TextInput.vue b/packages/survey-vue3-ui/src/TextInput.vue index 5e41248162..9d512dbb65 100644 --- a/packages/survey-vue3-ui/src/TextInput.vue +++ b/packages/survey-vue3-ui/src/TextInput.vue @@ -16,7 +16,7 @@ :list="question.dataListId" :placeholder="question.renderedPlaceholder" :autocomplete="question.autocomplete" - :value="question.inputValue" + :value="question.renderedValue" @change="question.onChange" @click="question.readOnlyBlocker" @pointerdown="question.readOnlyBlocker" @@ -48,7 +48,7 @@ :list="question.dataListId" :placeholder="question.renderedPlaceholder" :autocomplete="question.autocomplete" - :value="question.inputValue" + :value="question.renderedValue" @change="question.onChange" @click="question.readOnlyBlocker" @pointerdown="question.readOnlyBlocker"