Skip to content

Commit 7fac689

Browse files
committed
MOBILE-4065 directive: Improve ariaButtonClick directive
1 parent 15cdc01 commit 7fac689

File tree

7 files changed

+72
-28
lines changed

7 files changed

+72
-28
lines changed

src/core/components/user-avatar/core-user-avatar.html

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1-
<img *ngIf="avatarUrl" [src]="avatarUrl" [alt]="'core.pictureof' | translate:{$a: fullname}" core-external-content
2-
onError="this.src='assets/img/user-avatar.png'" (ariaButtonClick)="gotoProfile($event)" [attr.aria-hidden]="!linkProfile"
3-
[attr.role]="linkProfile ? 'button' : null" [attr.tabindex]="linkProfile ? 0 : null" [class.clickable]="linkProfile">
1+
<img *ngIf="avatarUrl && linkProfile" [src]="avatarUrl" [alt]="'core.pictureof' | translate:{$a: fullname}" core-external-content
2+
onError="this.src='assets/img/user-avatar.png'" (ariaButtonClick)="gotoProfile($event)">
43

5-
<img *ngIf="!avatarUrl" src="assets/img/user-avatar.png" [alt]="'core.pictureof' | translate:{$a: fullname}"
6-
(ariaButtonClick)="gotoProfile($event)" [attr.aria-hidden]="!linkProfile" [attr.role]="linkProfile ? 'button' : null"
7-
[attr.tabindex]="linkProfile ? 0 : null">
4+
<img *ngIf="avatarUrl && !linkProfile" [src]="avatarUrl" [alt]="'core.pictureof' | translate:{$a: fullname}" core-external-content
5+
onError="this.src='assets/img/user-avatar.png'" aria-hidden="true">
6+
7+
<img *ngIf="!avatarUrl && linkProfile" src="assets/img/user-avatar.png" [alt]="'core.pictureof' | translate:{$a: fullname}"
8+
(ariaButtonClick)="gotoProfile($event)">
9+
10+
<img *ngIf="!avatarUrl && !linkProfile" src="assets/img/user-avatar.png" [alt]="'core.pictureof' | translate:{$a: fullname}"
11+
aria-hidden="true">
812

913
<span *ngIf="checkOnline && isOnline()" class="contact-status online" role="status" [attr.aria-label]="'core.online' | translate">
1014
</span>

src/core/components/user-avatar/user-avatar.scss

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,6 @@
55
width: var(--core-avatar-size);
66
height: var(--core-avatar-size);
77

8-
.clickable {
9-
cursor: pointer;
10-
}
118
img {
129
border-radius: 50%;
1310
width: var(--core-avatar-size);

src/core/directives/aria-button.ts

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
import { Directive, ElementRef, OnInit, Output, EventEmitter } from '@angular/core';
15+
import { Directive, ElementRef, OnInit, Output, EventEmitter, OnChanges, SimpleChanges, Input } from '@angular/core';
1616
import { CoreDom } from '@singletons/dom';
1717

1818
/**
@@ -21,10 +21,11 @@ import { CoreDom } from '@singletons/dom';
2121
@Directive({
2222
selector: '[ariaButtonClick]',
2323
})
24-
export class CoreAriaButtonClickDirective implements OnInit {
24+
export class CoreAriaButtonClickDirective implements OnInit, OnChanges {
2525

2626
protected element: HTMLElement;
2727

28+
@Input() disabled = false;
2829
@Output() ariaButtonClick = new EventEmitter();
2930

3031
constructor(
@@ -34,10 +35,27 @@ export class CoreAriaButtonClickDirective implements OnInit {
3435
}
3536

3637
/**
37-
* Initialize actions.
38+
* @inheritdoc
3839
*/
3940
ngOnInit(): void {
40-
CoreDom.onActivate(this.element, (event) => this.ariaButtonClick.emit(event));
41+
CoreDom.initializeClickableElementA11y(this.element, (event) => this.ariaButtonClick.emit(event));
42+
}
43+
44+
/**
45+
* @inheritdoc
46+
*/
47+
ngOnChanges(changes: SimpleChanges): void {
48+
if (!changes.disabled) {
49+
return;
50+
}
51+
52+
if (this.element.getAttribute('tabindex') === '0' && this.disabled) {
53+
this.element.setAttribute('tabindex', '-1');
54+
}
55+
56+
if (this.element.getAttribute('tabindex') === '-1' && !this.disabled) {
57+
this.element.setAttribute('tabindex', '0');
58+
}
4159
}
4260

4361
}

