MOBILE-3708 core: Fix some a11y issues in dashboard, course and login
parent
4d001428ec
commit
ddbbb76ebc
|
@ -6,9 +6,11 @@
|
||||||
<div *ngFor="let item of items">
|
<div *ngFor="let item of items">
|
||||||
<ion-card>
|
<ion-card>
|
||||||
<ion-item class="core-course-module-handler item-media ion-text-wrap" detail="false" (click)="action($event, item)"
|
<ion-item class="core-course-module-handler item-media ion-text-wrap" detail="false" (click)="action($event, item)"
|
||||||
[title]="item.name" button>
|
button>
|
||||||
<img slot="start" [src]="item.iconUrl" alt="" role="presentation" *ngIf="item.iconUrl" class="core-module-icon">
|
<img slot="start" [src]="item.iconUrl" alt="" role="presentation" *ngIf="item.iconUrl" class="core-module-icon">
|
||||||
<ion-label>
|
<ion-label>
|
||||||
|
<!-- Add the icon title so accessibility tools read it. -->
|
||||||
|
<span class="sr-only" *ngIf="item.iconTitle">{{ item.iconTitle }}</span>
|
||||||
<h2>
|
<h2>
|
||||||
<core-format-text [text]="item.name" contextLevel="module" [contextInstanceId]="item.cmid"
|
<core-format-text [text]="item.name" contextLevel="module" [contextInstanceId]="item.cmid"
|
||||||
[courseId]="item.courseid"></core-format-text>
|
[courseId]="item.courseid"></core-format-text>
|
||||||
|
|
|
@ -56,6 +56,7 @@ export class AddonBlockRecentlyAccessedItemsProvider {
|
||||||
const modicon = item.icon && CoreDomUtils.getHTMLElementAttribute(item.icon, 'src');
|
const modicon = item.icon && CoreDomUtils.getHTMLElementAttribute(item.icon, 'src');
|
||||||
|
|
||||||
item.iconUrl = CoreCourse.getModuleIconSrc(item.modname, modicon || undefined);
|
item.iconUrl = CoreCourse.getModuleIconSrc(item.modname, modicon || undefined);
|
||||||
|
item.iconTitle = item.icon && CoreDomUtils.getHTMLElementAttribute(item.icon, 'title');
|
||||||
|
|
||||||
return item;
|
return item;
|
||||||
});
|
});
|
||||||
|
@ -99,4 +100,5 @@ export type AddonBlockRecentlyAccessedItemsItem = {
|
||||||
*/
|
*/
|
||||||
export type AddonBlockRecentlyAccessedItemsItemCalculatedData = {
|
export type AddonBlockRecentlyAccessedItemsItemCalculatedData = {
|
||||||
iconUrl: string; // Icon URL. Calculated by the app.
|
iconUrl: string; // Icon URL. Calculated by the app.
|
||||||
|
iconTitle?: string | null; // Icon title.
|
||||||
};
|
};
|
||||||
|
|
|
@ -21,8 +21,9 @@
|
||||||
[class.core-section-download]="downloadEnabled">
|
[class.core-section-download]="downloadEnabled">
|
||||||
<ion-button class="core-button-select button-no-uppercase" (click)="showSectionSelector()"
|
<ion-button class="core-button-select button-no-uppercase" (click)="showSectionSelector()"
|
||||||
aria-haspopup="true" [attr.aria-expanded]="sectionSelectorExpanded"
|
aria-haspopup="true" [attr.aria-expanded]="sectionSelectorExpanded"
|
||||||
id="core-course-section-button" expand="block"> <!-- @todo: attr.aria-label? -->
|
id="core-course-section-button" expand="block">
|
||||||
<ion-icon name="fas-folder" slot="start"></ion-icon>
|
<ion-icon name="fas-folder" slot="start" aria-hidden="true"></ion-icon>
|
||||||
|
<span class="sr-only" *ngIf="selectedSection">{{ 'core.course.sections' | translate }}:</span>
|
||||||
<span class="core-button-select-text">
|
<span class="core-button-select-text">
|
||||||
<core-format-text *ngIf="selectedSection" [text]="selectedSection.name" contextLevel="course"
|
<core-format-text *ngIf="selectedSection" [text]="selectedSection.name" contextLevel="course"
|
||||||
[contextInstanceId]="course?.id" [clean]="true" [singleLine]="true">
|
[contextInstanceId]="course?.id" [clean]="true" [singleLine]="true">
|
||||||
|
|
|
@ -9,23 +9,25 @@
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
<ion-content>
|
<ion-content>
|
||||||
<ion-list id="core-course-section-selector" role="menu">
|
<ion-list id="core-course-section-selector">
|
||||||
<ng-container *ngFor="let section of sections">
|
<ng-container *ngFor="let section of sections">
|
||||||
<ion-item *ngIf="!section.hiddenbynumsections && section.id != stealthModulesSectionId" class="ion-text-wrap"
|
<ion-item *ngIf="!section.hiddenbynumsections && section.id != stealthModulesSectionId" class="ion-text-wrap"
|
||||||
(click)="selectSection(section)" [class.core-selected-item]="selected?.id == section.id"
|
(click)="selectSection(section)" [class.core-selected-item]="selected?.id == section.id"
|
||||||
[class.item-dimmed]="section.visible === 0 || section.uservisible === false" detail="false" role="menuitem"
|
[class.item-dimmed]="section.visible === 0 || section.uservisible === false" detail="false"
|
||||||
[attr.aria-hidden]="section.uservisible === false" button>
|
[attr.aria-hidden]="section.uservisible === false" button>
|
||||||
|
|
||||||
<ion-icon name="fas-folder" slot="start"></ion-icon>
|
<ion-icon name="fas-folder" slot="start" aria-hidden="true"></ion-icon>
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h2><core-format-text [text]="section.name" contextLevel="course" [contextInstanceId]="course?.id">
|
<h2><core-format-text [text]="section.name" contextLevel="course" [contextInstanceId]="course?.id">
|
||||||
</core-format-text></h2>
|
</core-format-text></h2>
|
||||||
<core-progress-bar *ngIf="section.progress >= 0" [progress]="section.progress"></core-progress-bar>
|
<core-progress-bar *ngIf="section.progress >= 0" [progress]="section.progress"></core-progress-bar>
|
||||||
|
|
||||||
<ion-badge color="secondary" *ngIf="section.visible === 0 && section.uservisible !== false" class="ion-text-wrap">
|
<ion-badge color="secondary" *ngIf="section.visible === 0 && section.uservisible !== false"
|
||||||
|
class="ion-text-wrap">
|
||||||
{{ 'core.course.hiddenfromstudents' | translate }}
|
{{ 'core.course.hiddenfromstudents' | translate }}
|
||||||
</ion-badge>
|
</ion-badge>
|
||||||
<ion-badge color="secondary" *ngIf="section.visible === 0 && section.uservisible === false" class="ion-text-wrap">
|
<ion-badge color="secondary" *ngIf="section.visible === 0 && section.uservisible === false"
|
||||||
|
class="ion-text-wrap">
|
||||||
{{ 'core.notavailable' | translate }}
|
{{ 'core.notavailable' | translate }}
|
||||||
</ion-badge>
|
</ion-badge>
|
||||||
<ion-badge color="secondary" *ngIf="section.availabilityinfo" class="ion-text-wrap">
|
<ion-badge color="secondary" *ngIf="section.availabilityinfo" class="ion-text-wrap">
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
[class.core-course-with-buttons]="courseOptionMenuEnabled || (downloadCourseEnabled && showDownload)"
|
[class.core-course-with-buttons]="courseOptionMenuEnabled || (downloadCourseEnabled && showDownload)"
|
||||||
[class.core-course-with-spinner]="(downloadCourseEnabled && prefetchCourseData.icon == 'spinner') || showSpinner">
|
[class.core-course-with-spinner]="(downloadCourseEnabled && prefetchCourseData.icon == 'spinner') || showSpinner">
|
||||||
<p *ngIf="course.categoryname || (course.displayname && course.shortname && course.fullname != course.displayname)"
|
<p *ngIf="course.categoryname || (course.displayname && course.shortname && course.fullname != course.displayname)"
|
||||||
class="core-course-additional-info">
|
class="core-course-additional-info" aria-hidden="true">
|
||||||
<span *ngIf="course.categoryname" class="core-course-category">
|
<span *ngIf="course.categoryname" class="core-course-category">
|
||||||
<core-format-text [text]="course.categoryname"></core-format-text>
|
<core-format-text [text]="course.categoryname"></core-format-text>
|
||||||
</span>
|
</span>
|
||||||
|
@ -24,7 +24,10 @@
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
<h2>
|
<h2>
|
||||||
<ion-icon name="fas-star" *ngIf="course.isfavourite"></ion-icon>
|
<ion-icon name="fas-star" *ngIf="course.isfavourite" [attr.aria-label]="'core.courses.favourite' | translate">
|
||||||
|
</ion-icon>
|
||||||
|
<span class="sr-only" *ngIf="course.isfavourite">{{ 'core.courses.aria:favourite' | translate }}</span>
|
||||||
|
<span class="sr-only">{{ 'core.courses.aria:coursename' | translate }}</span>
|
||||||
<core-format-text [text]="course.fullname" contextLevel="course" [contextInstanceId]="course.id"></core-format-text>
|
<core-format-text [text]="course.fullname" contextLevel="course" [contextInstanceId]="course.id"></core-format-text>
|
||||||
</h2>
|
</h2>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
|
@ -50,9 +53,10 @@
|
||||||
</ion-button>
|
</ion-button>
|
||||||
</div>
|
</div>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item *ngIf="showAll && course.progress! >= 0 && course.completionusertracked !== false"
|
<ion-item *ngIf="showAll && course.progress! >= 0 && course.completionusertracked !== false" lines="none">
|
||||||
lines="none">
|
<ion-label>
|
||||||
<ion-label><core-progress-bar [progress]="course.progress"></core-progress-bar></ion-label>
|
<core-progress-bar [progress]="course.progress"></core-progress-bar>
|
||||||
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ng-content></ng-content>
|
<ng-content></ng-content>
|
||||||
</ion-card>
|
</ion-card>
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
{
|
{
|
||||||
"addtofavourites": "Star this course",
|
"addtofavourites": "Star this course",
|
||||||
"allowguests": "This course allows guest users to enter",
|
"allowguests": "This course allows guest users to enter",
|
||||||
|
"aria:coursename": "Course name",
|
||||||
|
"aria:courseprogress": "Course progress:",
|
||||||
|
"aria:favourite": "Course is starred",
|
||||||
"availablecourses": "Available courses",
|
"availablecourses": "Available courses",
|
||||||
"cannotretrievemorecategories": "Categories deeper than level {{$a}} cannot be retrieved.",
|
"cannotretrievemorecategories": "Categories deeper than level {{$a}} cannot be retrieved.",
|
||||||
"categories": "Course categories",
|
"categories": "Course categories",
|
||||||
|
@ -13,6 +16,7 @@
|
||||||
"errorloadplugins": "The plugins required by this course could not be loaded correctly. Please reload the app to try again.",
|
"errorloadplugins": "The plugins required by this course could not be loaded correctly. Please reload the app to try again.",
|
||||||
"errorsearching": "An error occurred while searching.",
|
"errorsearching": "An error occurred while searching.",
|
||||||
"errorselfenrol": "An error occurred while self enrolling.",
|
"errorselfenrol": "An error occurred while self enrolling.",
|
||||||
|
"favourite": "Starred course",
|
||||||
"filtermycourses": "Filter my courses",
|
"filtermycourses": "Filter my courses",
|
||||||
"frontpage": "Front page",
|
"frontpage": "Front page",
|
||||||
"hidecourse": "Remove from view",
|
"hidecourse": "Remove from view",
|
||||||
|
|
|
@ -49,7 +49,7 @@
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item button *ngIf="enteredSiteUrl" (click)="connect($event, enteredSiteUrl.url)"
|
<ion-item button *ngIf="enteredSiteUrl" (click)="connect($event, enteredSiteUrl.url)"
|
||||||
[attr.aria-label]="'core.login.connect' | translate" detail-push class="core-login-entered-site">
|
[attr.aria-label]="'core.login.connect' | translate" detail-push class="core-login-entered-site">
|
||||||
<ion-thumbnail slot="start">
|
<ion-thumbnail slot="start" aria-hidden="true">
|
||||||
<ion-icon name="fas-pen"></ion-icon>
|
<ion-icon name="fas-pen"></ion-icon>
|
||||||
</ion-thumbnail>
|
</ion-thumbnail>
|
||||||
<ion-label>
|
<ion-label>
|
||||||
|
@ -101,7 +101,8 @@
|
||||||
|
|
||||||
<ng-container *ngIf="showScanQR && !hasSites && !enteredSiteUrl">
|
<ng-container *ngIf="showScanQR && !hasSites && !enteredSiteUrl">
|
||||||
<div class="ion-text-center ion-padding ion-margin-top">{{ 'core.login.or' | translate }}</div>
|
<div class="ion-text-center ion-padding ion-margin-top">{{ 'core.login.or' | translate }}</div>
|
||||||
<ion-button expand="block" color="light" class="ion-margin" lines="none" (click)="showInstructionsAndScanQR()">
|
<ion-button expand="block" color="light" class="ion-margin" lines="none" (click)="showInstructionsAndScanQR()"
|
||||||
|
aria-haspopup="true">
|
||||||
<ion-icon slot="start" name="fas-qrcode" aria-hidden="true"></ion-icon>
|
<ion-icon slot="start" name="fas-qrcode" aria-hidden="true"></ion-icon>
|
||||||
<ion-label>{{ 'core.scanqr' | translate }}</ion-label>
|
<ion-label>{{ 'core.scanqr' | translate }}</ion-label>
|
||||||
</ion-button>
|
</ion-button>
|
||||||
|
@ -109,7 +110,8 @@
|
||||||
|
|
||||||
<!-- Help. -->
|
<!-- Help. -->
|
||||||
<ion-list lines="none" class="ion-margin-top">
|
<ion-list lines="none" class="ion-margin-top">
|
||||||
<ion-item button class="ion-text-center ion-text-wrap core-login-need-help" (click)="showHelp()" detail="false">
|
<ion-item button class="ion-text-center ion-text-wrap core-login-need-help" (click)="showHelp()" detail="false"
|
||||||
|
aria-haspopup="true">
|
||||||
<ion-label>{{ 'core.needhelp' | translate }}</ion-label>
|
<ion-label>{{ 'core.needhelp' | translate }}</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</ion-list>
|
</ion-list>
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
// Text for accessibility, hidden from the view.
|
||||||
|
.sr-only, .accesshide {
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
height: 1px;
|
||||||
|
padding: 0;
|
||||||
|
margin: -1px;
|
||||||
|
overflow: hidden;
|
||||||
|
clip: rect(0, 0, 0, 0);
|
||||||
|
white-space: nowrap;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sr-only-focusable:active, .sr-only-focusable:focus {
|
||||||
|
position: static;
|
||||||
|
width: auto;
|
||||||
|
height: auto;
|
||||||
|
overflow: visible;
|
||||||
|
clip: auto;
|
||||||
|
white-space: normal;
|
||||||
|
}
|
|
@ -436,14 +436,6 @@ ion-button.core-button-select {
|
||||||
background-color: var(--text-hightlight-background-color);
|
background-color: var(--text-hightlight-background-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Text for accessibility, hidden from the view.
|
|
||||||
.accesshide {
|
|
||||||
position: absolute;
|
|
||||||
left: -10000px;
|
|
||||||
font-weight: normal;
|
|
||||||
font-size: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Monospaced font.
|
// Monospaced font.
|
||||||
.core-monospaced {
|
.core-monospaced {
|
||||||
font-family: Andale Mono,Monaco,Courier New,DejaVu Sans Mono,monospace;
|
font-family: Andale Mono,Monaco,Courier New,DejaVu Sans Mono,monospace;
|
||||||
|
|
|
@ -23,6 +23,9 @@
|
||||||
@import "./components/rubrics.scss";
|
@import "./components/rubrics.scss";
|
||||||
@import "./components/mod-label.scss";
|
@import "./components/mod-label.scss";
|
||||||
|
|
||||||
|
/* Some styles from 3rd party libraries. */
|
||||||
|
@import "./bootstrap.scss";
|
||||||
|
|
||||||
/* Core CSS required for Ionic components to work properly */
|
/* Core CSS required for Ionic components to work properly */
|
||||||
@import "~@ionic/angular/css/core.css";
|
@import "~@ionic/angular/css/core.css";
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue