MOBILE-2819 a11y: Apply aria attributes to components
parent
4c602c62bf
commit
b95de260ee
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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');
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
|
@ -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">
|
||||||
|
|
|
@ -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>
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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++) {
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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) || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in New Issue