Skip to content

A11Y: Paneldynamic progressbar #9857

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions accessibilityTests/navigation.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { frameworks, initSurvey, url } from "./helper";
import { checkA11y, injectAxe } from "axe-playwright";
import { axeOptions, frameworks, initSurvey, url } from "./helper";
import { test, expect } from "@playwright/test";
const title = "navigation";

Expand Down Expand Up @@ -70,12 +71,14 @@ frameworks.forEach((framework) => {
test.describe(`${framework} a11y:${title}`, () => {
test.beforeEach(async ({ page }) => {
await page.goto(`${url}${framework}`);
await initSurvey(page, framework, json);
await injectAxe(page);
});
test("progress bar", async ({ page }) => {
await initSurvey(page, framework, json);
await expect(page.locator("[role='progressbar']")).toHaveAttribute(
"aria-label"
);
await checkA11y(page, ".sd-progress-buttons", { axeOptions });
});
});
});
54 changes: 54 additions & 0 deletions accessibilityTests/paneldynamic.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { Page, test } from "@playwright/test";
import { frameworks, url, initSurvey, axeOptions } from "./helper";
import { injectAxe, checkA11y } from "axe-playwright";

const title = "Paneldynamic";

frameworks.forEach((framework) => {
test.describe(`${framework} a11y:${title}`, () => {
test.beforeEach(async ({ page }) => {
await page.goto(`${url}${framework}`);
await injectAxe(page);
});
test("axe check paneldynamic", async ({ page }) => {
await initSurvey(page, framework, {
"pages": [
{
"name": "page1",
"elements": [
{
"type": "paneldynamic",
"name": "relatives26",
"title": "Panel Dynamic",
"templateElements": [
{
"type": "dropdown",
"name": "relativeType27",
"title": "Relative",
"choices": ["father", "mother", "brother", "sister", "son", "daughter"]
},
{
"type": "radiogroup",
"name": "isalive28",
"startWithNewLine": false,
"title": "Alive?",
"choices": ["Yes", "No"],
"colCount": 0
}
],
"templateTitle": "Information about: {panel.relativeType}",
"panelCount": 2,
"addPanelText": "Add a blood relative",
"removePanelText": "Remove the relative",
"renderMode": "progressTop",
"displayMode": "carousel"
},
]
}
],
"questionErrorLocation": "bottom"
});
await checkA11y(page, ".sd-question", { axeOptions });
});
});
});
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
<div [class]="model.getRootCss(container)" [style.maxWidth]="model.progressWidth" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-label="progress">
<div [class]="model.getRootCss(container)" [style.maxWidth]="model.progressWidth"
role="progressbar" aria-valuemin="0" aria-valuemax="100" [attr.aria-label]="model.progressBarAriaLabel"
>
<div *ngIf="canShowHeader" [class]="survey.css.progressButtonsHeader">
<div [class]="survey.css.progressButtonsPageTitle" [title]="model.headerText">{{ model.headerText }}</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<ng-template #template>
<div [class]="model.getProgressCssClasses(container)">
<div [class]="model.css.progressBar" [style.width]="model.progressValue + '%'"
role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-label="progress">
role="progressbar" aria-valuemin="0" aria-valuemax="100" [attr.aria-label]="model.progressBarAriaLabel">
<span [class]="getProgressTextInBarCss(model.css)">
{{ model.progressText }}
</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
</div>
<sv-ng-placeholder-paneldynamic [question]="model"></sv-ng-placeholder-paneldynamic>
<div [class]="model.cssClasses.progress" *ngIf="model.isProgressTopShowing && model.isRangeShowing">
<div [class]="model.cssClasses.progressBar" [style]="{ width: model.progress }" role="progressbar"></div>
<div [class]="model.cssClasses.progressBar" [style]="{ width: model.progress }"
role="progressbar" [attr.aria-label]="model.progressBarAriaLabel"
></div>
</div>
<div [class]="model.cssClasses.panelsContainer">
<ng-container *ngFor="let panel of model.renderedPanels; index as index; trackBy: trackPanelBy">
Expand All @@ -27,7 +29,9 @@
<div [class]="model.cssClasses.footer" *ngIf="!!model.cssClasses.footer">
<hr [class]="model.cssClasses.separator" />
<div [class]="model.cssClasses.progress" *ngIf="model.isRangeShowing && model.isProgressBottomShowing">
<div [class]="model.cssClasses.progressBar" [style]="{ width: model.progress }" role="progressbar"></div>
<div [class]="model.cssClasses.progressBar" [style]="{ width: model.progress }"
role="progressbar" [attr.aria-label]="model.progressBarAriaLabel"
></div>
</div>
<div *ngIf="model.footerToolbar.visibleActions.length" [class]="model.cssClasses.footerButtonsContainer">
<sv-ng-action-bar [model]="model.footerToolbar"></sv-ng-action-bar>
Expand Down
1 change: 1 addition & 0 deletions packages/survey-core/src/localization/english.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ export var englishStrings = {
cancel: "Cancel",
createCustomItem: "Create \"{0}\" item...",
toc: "Table of contents",
progressbar: "Progress bar",
};