src/core/directives/format-text.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -610,12 +610,7 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncCompo
610610
return;
611611
}
612612

613-
if (element.tagName !== 'BUTTON' && element.tagName !== 'A') {
614-
element.setAttribute('tabindex', '0');
615-
element.setAttribute('role', 'button');
616-
}
617-
618-
CoreDom.onActivate(element, async (event) => {
613+
CoreDom.initializeClickableElementA11y(element, async (event) => {
619614
event.preventDefault();
620615
event.stopPropagation();
621616

src/core/directives/link.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,7 @@ export class CoreLinkDirective implements OnInit {
5757
* Function executed when the component is initialized.
5858
*/
5959
ngOnInit(): void {
60-
if (this.element.tagName != 'BUTTON' && this.element.tagName != 'A') {
61-
this.element.setAttribute('tabindex', '0');
62-
this.element.setAttribute('role', 'button');
63-
}
64-
65-
CoreDom.onActivate(this.element, (event) => this.performAction(event));
60+
CoreDom.initializeClickableElementA11y(this.element, (event) => this.performAction(event));
6661
}
6762

6863
/**
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
<core-user-avatar *ngIf="(alwaysShow || isMainScreen) && siteInfo" [user]="siteInfo" class="core-bar-button-image clickable"
2-
[linkProfile]="false" (ariaButtonClick)="openUserMenu($event)" [userTour]="userTour" role="button" tabindex="0"
2+
[linkProfile]="false" (ariaButtonClick)="openUserMenu($event)" [userTour]="userTour"
33
[attr.aria-label]="'core.user.useraccount' | translate">
44
</core-user-avatar>

src/core/singletons/dom.ts

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -514,8 +514,26 @@ export class CoreDom {
514514
*
515515
* @param element Element to listen to events.
516516
* @param callback Callback to call when clicked or the key is pressed.
517+
* @deprecated since 4.1.1: Use initializeClickableElementA11y instead.
517518
*/
518-
static onActivate(element: HTMLElement, callback: (event: MouseEvent | KeyboardEvent) => void): void {
519+
static onActivate(
520+
element: HTMLElement & {disabled?: boolean},
521+
callback: (event: MouseEvent | KeyboardEvent) => void,
522+
): void {
523+
this.initializeClickableElementA11y(element, callback);
524+
}
525+
526+
/**
527+
* Initializes a clickable element a11y calling the click action when pressed enter or space
528+
* and adding tabindex and role if needed.
529+
*
530+
* @param element Element to listen to events.
531+
* @param callback Callback to call when clicked or the key is pressed.
532+
*/
533+
static initializeClickableElementA11y(
534+
element: HTMLElement & {disabled?: boolean},
535+
callback: (event: MouseEvent | KeyboardEvent) => void,
536+
): void {
519537
element.addEventListener('click', (event) => callback(event));
520538

521539
element.addEventListener('keydown', (event) => {
@@ -526,10 +544,27 @@ export class CoreDom {
526544
});
527545

528546
element.addEventListener('keyup', (event) => {
529-
if ((event.key == ' ' || event.key == 'Enter')) {
547+
if (event.key === ' ' || event.key === 'Enter') {
548+
event.preventDefault();
549+
event.stopPropagation();
550+
530551
callback(event);
531552
}
532553
});
554+
555+
if (element.tagName !== 'BUTTON' && element.tagName !== 'A') {
556+
// Set tabindex if not previously set.
557+
if (element.getAttribute('tabindex') === null) {
558+
element.setAttribute('tabindex', element.disabled ? '-1' : '0');
559+
}
560+
561+
// Set role if not previously set.
562+
if (!element.getAttribute('role')) {
563+
element.setAttribute('role', 'button');
564+
}
565+
566+
element.classList.add('clickable');
567+
}
533568
}
534569

535570
}

0 commit comments

Comments
 (0)