Skip to content

Commit

Permalink
Allow always scroll into view on focus (#6787)
Browse files Browse the repository at this point in the history
* Allow always scroll into view on focus

* Add a functional test
  • Loading branch information
andrewtelnov authored Aug 25, 2023
1 parent 9ea27c7 commit 0d183fb
Show file tree
Hide file tree
Showing 6 changed files with 56 additions and 10 deletions.
2 changes: 1 addition & 1 deletion src/base-interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ export interface ISurvey extends ITextProcessor, ISurveyErrorOwner {
element: ISurveyElement,
question: IQuestion,
page: IPage,
id: string
id: string, scrollIfVisible?: boolean
): any;
runExpression(expression: string): any;
elementContentVisibilityChanged(element: ISurveyElement): void;
Expand Down
8 changes: 4 additions & 4 deletions src/question.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1012,20 +1012,20 @@ export class Question extends SurveyElement<Question>
* Moves focus to the input field of this question.
* @param onError Pass `true` if you want to focus an input field with the first validation error. Default value: `false` (focuses the first input field). Applies to question types with multiple input fields.
*/
public focus(onError: boolean = false): void {
public focus(onError: boolean = false, scrollIfVisible?: boolean): void {
if (this.isDesignMode || !this.isVisible || !this.survey) return;
let page = this.page;
const shouldChangePage = !!page && this.survey.activePage !== page;
if(shouldChangePage) {
this.survey.focusQuestionByInstance(this, onError);
} else {
this.focuscore(onError);
this.focuscore(onError, scrollIfVisible);
}
}
private focuscore(onError: boolean = false): void {
private focuscore(onError: boolean = false, scrollIfVisible?: boolean): void {
if (!!this.survey) {
this.expandAllParents(this);
this.survey.scrollElementToTop(this, this, null, this.id);
this.survey.scrollElementToTop(this, this, null, this.id, scrollIfVisible);
}
var id = !onError
? this.getFirstInputElementId()
Expand Down
4 changes: 2 additions & 2 deletions src/survey-element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,12 +161,12 @@ export class SurveyElement<E = any> extends SurveyElementCore implements ISurvey

public readOnlyChangedCallback: () => void;

public static ScrollElementToTop(elementId: string): boolean {
public static ScrollElementToTop(elementId: string, scrollIfVisible?: boolean): boolean {
const { root } = settings.environment;
if (!elementId || typeof root === "undefined") return false;
const el = root.getElementById(elementId);
if (!el || !el.scrollIntoView) return false;
const elemTop: number = el.getBoundingClientRect().top;
const elemTop: number = scrollIfVisible ? -1 : el.getBoundingClientRect().top;
if (elemTop < 0) el.scrollIntoView();
return elemTop < 0;
}
Expand Down
4 changes: 2 additions & 2 deletions src/survey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4855,7 +4855,7 @@ export class SurveyModel extends SurveyElementCore
element: ISurveyElement,
question: Question,
page: PageModel,
id: string
id: string, scrollIfVisible?: boolean
): any {
const options: ScrollingElementToTopEvent = {
element: element,
Expand All @@ -4866,7 +4866,7 @@ export class SurveyModel extends SurveyElementCore
};
this.onScrollingElementToTop.fire(this, options);
if (!options.cancel) {
SurveyElement.ScrollElementToTop(options.elementId);
SurveyElement.ScrollElementToTop(options.elementId, scrollIfVisible);
}
}

Expand Down
10 changes: 10 additions & 0 deletions testCafe/helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -286,3 +286,13 @@ export const explicitErrorHandler = ClientFunction(() => {
}
});
});
export function filterIsInViewport(node) {
const rect = node.getBoundingClientRect();

return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
);
}
38 changes: 37 additions & 1 deletion testCafe/survey/focusFirstQuestionAutomatic.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { url, initSurvey, frameworks, getSurveyResult } from "../helper";
import { url, initSurvey, frameworks, getSurveyResult, filterIsInViewport } from "../helper";
import { Selector, ClientFunction } from "testcafe";

const title = "focusFirstQuestionAutomatic";
Expand Down Expand Up @@ -68,4 +68,40 @@ frameworks.forEach(async framework => {
let surveyResult = await getSurveyResult();
await t.expect(surveyResult).eql({ });
});
test("Focus and scroll into view question", async t => {
const focusQuestion = ClientFunction((name, doScroll) => {
const q = window["survey"].getQuestionByName(name);
q.focus(false, doScroll);
});
const q1Sel = Selector("input[type=text]").nth(0);
const panel1_q1Sel = Selector("input[type=text]").nth(1);

await initSurvey(framework, {
elements: [
{ type: "text", name: "q1" },
{ type: "radiogroup", name: "q2", choices: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] },
{ type: "radiogroup", name: "q3", choices: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] },
{ type: "panel", name: "panel1",
elements: [
{ type: "text", name: "panel1_q1" },
{ type: "radiogroup", name: "panel1_q2", choices: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] },
{ type: "radiogroup", name: "panel1_q3", choices: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] }
]
}
]
});
await focusQuestion("panel1_q1", false);
await t.expect(panel1_q1Sel.filter(filterIsInViewport).exists).ok()
.pressKey("a");
await focusQuestion("q1", false);
await t.expect(q1Sel.filter(filterIsInViewport).exists).ok()
.pressKey("b");
await focusQuestion("panel1_q1", true);
await t.expect(panel1_q1Sel.filter(filterIsInViewport).exists).ok()
.expect(q1Sel.filter(filterIsInViewport).exists).notOk();

await t.click("input[value=Complete]");
let surveyResult = await getSurveyResult();
await t.expect(surveyResult).eql({ q1: "b", panel1_q1: "a" });
});
});

0 comments on commit 0d183fb

Please sign in to comment.