// Uncomment the lines below if you create a custom dictionary.
Expand Down
4 changes: 4 additions & 0 deletions packages/survey-core/src/progress-buttons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Base, EventBase } from "./base";
import { surveyCss } from "./defaultCss/defaultCss";
import { PageModel } from "./page";
import { SurveyModel } from "./survey";
import { getLocaleString } from "./surveyStrings";
import { CssClassBuilder } from "./utils/cssClassBuilder";

export class ProgressButtons extends Base {
Expand Down Expand Up @@ -130,6 +131,9 @@ export class ProgressButtons extends Base {
public get progressText(): string {
return this.getPropertyValue("progressText", undefined, () => this.survey.getProgressText());
}
public get progressBarAriaLabel(): string {
return getLocaleString("progressbar", this.survey.getLocale());
}
public resetProgressText(): void {
this.resetPropertyValue("progressText");
}
Expand Down
4 changes: 4 additions & 0 deletions packages/survey-core/src/question_paneldynamic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { AdaptiveActionContainer } from "./actions/adaptive-container";
import { ITheme } from "./themes";
import { AnimationGroup, AnimationProperty, AnimationTab, IAnimationConsumer, IAnimationGroupConsumer } from "./utils/animation";
import { QuestionSingleInputSummary, QuestionSingleInputSummaryItem } from "./questionSingleInputSummary";
import { getLocaleString } from "./surveyStrings";

export interface IQuestionPanelDynamicData {
getItemIndex(item: ISurveyData): number;
Expand Down Expand Up @@ -2560,6 +2561,9 @@ export class QuestionPanelDynamicModel extends Question
public get progress(): string {
return ((this.currentIndex + 1) / this.visiblePanelCount) * 100 + "%";
}
public get progressBarAriaLabel(): string {
return getLocaleString("progressbar", this.getLocale());
}
public getRootCss(): string {
return new CssClassBuilder().append(super.getRootCss()).append(this.cssClasses.empty, this.getShowNoEntriesPlaceholder()).toString();
}
Expand Down
5 changes: 4 additions & 1 deletion packages/survey-core/src/survey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import { CalculatedValue } from "./calculatedValue";
import { PageModel } from "./page";
import { TextPreProcessor, TextPreProcessorValue } from "./textPreProcessor";
import { ProcessValue } from "./conditionProcessValue";
import { surveyLocalization } from "./surveyStrings";
import { getLocaleString, surveyLocalization } from "./surveyStrings";
import { CustomError } from "./error";
import { LocalizableString } from "./localizablestring";
// import { StylesManager } from "./stylesmanager";
Expand Down Expand Up @@ -5262,6 +5262,9 @@ export class SurveyModel extends SurveyElementCore
var pages = this.isDesignMode ? this.pages : this.visiblePages;
return SurveyElement.getProgressInfoByElements(pages, false);
}
public get progressBarAriaLabel(): string {
return getLocaleString("progressbar", this.getLocale());
}
/**
* Returns text displayed by the progress bar (for instance, "Page 2 of 3" or "Answered 3/8 questions"). Handle the [`onGetProgressText`](https://surveyjs.io/form-library/documentation/api-reference/survey-data-model#onGetProgressText) event to change this text.
* @see progressValue
Expand Down
5 changes: 4 additions & 1 deletion packages/survey-react-ui/src/progress.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ export class SurveyProgress extends SurveyNavigationBase {
protected get progressText(): string {
return this.survey.progressText;
}
protected get progressBarAriaLabel(): string {
return this.survey.progressBarAriaLabel;
}
render(): React.JSX.Element {
var progressStyle = {
width: this.progress + "%",
Expand All @@ -28,7 +31,7 @@ export class SurveyProgress extends SurveyNavigationBase {
role="progressbar"
aria-valuemin={0}
aria-valuemax={100}
aria-label="progress"
aria-label={this.progressBarAriaLabel}
>
<span
className={SurveyProgressModel.getProgressTextInBarCss(this.css)}
Expand Down
4 changes: 3 additions & 1 deletion packages/survey-react-ui/src/progressButtons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ export class SurveyProgressButtons extends SurveyNavigationBase implements IProg
}
render(): React.JSX.Element {
return (
<div className={this.model.getRootCss(this.props.container)} style={{ "maxWidth": this.model.progressWidth }} role="progressbar" aria-valuemin={0} aria-valuemax={100} aria-label="progress">
<div className={this.model.getRootCss(this.props.container)} style={{ "maxWidth": this.model.progressWidth }}
role="progressbar" aria-valuemin={0} aria-valuemax={100} aria-label={this.model.progressBarAriaLabel}
>
{this.state.canShowHeader ? <div className={this.css.progressButtonsHeader}>
<div className={this.css.progressButtonsPageTitle} title={this.model.headerText}>{this.model.headerText}</div>
</div> : null}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ export class SurveyQuestionPanelDynamic extends SurveyQuestionElementBase {
className={this.question.cssClasses.progressBar}
style={{ width: this.question.progress }}
role="progressbar"
aria-label={this.question.progressBarAriaLabel}
/>
</div>
);
Expand Down
1 change: 1 addition & 0 deletions packages/survey-vue3-ui/src/PanelDynamic.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
:class="question.cssClasses.progressBar"
:style="{ width: question.progress }"
role="progressbar"
:aria-label="question.progressBarAriaLabel"
></div>
</div>
<div :class="question.cssClasses.panelsContainer">
Expand Down
1 change: 1 addition & 0 deletions packages/survey-vue3-ui/src/PanelDynamicProgress.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
:class="question.cssClasses.progressBar"
:style="{ width: question.progress }"
role="progressbar"
:aria-label="question.progressBarAriaLabel"
></div>
</div>
<SvComponent
Expand Down
1 change: 1 addition & 0 deletions packages/survey-vue3-ui/src/PanelDynamicProgressV2.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
:class="question.cssClasses.progressBar"
:style="{ width: question.progress }"
role="progressbar"
:aria-label="question.progressBarAriaLabel"
></div>
</div>
<div
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
role="progressbar"
aria-valuemin="0"
aria-valuemax="100"
aria-label="progress"
:aria-label="survey.progressBarAriaLabel"
>
<span :class="getProgressTextInBarCss(survey.css)">{{
survey.progressText
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
role="progressbar"
aria-valuemin="0"
aria-valuemax="100"
aria-label="progress"
:aria-label="model.progressBarAriaLabel"
>
<div v-if="canShowHeader" :class="survey.css.progressButtonsHeader">
<div
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<div class="sd-paneldynamic__footer">
<hr class="sd-paneldynamic__separator">
<div class="sd-progress">
<div class="sd-progress__bar" role="progressbar" style="width:50%;">
<div aria-label="Progress bar" class="sd-progress__bar" role="progressbar" style="width:50%;">
</div>
</div>
<div class="sd-paneldynamic__buttons-container">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<div class="sd-paneldynamic__footer">
<hr class="sd-paneldynamic__separator">
<div class="sd-progress">
<div class="sd-progress__bar" role="progressbar" style="width:50%;">
<div aria-label="Progress bar" class="sd-progress__bar" role="progressbar" style="width:50%;">
</div>
</div>
<div class="sd-paneldynamic__buttons-container">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<div class="sd-paneldynamic">
<div class="sd-progress">
<div class="sd-progress__bar" role="progressbar" style="width:50%;">
<div aria-label="Progress bar" class="sd-progress__bar" role="progressbar" style="width:50%;">
</div>
</div>
<div class="sd-paneldynamic__panels-container">
Expand Down
2 changes: 1 addition & 1 deletion tests/markup/snapshots/paneldynamic-progress-top.snap.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<div class="sd-paneldynamic">
<div class="sd-progress">
<div class="sd-progress__bar" role="progressbar" style="width:50%;">
<div aria-label="Progress bar" class="sd-progress__bar" role="progressbar" style="width:50%;">
</div>
</div>
<div class="sd-paneldynamic__panels-container">
Expand Down
2 changes: 1 addition & 1 deletion visualRegressionTests/tests/defaultV2/responsiveness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ frameworks.forEach(framework => {
"description": "Enter 8 characters minimum.",
"inputType": "password",
"isRequired": true,
"autocomplete": "password",
"autocomplete": "current-password",
"validators": [{
"type": "text",
"minLength": 8,
Expand Down