MOBILE-2819 a11y: Apply aria attributes to components

main
Pau Ferrer Ocaña 2019-02-26 15:06:33 +01:00
parent 4c602c62bf
commit b95de260ee
27 changed files with 144 additions and 74 deletions

View File

@ -7,7 +7,6 @@ $addon-mod-wiki-toc-background-color: $gray-light !default;
ion-app.app-root addon-mod-wiki-index { ion-app.app-root addon-mod-wiki-index {
background-color: $white; background-color: $white;
.core-tabs-content-container,
.addon-mod_wiki-page-content { .addon-mod_wiki-page-content {
background-color: $white; background-color: $white;
} }

View File

@ -28,7 +28,7 @@ import { Component, Input, OnChanges, OnDestroy, Output, EventEmitter, SimpleCha
*/ */
@Component({ @Component({
selector: 'core-chrono', selector: 'core-chrono',
template: '<span>{{ time / 1000 | coreSecondsToHMS }}</span>' template: '<span role="timer">{{ time / 1000 | coreSecondsToHMS }}</span>'
}) })
export class CoreChronoComponent implements OnChanges, OnDestroy { export class CoreChronoComponent implements OnChanges, OnDestroy {
@Input() running: boolean; // Set it to true to start the chrono. Set it to false to stop it. @Input() running: boolean; // Set it to true to start the chrono. Set it to false to stop it.

View File

@ -26,12 +26,14 @@ import { CoreLoggerProvider } from '@providers/logger';
}) })
export class CoreContextMenuPopoverComponent { export class CoreContextMenuPopoverComponent {
title: string; title: string;
uniqueId: string;
items: CoreContextMenuItemComponent[]; items: CoreContextMenuItemComponent[];
protected logger: any; protected logger: any;
constructor(navParams: NavParams, private viewCtrl: ViewController, logger: CoreLoggerProvider) { constructor(navParams: NavParams, private viewCtrl: ViewController, logger: CoreLoggerProvider) {
this.title = navParams.get('title'); this.title = navParams.get('title');
this.items = navParams.get('items') || []; this.items = navParams.get('items') || [];
this.uniqueId = navParams.get('id');
this.logger = logger.getInstance('CoreContextMenuPopoverComponent'); this.logger = logger.getInstance('CoreContextMenuPopoverComponent');
} }

View File

@ -16,6 +16,7 @@ import { Component, Input, OnInit, OnDestroy, ElementRef, Optional } from '@angu
import { PopoverController } from 'ionic-angular'; import { PopoverController } from 'ionic-angular';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreUtilsProvider } from '@providers/utils/utils';
import { CoreContextMenuItemComponent } from './context-menu-item'; import { CoreContextMenuItemComponent } from './context-menu-item';
import { CoreContextMenuPopoverComponent } from './context-menu-popover'; import { CoreContextMenuPopoverComponent } from './context-menu-popover';
import { CoreTabComponent } from '@components/tabs/tab'; import { CoreTabComponent } from '@components/tabs/tab';
@ -34,14 +35,16 @@ export class CoreContextMenuComponent implements OnInit, OnDestroy {
hideMenu = true; // It will be unhidden when items are added. hideMenu = true; // It will be unhidden when items are added.
ariaLabel: string; ariaLabel: string;
expanded = false;
protected items: CoreContextMenuItemComponent[] = []; protected items: CoreContextMenuItemComponent[] = [];
protected itemsMovedToParent: CoreContextMenuItemComponent[] = []; protected itemsMovedToParent: CoreContextMenuItemComponent[] = [];
protected itemsChangedStream: Subject<void>; // Stream to update the hideMenu boolean when items change. protected itemsChangedStream: Subject<void>; // Stream to update the hideMenu boolean when items change.
protected instanceId: string; protected instanceId: string;
protected parentContextMenu: CoreContextMenuComponent; protected parentContextMenu: CoreContextMenuComponent;
protected uniqueId: string;
constructor(private translate: TranslateService, private popoverCtrl: PopoverController, elementRef: ElementRef, constructor(private translate: TranslateService, private popoverCtrl: PopoverController, elementRef: ElementRef,
private domUtils: CoreDomUtilsProvider, @Optional() public coreTab: CoreTabComponent) { private domUtils: CoreDomUtilsProvider, @Optional() public coreTab: CoreTabComponent, utils: CoreUtilsProvider) {
// Create the stream and subscribe to it. We ignore successive changes during 250ms. // Create the stream and subscribe to it. We ignore successive changes during 250ms.
this.itemsChangedStream = new Subject<void>(); this.itemsChangedStream = new Subject<void>();
this.itemsChangedStream.auditTime(250).subscribe(() => { this.itemsChangedStream.auditTime(250).subscribe(() => {
@ -56,6 +59,9 @@ export class CoreContextMenuComponent implements OnInit, OnDestroy {
}); });
}); });
// Calculate the unique ID.
this.uniqueId = 'core-context-menu-' + utils.getUniqueId('CoreContextMenuComponent');
this.instanceId = this.domUtils.storeInstanceByElement(elementRef.nativeElement, this); this.instanceId = this.domUtils.storeInstanceByElement(elementRef.nativeElement, this);
} }
@ -170,10 +176,19 @@ export class CoreContextMenuComponent implements OnInit, OnDestroy {
* @param {MouseEvent} event Event. * @param {MouseEvent} event Event.
*/ */
showContextMenu(event: MouseEvent): void { showContextMenu(event: MouseEvent): void {
const popover = this.popoverCtrl.create(CoreContextMenuPopoverComponent, { title: this.title, items: this.items }); if (!this.expanded) {
popover.present({ const popover = this.popoverCtrl.create(CoreContextMenuPopoverComponent,
ev: event { title: this.title, items: this.items, id: this.uniqueId });
});
popover.onDidDismiss(() => {
this.expanded = false;
});
popover.present({
ev: event
});
this.expanded = true;
}
} }
/** /**

View File

@ -1,6 +1,6 @@
<ion-list> <ion-list [id]="uniqueId" role="menu">
<ion-list-header *ngIf="title">{{title}}</ion-list-header> <ion-list-header *ngIf="title">{{title}}</ion-list-header>
<a ion-item text-wrap *ngFor="let item of items" core-link [capture]="item.captureLink" [autoLogin]="item.autoLogin" [href]="item.href" (click)="itemClicked($event, item)" [attr.aria-label]="item.ariaAction" [hidden]="item.hidden" [attr.detail-none]="!item.href || item.iconAction"> <a ion-item text-wrap *ngFor="let item of items" core-link [capture]="item.captureLink" [autoLogin]="item.autoLogin" [href]="item.href" (click)="itemClicked($event, item)" [attr.aria-label]="item.ariaAction" [hidden]="item.hidden" [attr.detail-none]="!item.href || item.iconAction" role="menuitem" [attr.aria-controls]="uniqueId">
<core-icon *ngIf="item.iconDescription" [name]="item.iconDescription" [attr.aria-label]="item.ariaDescription" item-start></core-icon> <core-icon *ngIf="item.iconDescription" [name]="item.iconDescription" [attr.aria-label]="item.ariaDescription" item-start></core-icon>
<core-format-text [clean]="true" [text]="item.content"></core-format-text> <core-format-text [clean]="true" [text]="item.content"></core-format-text>
<core-icon *ngIf="(item.href || item.action) && item.iconAction && item.iconAction != 'spinner'" [name]="item.iconAction" item-end></core-icon> <core-icon *ngIf="(item.href || item.action) && item.iconAction && item.iconAction != 'spinner'" [name]="item.iconAction" item-end></core-icon>

View File

@ -1,4 +1,4 @@
<button [hidden]="hideMenu" ion-button clear icon-only [attr.aria-label]="ariaLabel" (click)="showContextMenu($event)" class="bar-button"> <button [hidden]="hideMenu" ion-button clear icon-only [attr.aria-label]="ariaLabel" (click)="showContextMenu($event)" class="bar-button" aria-haspopup="true" [attr.aria-expanded]="expanded" [attr.aria-controls]="uniqueId">
<core-icon [name]="icon"></core-icon> <core-icon [name]="icon"></core-icon>
</button> </button>
<ng-content></ng-content> <ng-content></ng-content>

View File

@ -1 +1 @@
<ion-icon [name]="name" [isActive]="isActive" [md]="md" [ios]="ios" [color]="color"></ion-icon> <ion-icon [name]="name" [isActive]="isActive" [md]="md" [ios]="ios" [color]="color" aria-hidden="true" role="presentation"></ion-icon>

View File

@ -54,6 +54,8 @@ export class CoreIconComponent implements OnInit, OnDestroy {
this.newElement.classList.add('icon'); this.newElement.classList.add('icon');
this.newElement.classList.add('fa'); this.newElement.classList.add('fa');
this.newElement.classList.add(this.name); this.newElement.classList.add(this.name);
this.newElement.setAttribute('aria-hidden', 'true');
this.newElement.setAttribute('role', 'img');
if (this.isTrueProperty(this.fixedWidth)) { if (this.isTrueProperty(this.fixedWidth)) {
this.newElement.classList.add('fa-fw'); this.newElement.classList.add('fa-fw');
} }

View File

@ -12,5 +12,5 @@
</div> </div>
</ng-container> </ng-container>
</ng-container> </ng-container>
<div *ngIf="errorText" class="core-input-error">{{ errorText }}</div> <div *ngIf="errorText" class="core-input-error" aria-live="assertive">{{ errorText }}</div>
</div> </div>

View File

@ -1,5 +1,5 @@
<div class="tabbar" role="tablist" #tabbar [hidden]="hidden"> <div class="tabbar" role="tablist" #tabbar>
<a [hidden]="_loaded === false" *ngFor="let t of _tabs" [tab]="t" class="tab-button" role="tab" href="#" (ionSelect)="select(t)"></a> <a [hidden]="_loaded === false" *ngFor="let t of _tabs" [tab]="t" class="tab-button" role="tab" href="#" (ionSelect)="select(t)" [attr.aria-hidden]="!t.show" [attr.aria-label]="t.tabTitle || ''"></a>
<div class="tab-highlight"></div> <div class="tab-highlight"></div>
<div *ngIf="_loaded === false" class="core-ion-tabs-loading"> <div *ngIf="_loaded === false" class="core-ion-tabs-loading">
<span class="core-ion-tabs-loading-spinner"> <span class="core-ion-tabs-loading-spinner">

View File

@ -1,10 +1,10 @@
<div [@coreShowHideAnimation] class="core-loading-container" *ngIf="!hideUntil"> <div [@coreShowHideAnimation] class="core-loading-container" *ngIf="!hideUntil" role="status">
<span class="core-loading-spinner"> <span class="core-loading-spinner">
<ion-spinner></ion-spinner> <ion-spinner></ion-spinner>
<p class="core-loading-message" *ngIf="message">{{message}}</p> <p class="core-loading-message" *ngIf="message" role="status">{{message}}</p>
</span> </span>
</div> </div>
<div #content class="core-loading-content" [id]="uniqueId"> <div #content class="core-loading-content" [id]="uniqueId" [attr.aria-busy]="hideUntil">
<ng-content [@coreShowHideAnimation] *ngIf="hideUntil"> <ng-content [@coreShowHideAnimation] *ngIf="hideUntil">
</ng-content> </ng-content>
</div> </div>

View File

@ -1,5 +1,5 @@
<div *ngIf="progress >= 0"> <div *ngIf="progress >= 0">
<progress max="100" [value]="progress"> <progress max="100" [value]="progress" role="progressbar" aria-valuemin="0" aria-valuemax="100" [attr.aria-valuenow]="progress">
<div class="progress-bar-fallback" role="progressbar" aria-valuemin="0" aria-valuemax="100" [attr.aria-valuenow]="progress"> <div class="progress-bar-fallback" role="progressbar" aria-valuemin="0" aria-valuemax="100" [attr.aria-valuenow]="progress">
<span [style.width]="width"></span> <span [style.width]="width"></span>
</div> </div>

View File

@ -1,5 +1,5 @@
<div [hidden]="!rteEnabled"> <div [hidden]="!rteEnabled">
<div #editor contenteditable="true" class="core-rte-editor" tappable [attr.data-placeholder-text]="placeholder"> <div #editor contenteditable="true" class="core-rte-editor" tappable [attr.data-placeholder-text]="placeholder" role="textbox">
</div> </div>
<!-- https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand --> <!-- https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand -->
@ -22,7 +22,7 @@
</div> </div>
<div [hidden]="rteEnabled"> <div [hidden]="rteEnabled">
<ion-textarea #textarea class="core-textarea" [placeholder]="placeholder" [attr.name]="name" ngControl="control" (ionChange)="onChange($event)"></ion-textarea> <ion-textarea #textarea class="core-textarea" [placeholder]="placeholder" [attr.name]="name" ngControl="control" (ionChange)="onChange($event)" role="textbox"></ion-textarea>
<div class="core-rte-toolbar" [hidden]="!editorSupported"> <div class="core-rte-toolbar" [hidden]="!editorSupported">
<div #decorate class="core-rte-buttons"> <div #decorate class="core-rte-buttons">
<button tappable [core-suppress-events] (onClick)="toggleEditor($event)"><core-icon name="fa-pencil-square-o"></core-icon> {{ 'core.vieweditor' | translate }}</button> <button tappable [core-suppress-events] (onClick)="toggleEditor($event)"><core-icon name="fa-pencil-square-o"></core-icon> {{ 'core.vieweditor' | translate }}</button>

View File

@ -1,7 +1,7 @@
<ion-card> <ion-card>
<form #f="ngForm" (ngSubmit)="submitForm($event)"> <form #f="ngForm" (ngSubmit)="submitForm($event)" role="search">
<ion-item> <ion-item>
<ion-input type="text" name="search" [(ngModel)]="searchText" [placeholder]="placeholder" [autocorrect]="autocorrect" [spellcheck]="spellcheck" [core-auto-focus]="autoFocus" [disabled]="disabled"></ion-input> <ion-input type="search" name="search" [(ngModel)]="searchText" [placeholder]="placeholder" [autocorrect]="autocorrect" [spellcheck]="spellcheck" [core-auto-focus]="autoFocus" [disabled]="disabled" role="searchbox"></ion-input>
<button item-end ion-button clear icon-only type="submit" class="button-small" [attr.aria-label]="searchLabel" [disabled]="!searchText || (searchText.length < lengthCheck)" [disabled]="disabled"> <button item-end ion-button clear icon-only type="submit" class="button-small" [attr.aria-label]="searchLabel" [disabled]="!searchText || (searchText.length < lengthCheck)" [disabled]="disabled">
<ion-icon name="search"></ion-icon> <ion-icon name="search"></ion-icon>
</button> </button>

View File

@ -1,5 +1,5 @@
<form> <form>
<textarea class="core-send-message-input" [core-auto-focus]="showKeyboard" [placeholder]="placeholder" rows="1" core-auto-rows [(ngModel)]="message" name="message" (onResize)="textareaResized()" (keydown.enter)="enterClicked($event)" (keydown.control.enter)="enterClicked($event, 'control')" (keydown.meta.enter)="enterClicked($event, 'meta')"></textarea> <textarea class="core-send-message-input" [core-auto-focus]="showKeyboard" [placeholder]="placeholder" rows="1" core-auto-rows [(ngModel)]="message" name="message" (onResize)="textareaResized()" (keydown.enter)="enterClicked($event)" (keydown.control.enter)="enterClicked($event, 'control')" (keydown.meta.enter)="enterClicked($event, 'meta')" aria-multiline="true"></textarea>
<ion-buttons end> <ion-buttons end>
<button ion-button icon-only clear="true" type="submit" [disabled]="!message" [attr.aria-label]="'core.send' | translate" [core-suppress-events] (onClick)="submitForm($event)"> <button ion-button icon-only clear="true" type="submit" [disabled]="!message" [attr.aria-label]="'core.send' | translate" [core-suppress-events] (onClick)="submitForm($event)">
<ion-icon name="send" color="dark"></ion-icon> <ion-icon name="send" color="dark"></ion-icon>

View File

@ -1,23 +1,21 @@
<core-loading [hideUntil]="hideUntil" class="core-loading-center"> <core-loading [hideUntil]="hideUntil" class="core-loading-center">
<div class="core-tabs-bar" #topTabs [hidden]="!tabs || numTabsShown < 2"> <div class="core-tabs-bar" #topTabs [hidden]="!tabs || numTabsShown < 2" id="core-tabs-bar">
<ion-row> <ion-row>
<ion-col class="col-with-arrow" (click)="slidePrev()" no-padding col-1> <ion-col class="col-with-arrow" (click)="slidePrev()" no-padding col-1 aria-hidden="true">
<ion-icon *ngIf="showPrevButton" name="arrow-back" md="ios-arrow-back"></ion-icon> <ion-icon *ngIf="showPrevButton" name="arrow-back" md="ios-arrow-back"></ion-icon>
</ion-col> </ion-col>
<ion-col no-padding col-10> <ion-col no-padding col-10>
<ion-slides (ionSlideDidChange)="slideChanged()" [slidesPerView]="slidesShown" [dir]="direction"> <ion-slides (ionSlideDidChange)="slideChanged()" [slidesPerView]="slidesShown" [dir]="direction" role="tablist" [attr.aria-label]="description" aria-hidden="false">
<ng-container *ngFor="let tab of tabs; let idx = index"> <ng-container *ngFor="let tab of tabs; let idx = index">
<ion-slide *ngIf="tab.show"> <ion-slide *ngIf="tab.show" [attr.aria-selected]="selected == idx" (click)="selectTab(idx)" class="tab-slide" [attr.aria-label]="tab.title || ''" role="tab" [attr.aria-controls]="tab.id" [id]="tab.id + '-tab'" [tabindex]="selected == idx ? null : -1">
<a [attr.aria-selected]="selected == idx" (click)="selectTab(idx)" class="tab-slide"> <core-icon *ngIf="tab.icon" [name]="tab.icon"></core-icon>
<core-icon *ngIf="tab.icon" [name]="tab.icon"></core-icon> <div *ngIf="tab.title">{{ tab.title }}</div>
<span *ngIf="tab.title">{{ tab.title }}</span> <ion-badge *ngIf="tab.badge" [color]="tab.badgeStyle" class="tab-badge">{{tab.badge}}</ion-badge>
<ion-badge *ngIf="tab.badge" [color]="tab.badgeStyle" class="tab-badge">{{tab.badge}}</ion-badge>
</a>
</ion-slide> </ion-slide>
</ng-container> </ng-container>
</ion-slides> </ion-slides>
</ion-col> </ion-col>
<ion-col class="col-with-arrow" (click)="slideNext()" no-padding col-1> <ion-col class="col-with-arrow" (click)="slideNext()" no-padding col-1 aria-hidden="true">
<ion-icon *ngIf="showNextButton" name="arrow-forward" md="ios-arrow-forward"></ion-icon> <ion-icon *ngIf="showNextButton" name="arrow-forward" md="ios-arrow-forward"></ion-icon>
</ion-col> </ion-col>
</ion-row> </ion-row>

View File

@ -16,6 +16,7 @@ import { Component, Input, Output, OnInit, OnDestroy, ElementRef, EventEmitter,
import { CoreTabsComponent } from './tabs'; import { CoreTabsComponent } from './tabs';
import { Content } from 'ionic-angular'; import { Content } from 'ionic-angular';
import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreUtilsProvider } from '@providers/utils/utils';
import { CoreNavBarButtonsComponent } from '../navbar-buttons/navbar-buttons'; import { CoreNavBarButtonsComponent } from '../navbar-buttons/navbar-buttons';
/** /**
@ -54,6 +55,9 @@ export class CoreTabComponent implements OnInit, OnDestroy {
if (this.initialized && hasChanged) { if (this.initialized && hasChanged) {
this.tabs.tabVisibilityChanged(); this.tabs.tabVisibilityChanged();
this.tabElement = document.getElementById(this.id + '-tab');
this.tabElement && this.tabElement.setAttribute('aria-hidden', !this._show);
} }
} }
} }
@ -70,17 +74,31 @@ export class CoreTabComponent implements OnInit, OnDestroy {
loaded = false; loaded = false;
initialized = false; initialized = false;
_show = true; _show = true;
tabElement: any;
constructor(protected tabs: CoreTabsComponent, element: ElementRef, protected domUtils: CoreDomUtilsProvider) { constructor(protected tabs: CoreTabsComponent, element: ElementRef, protected domUtils: CoreDomUtilsProvider,
utils: CoreUtilsProvider) {
this.element = element.nativeElement; this.element = element.nativeElement;
this.element.setAttribute('role', 'tabpanel');
this.element.setAttribute('tabindex', '0');
this.id = this.id || 'core-tab-' + utils.getUniqueId('CoreTabComponent');
} }
/** /**
* Component being initialized. * Component being initialized.
*/ */
ngOnInit(): void { ngOnInit(): void {
this.element.setAttribute('aria-labelledby', this.id + '-tab');
this.element.setAttribute('id', this.id);
this.tabs.addTab(this); this.tabs.addTab(this);
this.initialized = true; this.initialized = true;
setTimeout(() => {
this.tabElement = document.getElementById(this.id + '-tab');
this.tabElement && this.tabElement.setAttribute('aria-hidden', !this._show);
}, 1000);
} }
/** /**
@ -96,6 +114,11 @@ export class CoreTabComponent implements OnInit, OnDestroy {
selectTab(): void { selectTab(): void {
this.element.classList.add('selected'); this.element.classList.add('selected');
this.tabElement = this.tabElement || document.getElementById(this.id + '-tab');
this.tabElement && this.tabElement.setAttribute('aria-selected', true);
this.tabElement && this.tabElement.setAttribute('aria-hidden', !this._show);
this.loaded = true; this.loaded = true;
this.ionSelect.emit(this); this.ionSelect.emit(this);
this.showHideNavBarButtons(true); this.showHideNavBarButtons(true);
@ -114,6 +137,9 @@ export class CoreTabComponent implements OnInit, OnDestroy {
* Unselect tab. * Unselect tab.
*/ */
unselectTab(): void { unselectTab(): void {
this.tabElement && this.tabElement.setAttribute('aria-selected', false);
this.tabElement && this.tabElement.setAttribute('aria-hidden', true);
this.element.classList.remove('selected'); this.element.classList.remove('selected');
this.showHideNavBarButtons(false); this.showHideNavBarButtons(false);
} }

View File

@ -10,19 +10,20 @@ ion-app.app-root .core-tabs-bar {
width: 100%; width: 100%;
} }
a.tab-slide { ion-slide.tab-slide {
@extend .tab-button; @extend .tab-button;
background: $core-top-tabs-background; background: $core-top-tabs-background;
color: $core-top-tabs-color !important; color: $core-top-tabs-color !important;
font-size: 1.6rem; font-size: 1.6rem;
border: 0; border-bottom: 2px solid transparent !important;
padding: 0 5px 0 5px !important; padding: 0 2px 0 2px !important;
margin: 0 auto !important; margin: 0 auto !important;
display: flex; display: flex;
flex-direction: row; flex: none;
min-width: 100px;
span { div {
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
@ -44,9 +45,12 @@ ion-app.app-root .core-tabs-bar {
&[aria-selected=true] { &[aria-selected=true] {
color: $core-top-tabs-color-active !important; color: $core-top-tabs-color-active !important;
border: 0 !important; border-bottom-color: $core-top-tabs-border-active !important;
border-bottom: 2px solid $core-top-tabs-border-active !important; }
padding: 0 !important;
.slide-zoom {
display: flex;
flex-direction: column;
} }
} }
@ -64,7 +68,7 @@ ion-app.app-root .core-tabs-bar {
} }
} }
ion-app.app-root.md .core-tabs-bar a.tab-slide { ion-app.app-root.md .core-tabs-bar ion-slide.tab-slide {
// @extend .tabs-md .tab-button; // @extend .tabs-md .tab-button;
min-height: $tabs-md-tab-min-height; min-height: $tabs-md-tab-min-height;
@ -72,7 +76,7 @@ ion-app.app-root.md .core-tabs-bar a.tab-slide {
color: $tabs-md-tab-text-color; color: $tabs-md-tab-text-color;
} }
ion-app.app-root.ios .core-tabs-bar a.tab-slide { ion-app.app-root.ios .core-tabs-bar ion-slide.tab-slide {
// @extend .tabs-ios .tab-button; // @extend .tabs-ios .tab-button;
max-width: $tabs-ios-tab-max-width; max-width: $tabs-ios-tab-max-width;
min-height: $tabs-ios-tab-min-height; min-height: $tabs-ios-tab-min-height;
@ -82,7 +86,7 @@ ion-app.app-root.ios .core-tabs-bar a.tab-slide {
color: $tabs-ios-tab-text-color; color: $tabs-ios-tab-text-color;
} }
ion-app.app-root.wp .core-tabs-bar a.tab-slide { ion-app.app-root.wp .core-tabs-bar ion-slide.tab-slide {
//@extend .tabs-wp .tab-button; //@extend .tabs-wp .tab-button;
@include border-radius(0); @include border-radius(0);
@ -124,6 +128,7 @@ ion-app.app-root core-tabs {
display: none; display: none;
height: 100%; height: 100%;
position: relative; position: relative;
z-index: 1;
&.selected { &.selected {
display: block; display: block;

View File

@ -62,6 +62,7 @@ export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges, OnDe
slidesShown = this.maxSlides; slidesShown = this.maxSlides;
numTabsShown = 0; numTabsShown = 0;
direction = 'ltr'; direction = 'ltr';
description = '';
protected originalTabsContainer: HTMLElement; // The container of the original tabs. It will include each tab's content. protected originalTabsContainer: HTMLElement; // The container of the original tabs. It will include each tab's content.
protected initialized = false; protected initialized = false;
@ -238,8 +239,8 @@ export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges, OnDe
/** /**
* Get the index of tab. * Get the index of tab.
* *
* @param {any} tab [description] * @param {any} tab Tab object to check.
* @return {number} [description] * @return {number} Index number on the tabs array or -1 if not found.
*/ */
getIndex(tab: any): number { getIndex(tab: any): number {
for (let i = 0; i < this.tabs.length; i++) { for (let i = 0; i < this.tabs.length; i++) {

View File

@ -1,5 +1,5 @@
<ion-item #container class="core-timer" [attr.text-center]="align == 'center' ? true : null" [attr.text-end]="align == 'right' ? true : null"> <ion-item #container class="core-timer" [attr.text-center]="align == 'center' ? true : null" [attr.text-end]="align == 'right' ? true : null" role="timer">
<ion-icon name="timer" item-start></ion-icon> <ion-icon name="timer" item-start role="presentation"></ion-icon>
<label *ngIf="timeLeft > 0 && timerText">{{ timerText }}</label> <label *ngIf="timeLeft > 0 && timerText">{{ timerText }}</label>
<span *ngIf="timeLeft > 0">{{ timeLeft | coreSecondsToHMS }}</span> <span *ngIf="timeLeft > 0">{{ timeLeft | coreSecondsToHMS }}</span>
<span class="core-timesup" *ngIf="timeLeft <= 0">{{ 'core.timesup' | translate }}</span> <span class="core-timesup" *ngIf="timeLeft <= 0">{{ 'core.timesup' | translate }}</span>

View File

@ -11,7 +11,7 @@
<!-- Section selector. --> <!-- Section selector. -->
<core-dynamic-component [component]="sectionSelectorComponent" [data]="data"> <core-dynamic-component [component]="sectionSelectorComponent" [data]="data">
<div text-wrap *ngIf="displaySectionSelector && sections && sections.length" padding class="clearfix" ion-row justify-content-between class="safe-padding-horizontal"> <div text-wrap *ngIf="displaySectionSelector && sections && sections.length" padding class="clearfix" ion-row justify-content-between class="safe-padding-horizontal">
<button float-start ion-button icon-start icon-end (click)="showSectionSelector($event)" color="light" class="core-button-select button-no-uppercase" ion-col> <button float-start ion-button icon-start icon-end (click)="showSectionSelector($event)" color="light" class="core-button-select button-no-uppercase" ion-col [attr.aria-label]="('core.course.sections' | translate) + ': ' + (selectedSection && (selectedSection.formattedName || selectedSection.name))" aria-haspopup="true" [attr.aria-expanded]="sectionSelectorExpanded" aria-controls="core-course-section-selector" id="core-course-section-button">
<core-icon name="fa-folder"></core-icon> <core-icon name="fa-folder"></core-icon>
<span class="core-section-selector-text">{{selectedSection && (selectedSection.formattedName || selectedSection.name) || 'core.course.sections' | translate }}</span> <span class="core-section-selector-text">{{selectedSection && (selectedSection.formattedName || selectedSection.name) || 'core.course.sections' | translate }}</span>
<ion-icon name="arrow-dropdown" ios="md-arrow-dropdown"></ion-icon> <ion-icon name="arrow-dropdown" ios="md-arrow-dropdown"></ion-icon>

View File

@ -61,6 +61,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
allSectionsComponent: any; allSectionsComponent: any;
canLoadMore = false; canLoadMore = false;
showSectionId = 0; showSectionId = 0;
sectionSelectorExpanded = false;
// Data to pass to the components. // Data to pass to the components.
data: any = {}; data: any = {};
@ -243,16 +244,27 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
/** /**
* Display the section selector modal. * Display the section selector modal.
*
* @param {MouseEvent} event Event.
*/ */
showSectionSelector(): void { showSectionSelector(event: MouseEvent): void {
const modal = this.modalCtrl.create('CoreCourseSectionSelectorPage', if (!this.sectionSelectorExpanded) {
{course: this.course, sections: this.sections, selected: this.selectedSection}); const modal = this.modalCtrl.create('CoreCourseSectionSelectorPage',
modal.onDidDismiss((newSection) => { {course: this.course, sections: this.sections, selected: this.selectedSection});
if (newSection) { modal.onDidDismiss((newSection) => {
this.sectionChanged(newSection); if (newSection) {
} this.sectionChanged(newSection);
}); }
modal.present();
this.sectionSelectorExpanded = false;
});
modal.present({
ev: event
});
this.sectionSelectorExpanded = true;
}
} }
/** /**

View File

@ -1,6 +1,6 @@
<a *ngIf="module && module.visibleoncoursepage !== 0" ion-item text-wrap id="core-course-module-{{module.id}}" class="core-course-module-handler {{module.handlerData.class}}" (click)="moduleClicked($event)" [ngClass]="{'item-media': module.handlerData.icon, 'core-not-clickable': !module.handlerData.action || !module.uservisible === false, 'item-dimmed': module.visible === 0 || module.uservisible === false}" [title]="module.handlerData.a11yTitle" detail-none> <a *ngIf="module && module.visibleoncoursepage !== 0" ion-item text-wrap id="core-course-module-{{module.id}}" class="core-course-module-handler {{module.handlerData.class}}" (click)="moduleClicked($event)" [ngClass]="{'item-media': module.handlerData.icon, 'core-not-clickable': !module.handlerData.action || !module.uservisible === false, 'item-dimmed': module.visible === 0 || module.uservisible === false}" [title]="module.handlerData.a11yTitle" detail-none>
<img item-start *ngIf="module.handlerData.icon" [src]="module.handlerData.icon" alt="" role="presentation" class="core-module-icon"> <img item-start *ngIf="module.handlerData.icon" [src]="module.handlerData.icon" [alt]="module.modnametranslated" class="core-module-icon">
<div class="core-module-title"> <div class="core-module-title">
<core-format-text [text]="module.handlerData.title"></core-format-text> <core-format-text [text]="module.handlerData.title"></core-format-text>

View File

@ -18,6 +18,7 @@ import { CoreEventsProvider } from '@providers/events';
import { CoreSitesProvider } from '@providers/sites'; import { CoreSitesProvider } from '@providers/sites';
import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreCourseHelperProvider } from '../../providers/helper'; import { CoreCourseHelperProvider } from '../../providers/helper';
import { CoreCourseProvider } from '../../providers/course';
import { CoreCourseModuleHandlerButton } from '../../providers/module-delegate'; import { CoreCourseModuleHandlerButton } from '../../providers/module-delegate';
import { CoreCourseModulePrefetchDelegate, CoreCourseModulePrefetchHandler } from '../../providers/module-prefetch-delegate'; import { CoreCourseModulePrefetchDelegate, CoreCourseModulePrefetchHandler } from '../../providers/module-prefetch-delegate';
import { CoreConstants } from '../../../constants'; import { CoreConstants } from '../../../constants';
@ -63,7 +64,8 @@ export class CoreCourseModuleComponent implements OnInit, OnDestroy {
constructor(@Optional() protected navCtrl: NavController, protected prefetchDelegate: CoreCourseModulePrefetchDelegate, constructor(@Optional() protected navCtrl: NavController, protected prefetchDelegate: CoreCourseModulePrefetchDelegate,
protected domUtils: CoreDomUtilsProvider, protected courseHelper: CoreCourseHelperProvider, protected domUtils: CoreDomUtilsProvider, protected courseHelper: CoreCourseHelperProvider,
protected eventsProvider: CoreEventsProvider, protected sitesProvider: CoreSitesProvider) { protected eventsProvider: CoreEventsProvider, protected sitesProvider: CoreSitesProvider,
protected courseProvider: CoreCourseProvider) {
this.completionChanged = new EventEmitter(); this.completionChanged = new EventEmitter();
} }
@ -97,6 +99,8 @@ export class CoreCourseModuleComponent implements OnInit, OnDestroy {
this.module.handlerData.a11yTitle = typeof this.module.handlerData.a11yTitle != 'undefined' ? this.module.handlerData.a11yTitle = typeof this.module.handlerData.a11yTitle != 'undefined' ?
this.module.handlerData.a11yTitle : this.module.handlerData.title; this.module.handlerData.a11yTitle : this.module.handlerData.title;
this.module.modnametranslated = this.courseProvider.translateModuleName(this.module.modname) || '';
} }
/** /**

View File

@ -9,13 +9,15 @@
</ion-navbar> </ion-navbar>
</ion-header> </ion-header>
<ion-content> <ion-content>
<ng-container *ngFor="let section of sections"> <ion-list id="core-course-section-selector" role="menu" aria-labelledby="core-course-section-button">
<a ion-item *ngIf="!section.hiddenbynumsections && section.id != stealthModulesSectionId" text-wrap (click)="selectSection(section)" [class.core-primary-selected-item]="selected.id == section.id" [class.item-dimmed]="section.visible === 0 || section.uservisible === false" detail-none> <ng-container *ngFor="let section of sections">
<core-icon name="fa-folder" item-start></core-icon> <a ion-item *ngIf="!section.hiddenbynumsections && section.id != stealthModulesSectionId" text-wrap (click)="selectSection(section)" [class.core-primary-selected-item]="selected.id == section.id" [class.item-dimmed]="section.visible === 0 || section.uservisible === false" detail-none role="menuitem" [attr.aria-hidden]="section.uservisible === false" [attr.aria-label]="section.formattedName || section.name">
<h2><core-format-text [text]="section.formattedName || section.name"></core-format-text></h2> <core-icon name="fa-folder" item-start></core-icon>
<core-progress-bar *ngIf="section.progress >= 0" [progress]="section.progress"></core-progress-bar> <h2><core-format-text [text]="section.formattedName || section.name"></core-format-text></h2>
<ion-badge color="secondary" *ngIf="section.visible === 0 && section.uservisible !== false">{{ 'core.course.hiddenfromstudents' | translate }}</ion-badge> <core-progress-bar *ngIf="section.progress >= 0" [progress]="section.progress"></core-progress-bar>
<ion-badge color="secondary" *ngIf="section.availabilityinfo"><core-format-text [text]=" section.availabilityinfo"></core-format-text></ion-badge> <ion-badge color="secondary" *ngIf="section.visible === 0 && section.uservisible !== false" text-wrap>{{ 'core.course.hiddenfromstudents' | translate }}</ion-badge>
</a> <ion-badge color="secondary" *ngIf="section.availabilityinfo" text-wrap><core-format-text [text]=" section.availabilityinfo"></core-format-text></ion-badge>
</ng-container> </a>
</ng-container>
</ion-list>
</ion-content> </ion-content>

View File

@ -8,4 +8,8 @@ ion-app.app-root page-core-course-section-selector {
margin: 8px 0 4px 0; margin: 8px 0 4px 0;
} }
} }
ion-badge {
text-align: left;
}
} }

View File

@ -30,19 +30,19 @@
</a> </a>
</div> </div>
<a *ngIf="showWeb" ion-item [href]="siteInfo.siteurl" core-link autoLogin="yes" title="{{ 'core.mainmenu.website' | translate }}"> <a *ngIf="showWeb" ion-item [href]="siteInfo.siteurl" core-link autoLogin="yes" title="{{ 'core.mainmenu.website' | translate }}">
<ion-icon name="globe" item-start></ion-icon> <ion-icon name="globe" item-start aria-hidden="true"></ion-icon>
<p>{{ 'core.mainmenu.website' | translate }}</p> <p>{{ 'core.mainmenu.website' | translate }}</p>
</a> </a>
<a *ngIf="showHelp" ion-item [href]="docsUrl" core-link autoLogin="no" title="{{ 'core.mainmenu.help' | translate }}"> <a *ngIf="showHelp" ion-item [href]="docsUrl" core-link autoLogin="no" title="{{ 'core.mainmenu.help' | translate }}">
<ion-icon name="help-buoy" item-start></ion-icon> <ion-icon name="help-buoy" item-start aria-hidden="true"></ion-icon>
<p>{{ 'core.mainmenu.help' | translate }}</p> <p>{{ 'core.mainmenu.help' | translate }}</p>
</a> </a>
<a ion-item (click)="openSettings()" title="{{ 'core.mainmenu.appsettings' | translate }}"> <a ion-item (click)="openSettings()" title="{{ 'core.mainmenu.appsettings' | translate }}">
<ion-icon name="cog" item-start></ion-icon> <ion-icon name="cog" item-start aria-hidden="true"></ion-icon>
<p>{{ 'core.mainmenu.appsettings' | translate }}</p> <p>{{ 'core.mainmenu.appsettings' | translate }}</p>
</a> </a>
<a ion-item (click)="logout()" title="{{ logoutLabel | translate }}"> <a ion-item (click)="logout()" title="{{ logoutLabel | translate }}">
<ion-icon name="log-out" item-start></ion-icon> <ion-icon name="log-out" item-start aria-hidden="true"></ion-icon>
<p>{{ logoutLabel | translate }}</p> <p>{{ logoutLabel | translate }}</p>
</a> </a>
</ion-list> </ion-list>