Merge branch 'w2p-117544_support-for-disabled-elements-for-screen-readers-7.6' into w2p-117544_support-for-disabled-elements-for-screen-readers-8.0

This commit is contained in:
Jens Vannerum
2024-08-26 13:38:55 +02:00
61 changed files with 328 additions and 122 deletions

View File

@@ -10,7 +10,7 @@
<button class="btn btn-outline-primary mr-3" (click)="reset()"> <button class="btn btn-outline-primary mr-3" (click)="reset()">
{{ 'access-control-cancel' | translate }} {{ 'access-control-cancel' | translate }}
</button> </button>
<button class="btn btn-primary" [disabled]="!canExport()" (click)="submit()"> <button class="btn btn-primary" [dsDisabled]="!canExport()" (click)="submit()">
{{ 'access-control-execute' | translate }} {{ 'access-control-execute' | translate }}
</button> </button>
</div> </div>

View File

@@ -57,6 +57,7 @@ import { NotificationsServiceStub } from '../../shared/testing/notifications-ser
import { PaginationServiceStub } from '../../shared/testing/pagination-service.stub'; import { PaginationServiceStub } from '../../shared/testing/pagination-service.stub';
import { EPeopleRegistryComponent } from './epeople-registry.component'; import { EPeopleRegistryComponent } from './epeople-registry.component';
import { EPersonFormComponent } from './eperson-form/eperson-form.component'; import { EPersonFormComponent } from './eperson-form/eperson-form.component';
import {DisabledDirective} from '../../shared/disabled-directive';
describe('EPeopleRegistryComponent', () => { describe('EPeopleRegistryComponent', () => {
let component: EPeopleRegistryComponent; let component: EPeopleRegistryComponent;
@@ -151,7 +152,7 @@ describe('EPeopleRegistryComponent', () => {
paginationService = new PaginationServiceStub(); paginationService = new PaginationServiceStub();
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [CommonModule, NgbModule, FormsModule, ReactiveFormsModule, BrowserModule, RouterTestingModule.withRoutes([]), imports: [CommonModule, NgbModule, FormsModule, ReactiveFormsModule, BrowserModule, RouterTestingModule.withRoutes([]),
TranslateModule.forRoot(), EPeopleRegistryComponent], TranslateModule.forRoot(), EPeopleRegistryComponent, DisabledDirective],
providers: [ providers: [
{ provide: EPersonDataService, useValue: ePersonDataServiceStub }, { provide: EPersonDataService, useValue: ePersonDataServiceStub },
{ provide: NotificationsService, useValue: new NotificationsServiceStub() }, { provide: NotificationsService, useValue: new NotificationsServiceStub() },

View File

@@ -68,6 +68,7 @@ import { HasNoValuePipe } from '../../../shared/utils/has-no-value.pipe';
import { EPeopleRegistryComponent } from '../epeople-registry.component'; import { EPeopleRegistryComponent } from '../epeople-registry.component';
import { EPersonFormComponent } from './eperson-form.component'; import { EPersonFormComponent } from './eperson-form.component';
import { ValidateEmailNotTaken } from './validators/email-taken.validator'; import { ValidateEmailNotTaken } from './validators/email-taken.validator';
import {DisabledDirective} from '../../../shared/disabled-directive';
describe('EPersonFormComponent', () => { describe('EPersonFormComponent', () => {
let component: EPersonFormComponent; let component: EPersonFormComponent;
@@ -227,7 +228,7 @@ describe('EPersonFormComponent', () => {
route = new ActivatedRouteStub(); route = new ActivatedRouteStub();
router = new RouterStub(); router = new RouterStub();
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [CommonModule, NgbModule, FormsModule, ReactiveFormsModule, BrowserModule, imports: [CommonModule, NgbModule, FormsModule, ReactiveFormsModule, DisabledDirective, BrowserModule,
TranslateModule.forRoot({ TranslateModule.forRoot({
loader: { loader: {
provide: TranslateLoader, provide: TranslateLoader,
@@ -546,7 +547,8 @@ describe('EPersonFormComponent', () => {
// ePersonDataServiceStub.activeEPerson = eperson; // ePersonDataServiceStub.activeEPerson = eperson;
spyOn(component.epersonService, 'deleteEPerson').and.returnValue(createSuccessfulRemoteDataObject$('No Content', 204)); spyOn(component.epersonService, 'deleteEPerson').and.returnValue(createSuccessfulRemoteDataObject$('No Content', 204));
const deleteButton = fixture.debugElement.query(By.css('.delete-button')); const deleteButton = fixture.debugElement.query(By.css('.delete-button'));
expect(deleteButton.nativeElement.disabled).toBe(false); expect(deleteButton.nativeElement.getAttribute('aria-disabled')).toBe('false');
expect(deleteButton.nativeElement.classList.contains('disabled')).toBeFalse();
deleteButton.triggerEventHandler('click', null); deleteButton.triggerEventHandler('click', null);
fixture.detectChanges(); fixture.detectChanges();
expect(component.epersonService.deleteEPerson).toHaveBeenCalledWith(eperson); expect(component.epersonService.deleteEPerson).toHaveBeenCalledWith(eperson);

View File

@@ -69,7 +69,7 @@
<i class="fas fa-edit fa-fw"></i> <i class="fas fa-edit fa-fw"></i>
</button> </button>
<button *ngSwitchCase="false" <button *ngSwitchCase="false"
[disabled]="true" [dsDisabled]="true"
class="btn btn-outline-primary btn-sm btn-edit" class="btn btn-outline-primary btn-sm btn-edit"
placement="left" placement="left"
[ngbTooltip]="'admin.access-control.epeople.table.edit.buttons.edit-disabled' | translate" [ngbTooltip]="'admin.access-control.epeople.table.edit.buttons.edit-disabled' | translate"

View File

@@ -71,6 +71,7 @@ import { NotificationsServiceStub } from '../../shared/testing/notifications-ser
import { PaginationServiceStub } from '../../shared/testing/pagination-service.stub'; import { PaginationServiceStub } from '../../shared/testing/pagination-service.stub';
import { routeServiceStub } from '../../shared/testing/route-service.stub'; import { routeServiceStub } from '../../shared/testing/route-service.stub';
import { GroupsRegistryComponent } from './groups-registry.component'; import { GroupsRegistryComponent } from './groups-registry.component';
import {DisabledDirective} from '../../shared/disabled-directive';
describe('GroupsRegistryComponent', () => { describe('GroupsRegistryComponent', () => {
let component: GroupsRegistryComponent; let component: GroupsRegistryComponent;
@@ -208,6 +209,7 @@ describe('GroupsRegistryComponent', () => {
imports: [CommonModule, NgbModule, FormsModule, ReactiveFormsModule, BrowserModule, imports: [CommonModule, NgbModule, FormsModule, ReactiveFormsModule, BrowserModule,
TranslateModule.forRoot(), TranslateModule.forRoot(),
GroupsRegistryComponent, GroupsRegistryComponent,
DisabledDirective
], ],
providers: [GroupsRegistryComponent, providers: [GroupsRegistryComponent,
{ provide: DSONameService, useValue: new DSONameServiceMock() }, { provide: DSONameService, useValue: new DSONameServiceMock() },
@@ -278,7 +280,8 @@ describe('GroupsRegistryComponent', () => {
const editButtonsFound = fixture.debugElement.queryAll(By.css('#groups tr td:nth-child(5) button.btn-edit')); const editButtonsFound = fixture.debugElement.queryAll(By.css('#groups tr td:nth-child(5) button.btn-edit'));
expect(editButtonsFound.length).toEqual(2); expect(editButtonsFound.length).toEqual(2);
editButtonsFound.forEach((editButtonFound) => { editButtonsFound.forEach((editButtonFound) => {
expect(editButtonFound.nativeElement.disabled).toBeFalse(); expect(editButtonFound.nativeElement.getAttribute('aria-disabled')).toBeNull();
expect(editButtonFound.nativeElement.classList.contains('disabled')).toBeFalse();
}); });
}); });
@@ -312,7 +315,8 @@ describe('GroupsRegistryComponent', () => {
const editButtonsFound = fixture.debugElement.queryAll(By.css('#groups tr td:nth-child(5) button.btn-edit')); const editButtonsFound = fixture.debugElement.queryAll(By.css('#groups tr td:nth-child(5) button.btn-edit'));
expect(editButtonsFound.length).toEqual(2); expect(editButtonsFound.length).toEqual(2);
editButtonsFound.forEach((editButtonFound) => { editButtonsFound.forEach((editButtonFound) => {
expect(editButtonFound.nativeElement.disabled).toBeFalse(); expect(editButtonFound.nativeElement.getAttribute('aria-disabled')).toBeNull();
expect(editButtonFound.nativeElement.classList.contains('disabled')).toBeFalse();
}); });
}); });
}); });
@@ -331,7 +335,8 @@ describe('GroupsRegistryComponent', () => {
const editButtonsFound = fixture.debugElement.queryAll(By.css('#groups tr td:nth-child(5) button.btn-edit')); const editButtonsFound = fixture.debugElement.queryAll(By.css('#groups tr td:nth-child(5) button.btn-edit'));
expect(editButtonsFound.length).toEqual(2); expect(editButtonsFound.length).toEqual(2);
editButtonsFound.forEach((editButtonFound) => { editButtonsFound.forEach((editButtonFound) => {
expect(editButtonFound.nativeElement.disabled).toBeTrue(); expect(editButtonFound.nativeElement.getAttribute('aria-disabled')).toBe('true');
expect(editButtonFound.nativeElement.classList.contains('disabled')).toBeTrue();
}); });
}); });
}); });

View File

@@ -6,10 +6,10 @@
<p class="pb-2">{{ 'collection.delete.text' | translate:{ dso: dsoNameService.getName(dso) } }}</p> <p class="pb-2">{{ 'collection.delete.text' | translate:{ dso: dsoNameService.getName(dso) } }}</p>
<div class="form-group row"> <div class="form-group row">
<div class="col text-right space-children-mr"> <div class="col text-right space-children-mr">
<button class="btn btn-outline-secondary" (click)="onCancel(dso)" [disabled]="(processing$ | async)"> <button class="btn btn-outline-secondary" (click)="onCancel(dso)" [dsDisabled]="(processing$ | async)">
<i class="fas fa-times"></i> {{'collection.delete.cancel' | translate}} <i class="fas fa-times"></i> {{'collection.delete.cancel' | translate}}
</button> </button>
<button class="btn btn-danger" (click)="onConfirm(dso)" [disabled]="(processing$ | async)"> <button class="btn btn-danger" (click)="onConfirm(dso)" [dsDisabled]="(processing$ | async)">
<span *ngIf="processing$ | async"><i class='fas fa-circle-notch fa-spin'></i> {{'collection.delete.processing' | translate}}</span> <span *ngIf="processing$ | async"><i class='fas fa-circle-notch fa-spin'></i> {{'collection.delete.processing' | translate}}</span>
<span *ngIf="(processing$ | async) !== true"><i class="fas fa-trash"></i> {{'collection.delete.confirm' | translate}}</span> <span *ngIf="(processing$ | async) !== true"><i class="fas fa-trash"></i> {{'collection.delete.confirm' | translate}}</span>
</button> </button>

View File

@@ -27,6 +27,7 @@ import { createSuccessfulRemoteDataObject$ } from '../../../../shared/remote-dat
import { NotificationsServiceStub } from '../../../../shared/testing/notifications-service.stub'; import { NotificationsServiceStub } from '../../../../shared/testing/notifications-service.stub';
import { VarDirective } from '../../../../shared/utils/var.directive'; import { VarDirective } from '../../../../shared/utils/var.directive';
import { CollectionSourceControlsComponent } from './collection-source-controls.component'; import { CollectionSourceControlsComponent } from './collection-source-controls.component';
import {DisabledDirective} from '../../../../shared/disabled-directive';
describe('CollectionSourceControlsComponent', () => { describe('CollectionSourceControlsComponent', () => {
let comp: CollectionSourceControlsComponent; let comp: CollectionSourceControlsComponent;
@@ -104,7 +105,7 @@ describe('CollectionSourceControlsComponent', () => {
requestService = jasmine.createSpyObj('requestService', ['removeByHrefSubstring', 'setStaleByHrefSubstring']); requestService = jasmine.createSpyObj('requestService', ['removeByHrefSubstring', 'setStaleByHrefSubstring']);
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [TranslateModule.forRoot(), RouterTestingModule, CollectionSourceControlsComponent, VarDirective], imports: [TranslateModule.forRoot(), RouterTestingModule, CollectionSourceControlsComponent, VarDirective, DisabledDirective],
providers: [ providers: [
{ provide: ScriptDataService, useValue: scriptDataService }, { provide: ScriptDataService, useValue: scriptDataService },
{ provide: ProcessDataService, useValue: processDataService }, { provide: ProcessDataService, useValue: processDataService },
@@ -193,9 +194,11 @@ describe('CollectionSourceControlsComponent', () => {
const buttons = fixture.debugElement.queryAll(By.css('button')); const buttons = fixture.debugElement.queryAll(By.css('button'));
expect(buttons[0].nativeElement.disabled).toBeTrue(); buttons.forEach(button => {
expect(buttons[1].nativeElement.disabled).toBeTrue(); console.log(button.nativeElement);
expect(buttons[2].nativeElement.disabled).toBeTrue(); expect(button.nativeElement.getAttribute('aria-disabled')).toBe('true');
expect(button.nativeElement.classList.contains('disabled')).toBeTrue();
});
}); });
it('should be enabled when isEnabled is true', () => { it('should be enabled when isEnabled is true', () => {
comp.shouldShow = true; comp.shouldShow = true;
@@ -205,9 +208,10 @@ describe('CollectionSourceControlsComponent', () => {
const buttons = fixture.debugElement.queryAll(By.css('button')); const buttons = fixture.debugElement.queryAll(By.css('button'));
expect(buttons[0].nativeElement.disabled).toBeFalse(); buttons.forEach(button => {
expect(buttons[1].nativeElement.disabled).toBeFalse(); expect(button.nativeElement.getAttribute('aria-disabled')).toBe('false');
expect(buttons[2].nativeElement.disabled).toBeFalse(); expect(button.nativeElement.classList.contains('disabled')).toBeFalse();
});
}); });
it('should call the corresponding button when clicked', () => { it('should call the corresponding button when clicked', () => {
spyOn(comp, 'testConfiguration'); spyOn(comp, 'testConfiguration');

View File

@@ -44,6 +44,7 @@ import {
DsoEditMetadataValue, DsoEditMetadataValue,
} from '../dso-edit-metadata-form'; } from '../dso-edit-metadata-form';
import { DsoEditMetadataValueComponent } from './dso-edit-metadata-value.component'; import { DsoEditMetadataValueComponent } from './dso-edit-metadata-value.component';
import {DisabledDirective} from '../../../shared/disabled-directive';
const EDIT_BTN = 'edit'; const EDIT_BTN = 'edit';
const CONFIRM_BTN = 'confirm'; const CONFIRM_BTN = 'confirm';
@@ -188,6 +189,7 @@ describe('DsoEditMetadataValueComponent', () => {
RouterTestingModule.withRoutes([]), RouterTestingModule.withRoutes([]),
DsoEditMetadataValueComponent, DsoEditMetadataValueComponent,
VarDirective, VarDirective,
DisabledDirective
], ],
providers: [ providers: [
{ provide: RelationshipDataService, useValue: relationshipService }, { provide: RelationshipDataService, useValue: relationshipService },
@@ -524,7 +526,14 @@ describe('DsoEditMetadataValueComponent', () => {
}); });
it(`should${disabled ? ' ' : ' not '}be disabled`, () => { it(`should${disabled ? ' ' : ' not '}be disabled`, () => {
expect(btn.nativeElement.disabled).toBe(disabled); if (disabled) {
expect(btn.nativeElement.getAttribute('aria-disabled')).toBe('true');
expect(btn.nativeElement.classList.contains('disabled')).toBeTrue();
} else {
// Can be null or false, depending on if button was ever disabled so just check not true
expect(btn.nativeElement.getAttribute('aria-disabled')).not.toBe('true');
expect(btn.nativeElement.classList.contains('disabled')).toBeFalse();
}
}); });
} else { } else {
it('should not exist', () => { it('should not exist', () => {

View File

@@ -32,6 +32,7 @@ import { DsoEditMetadataHeadersComponent } from './dso-edit-metadata-headers/dso
import { DsoEditMetadataValueComponent } from './dso-edit-metadata-value/dso-edit-metadata-value.component'; import { DsoEditMetadataValueComponent } from './dso-edit-metadata-value/dso-edit-metadata-value.component';
import { DsoEditMetadataValueHeadersComponent } from './dso-edit-metadata-value-headers/dso-edit-metadata-value-headers.component'; import { DsoEditMetadataValueHeadersComponent } from './dso-edit-metadata-value-headers/dso-edit-metadata-value-headers.component';
import { MetadataFieldSelectorComponent } from './metadata-field-selector/metadata-field-selector.component'; import { MetadataFieldSelectorComponent } from './metadata-field-selector/metadata-field-selector.component';
import {DisabledDirective} from '../../shared/disabled-directive';
const ADD_BTN = 'add'; const ADD_BTN = 'add';
const REINSTATE_BTN = 'reinstate'; const REINSTATE_BTN = 'reinstate';
@@ -87,6 +88,7 @@ describe('DsoEditMetadataComponent', () => {
]); ]);
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [DisabledDirective]
imports: [ imports: [
CommonModule, CommonModule,
BrowserModule, BrowserModule,
@@ -216,7 +218,13 @@ describe('DsoEditMetadataComponent', () => {
}); });
it(`should${disabled ? ' ' : ' not '}be disabled`, () => { it(`should${disabled ? ' ' : ' not '}be disabled`, () => {
expect(btn.nativeElement.disabled).toBe(disabled); if (disabled) {
expect(btn.nativeElement.getAttribute('aria-disabled')).toBe('true');
expect(btn.nativeElement.classList.contains('disabled')).toBeTrue();
} else {
expect(btn.nativeElement.getAttribute('aria-disabled')).not.toBe('true');
expect(btn.nativeElement.classList.contains('disabled')).toBeFalse();
}
}); });
} else { } else {
it('should not exist', () => { it('should not exist', () => {

View File

@@ -28,7 +28,7 @@
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
<button <button
[disabled]="isInValid" [dsDisabled]="isInValid"
class="btn btn-default btn-primary" class="btn btn-default btn-primary"
(click)="submit()">{{'forgot-password.form.submit' | translate}}</button> (click)="submit()">{{'forgot-password.form.submit' | translate}}</button>
</div> </div>

View File

@@ -7,7 +7,7 @@
<div class="d-flex mt-4"> <div class="d-flex mt-4">
<button id="button-cancel" type="button" (click)="cancel()" class="btn btn-outline-secondary mr-auto">{{ 'info.end-user-agreement.buttons.cancel' | translate }}</button> <button id="button-cancel" type="button" (click)="cancel()" class="btn btn-outline-secondary mr-auto">{{ 'info.end-user-agreement.buttons.cancel' | translate }}</button>
<button id="button-save" type="submit" class="btn btn-primary" [disabled]="!accepted">{{ 'info.end-user-agreement.buttons.save' | translate }}</button> <button id="button-save" type="submit" class="btn btn-primary" [dsDisabled]="!accepted">{{ 'info.end-user-agreement.buttons.save' | translate }}</button>
</div> </div>
</form> </form>
</div> </div>

View File

@@ -20,6 +20,7 @@ import { NotificationsService } from '../../shared/notifications/notifications.s
import { ActivatedRouteStub } from '../../shared/testing/active-router.stub'; import { ActivatedRouteStub } from '../../shared/testing/active-router.stub';
import { EndUserAgreementComponent } from './end-user-agreement.component'; import { EndUserAgreementComponent } from './end-user-agreement.component';
import { EndUserAgreementContentComponent } from './end-user-agreement-content/end-user-agreement-content.component'; import { EndUserAgreementContentComponent } from './end-user-agreement-content/end-user-agreement-content.component';
import {DisabledDirective} from '../../shared/disabled-directive';
describe('EndUserAgreementComponent', () => { describe('EndUserAgreementComponent', () => {
let component: EndUserAgreementComponent; let component: EndUserAgreementComponent;
@@ -57,7 +58,7 @@ describe('EndUserAgreementComponent', () => {
beforeEach(waitForAsync(() => { beforeEach(waitForAsync(() => {
init(); init();
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [TranslateModule.forRoot(), EndUserAgreementComponent], imports: [TranslateModule.forRoot(), EndUserAgreementComponent, DisabledDirective],
providers: [ providers: [
{ provide: EndUserAgreementService, useValue: endUserAgreementService }, { provide: EndUserAgreementService, useValue: endUserAgreementService },
{ provide: NotificationsService, useValue: notificationsService }, { provide: NotificationsService, useValue: notificationsService },
@@ -95,7 +96,8 @@ describe('EndUserAgreementComponent', () => {
it('should disable the save button', () => { it('should disable the save button', () => {
const button = fixture.debugElement.query(By.css('#button-save')).nativeElement; const button = fixture.debugElement.query(By.css('#button-save')).nativeElement;
expect(button.disabled).toBeTruthy(); expect(button.getAttribute('aria-disabled')).toBe('true');
expect(button.classList.contains('disabled')).toBeTrue();
}); });
}); });

View File

@@ -26,6 +26,7 @@ import { EPersonMock } from '../../../shared/testing/eperson.mock';
import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub'; import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub';
import { routeServiceStub } from '../../../shared/testing/route-service.stub'; import { routeServiceStub } from '../../../shared/testing/route-service.stub';
import { FeedbackFormComponent } from './feedback-form.component'; import { FeedbackFormComponent } from './feedback-form.component';
import {DisabledDirective} from '../../../shared/disabled-directive';
describe('FeedbackFormComponent', () => { describe('FeedbackFormComponent', () => {
@@ -45,7 +46,7 @@ describe('FeedbackFormComponent', () => {
beforeEach(waitForAsync(() => { beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [TranslateModule.forRoot(), FeedbackFormComponent], imports: [TranslateModule.forRoot(), FeedbackFormComponent, DisabledDirective],
providers: [ providers: [
{ provide: RouteService, useValue: routeServiceStub }, { provide: RouteService, useValue: routeServiceStub },
{ provide: UntypedFormBuilder, useValue: new UntypedFormBuilder() }, { provide: UntypedFormBuilder, useValue: new UntypedFormBuilder() },
@@ -79,7 +80,8 @@ describe('FeedbackFormComponent', () => {
}); });
it('should have disabled button', () => { it('should have disabled button', () => {
expect(de.query(By.css('button')).nativeElement.disabled).toBeTrue(); expect(de.query(By.css('button')).nativeElement.getAttribute('aria-disabled')).toBe('true');
expect(de.query(By.css('button')).nativeElement.classList.contains('disabled')).toBeTrue();
}); });
describe('when message is inserted', () => { describe('when message is inserted', () => {
@@ -90,7 +92,8 @@ describe('FeedbackFormComponent', () => {
}); });
it('should not have disabled button', () => { it('should not have disabled button', () => {
expect(de.query(By.css('button')).nativeElement.disabled).toBeFalse(); expect(de.query(By.css('button')).nativeElement.getAttribute('aria-disabled')).toBe('false');
expect(de.query(By.css('button')).nativeElement.classList.contains('disabled')).toBeFalse();
}); });
it('on submit should call createFeedback of feedbackDataServiceStub service', () => { it('on submit should call createFeedback of feedbackDataServiceStub service', () => {

View File

@@ -79,7 +79,7 @@
</a> </a>
<button <button
[disabled]="requestCopyForm.invalid" [dsDisabled]="requestCopyForm.invalid"
class="btn btn-default btn-primary" class="btn btn-default btn-primary"
(click)="onSubmit()">{{'bitstream-request-a-copy.submit' | translate}}</button> (click)="onSubmit()">{{'bitstream-request-a-copy.submit' | translate}}</button>
</div> </div>

View File

@@ -40,7 +40,7 @@
<button [routerLink]="[(itemPageRoute$ | async), 'edit']" class="btn btn-outline-secondary"> <button [routerLink]="[(itemPageRoute$ | async), 'edit']" class="btn btn-outline-secondary">
<i class="fas fa-arrow-left"></i> {{'item.edit.move.cancel' | translate}} <i class="fas fa-arrow-left"></i> {{'item.edit.move.cancel' | translate}}
</button> </button>
<button class="btn btn-primary" [disabled]="!canMove" (click)="moveToCollection()"> <button class="btn btn-primary" [dsDisabled]="!canMove" (click)="moveToCollection()">
<span *ngIf="!processing"> <span *ngIf="!processing">
<i class="fas fa-save"></i> {{'item.edit.move.save-button' | translate}} <i class="fas fa-save"></i> {{'item.edit.move.save-button' | translate}}
</span> </span>
@@ -48,7 +48,7 @@
<i class="fas fa-circle-notch fa-spin"></i> {{'item.edit.move.processing' | translate}} <i class="fas fa-circle-notch fa-spin"></i> {{'item.edit.move.processing' | translate}}
</span> </span>
</button> </button>
<button class="btn btn-danger" [disabled]="!canSubmit" (click)="discard()"> <button class="btn btn-danger" [dsDisabled]="!canSubmit" (click)="discard()">
<i class="fas fa-times"></i> {{"item.edit.move.discard-button" | translate}} <i class="fas fa-times"></i> {{"item.edit.move.discard-button" | translate}}
</button> </button>
</div> </div>

View File

@@ -5,12 +5,12 @@
</div> </div>
<div class="col-9 float-left action-button"> <div class="col-9 float-left action-button">
<span *ngIf="operation.authorized"> <span *ngIf="operation.authorized">
<button class="btn btn-outline-primary" [disabled]="operation.disabled" [routerLink]="operation.operationUrl" [attr.aria-label]="'item.edit.tabs.status.buttons.' + operation.operationKey + '.button' | translate"> <button class="btn btn-outline-primary" [dsDisabled]="operation.disabled" [routerLink]="operation.operationUrl" [attr.aria-label]="'item.edit.tabs.status.buttons.' + operation.operationKey + '.button' | translate">
{{'item.edit.tabs.status.buttons.' + operation.operationKey + '.button' | translate}} {{'item.edit.tabs.status.buttons.' + operation.operationKey + '.button' | translate}}
</button> </button>
</span> </span>
<span *ngIf="!operation.authorized" [ngbTooltip]="'item.edit.tabs.status.buttons.unauthorized' | translate"> <span *ngIf="!operation.authorized" [ngbTooltip]="'item.edit.tabs.status.buttons.unauthorized' | translate">
<button class="btn btn-outline-primary" [disabled]="true" [attr.aria-label]="'item.edit.tabs.status.buttons.' + operation.operationKey + '.button' | translate"> <button class="btn btn-outline-primary" [dsDisabled]="true" [attr.aria-label]="'item.edit.tabs.status.buttons.' + operation.operationKey + '.button' | translate">
{{'item.edit.tabs.status.buttons.' + operation.operationKey + '.button' | translate}} {{'item.edit.tabs.status.buttons.' + operation.operationKey + '.button' | translate}}
</button> </button>
</span> </span>

View File

@@ -8,6 +8,7 @@ import { TranslateModule } from '@ngx-translate/core';
import { ItemOperationComponent } from './item-operation.component'; import { ItemOperationComponent } from './item-operation.component';
import { ItemOperation } from './itemOperation.model'; import { ItemOperation } from './itemOperation.model';
import {DisabledDirective} from '../../../shared/disabled-directive';
describe('ItemOperationComponent', () => { describe('ItemOperationComponent', () => {
let itemOperation: ItemOperation; let itemOperation: ItemOperation;
@@ -17,7 +18,7 @@ describe('ItemOperationComponent', () => {
beforeEach(waitForAsync(() => { beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([]), ItemOperationComponent], imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([]), ItemOperationComponent, DisabledDirective],
}).compileComponents(); }).compileComponents();
})); }));
@@ -43,7 +44,8 @@ describe('ItemOperationComponent', () => {
const span = fixture.debugElement.query(By.css('.action-label span')).nativeElement; const span = fixture.debugElement.query(By.css('.action-label span')).nativeElement;
expect(span.textContent).toContain('item.edit.tabs.status.buttons.key1.label'); expect(span.textContent).toContain('item.edit.tabs.status.buttons.key1.label');
const button = fixture.debugElement.query(By.css('button')).nativeElement; const button = fixture.debugElement.query(By.css('button')).nativeElement;
expect(button.disabled).toBeTrue(); expect(button.getAttribute('aria-disabled')).toBe('true');
expect(button.classList.contains('disabled')).toBeTrue();
expect(button.textContent).toContain('item.edit.tabs.status.buttons.key1.button'); expect(button.textContent).toContain('item.edit.tabs.status.buttons.key1.button');
}); });
}); });

View File

@@ -386,7 +386,8 @@ describe('EditRelationshipListComponent', () => {
comp.hasChanges = observableOf(true); comp.hasChanges = observableOf(true);
fixture.detectChanges(); fixture.detectChanges();
const element = de.query(By.css('.btn-success')); const element = de.query(By.css('.btn-success'));
expect(element.nativeElement?.disabled).toBeTrue(); expect(element.nativeElement?.getAttribute('aria-disabled')).toBe('true');
expect(element.nativeElement?.classList.contains('disabled')).toBeTrue();
}); });
}); });

View File

@@ -9,12 +9,12 @@
</div> </div>
<div class="col-2"> <div class="col-2">
<div class="btn-group relationship-action-buttons"> <div class="btn-group relationship-action-buttons">
<button [disabled]="!canRemove()" (click)="openVirtualMetadataModal(virtualMetadataModal)" <button [dsDisabled]="!canRemove()" (click)="openVirtualMetadataModal(virtualMetadataModal)"
class="btn btn-outline-danger btn-sm" class="btn btn-outline-danger btn-sm"
title="{{'item.edit.metadata.edit.buttons.remove' | translate}}"> title="{{'item.edit.metadata.edit.buttons.remove' | translate}}">
<i class="fas fa-trash-alt fa-fw"></i> <i class="fas fa-trash-alt fa-fw"></i>
</button> </button>
<button [disabled]="!canUndo()" (click)="undo()" <button [dsDisabled]="!canUndo()" (click)="undo()"
class="btn btn-outline-warning btn-sm" class="btn btn-outline-warning btn-sm"
title="{{'item.edit.metadata.edit.buttons.undo' | translate}}"> title="{{'item.edit.metadata.edit.buttons.undo' | translate}}">
<i class="fas fa-undo-alt fa-fw"></i> <i class="fas fa-undo-alt fa-fw"></i>

View File

@@ -19,7 +19,7 @@
<div class="buttons" *ngIf="medias?.length > 1"> <div class="buttons" *ngIf="medias?.length > 1">
<button <button
class="btn btn-primary previous" class="btn btn-primary previous"
[disabled]="currentIndex === 0" [dsDisabled]="currentIndex === 0"
(click)="prevMedia()" (click)="prevMedia()"
> >
{{ "media-viewer.previous" | translate }} {{ "media-viewer.previous" | translate }}
@@ -27,7 +27,7 @@
<button <button
class="btn btn-primary next" class="btn btn-primary next"
[disabled]="currentIndex === medias.length - 1" [dsDisabled]="currentIndex === medias.length - 1"
(click)="nextMedia()" (click)="nextMedia()"
> >
{{ "media-viewer.next" | translate }} {{ "media-viewer.next" | translate }}

View File

@@ -51,6 +51,7 @@ import { PaginationServiceStub } from '../../shared/testing/pagination-service.s
import { createPaginatedList } from '../../shared/testing/utils.test'; import { createPaginatedList } from '../../shared/testing/utils.test';
import { VarDirective } from '../../shared/utils/var.directive'; import { VarDirective } from '../../shared/utils/var.directive';
import { ItemVersionsComponent } from './item-versions.component'; import { ItemVersionsComponent } from './item-versions.component';
import {DisabledDirective} from '../../shared/disabled-directive';
describe('ItemVersionsComponent', () => { describe('ItemVersionsComponent', () => {
let component: ItemVersionsComponent; let component: ItemVersionsComponent;
@@ -158,7 +159,7 @@ describe('ItemVersionsComponent', () => {
beforeEach(waitForAsync(() => { beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [TranslateModule.forRoot(), RouterModule.forRoot([]), CommonModule, FormsModule, ReactiveFormsModule, BrowserModule, ItemVersionsComponent, VarDirective], imports: [TranslateModule.forRoot(), RouterModule.forRoot([]), CommonModule, FormsModule, ReactiveFormsModule, BrowserModule, ItemVersionsComponent, VarDirective, DisabledDirective],
providers: [ providers: [
{ provide: PaginationService, useValue: new PaginationServiceStub() }, { provide: PaginationService, useValue: new PaginationServiceStub() },
{ provide: UntypedFormBuilder, useValue: new UntypedFormBuilder() }, { provide: UntypedFormBuilder, useValue: new UntypedFormBuilder() },
@@ -234,8 +235,9 @@ describe('ItemVersionsComponent', () => {
it('should not disable the delete button', () => { it('should not disable the delete button', () => {
const deleteButtons: DebugElement[] = fixture.debugElement.queryAll(By.css('.version-row-element-delete')); const deleteButtons: DebugElement[] = fixture.debugElement.queryAll(By.css('.version-row-element-delete'));
expect(deleteButtons.length).not.toBe(0); expect(deleteButtons.length).not.toBe(0);
deleteButtons.forEach((btn: DebugElement) => { deleteButtons.forEach((btn) => {
expect(btn.nativeElement.disabled).toBe(false); expect(btn.nativeElement.getAttribute('aria-disabled')).toBe('false');
expect(btn.nativeElement.classList.contains('disabled')).toBeFalse();
}); });
}); });

View File

@@ -64,10 +64,10 @@
<span> {{ 'process.overview.delete.processing' | translate: {count: processBulkDeleteService.getAmountOfSelectedProcesses()} }}</span> <span> {{ 'process.overview.delete.processing' | translate: {count: processBulkDeleteService.getAmountOfSelectedProcesses()} }}</span>
</div> </div>
<div class="mt-4"> <div class="mt-4">
<button class="btn btn-primary mr-2" [disabled]="processBulkDeleteService.isProcessing$() |async" <button class="btn btn-primary mr-2" [dsDisabled]="processBulkDeleteService.isProcessing$() |async"
(click)="closeModal()">{{'process.detail.delete.cancel' | translate}}</button> (click)="closeModal()">{{'process.detail.delete.cancel' | translate}}</button>
<button id="delete-confirm" class="btn btn-danger" <button id="delete-confirm" class="btn btn-danger"
[disabled]="processBulkDeleteService.isProcessing$() |async" [dsDisabled]="processBulkDeleteService.isProcessing$() |async"
(click)="deleteSelected()">{{ 'process.overview.delete' | translate: {count: processBulkDeleteService.getAmountOfSelectedProcesses()} }} (click)="deleteSelected()">{{ 'process.overview.delete' | translate: {count: processBulkDeleteService.getAmountOfSelectedProcesses()} }}
</button> </button>
</div> </div>

View File

@@ -29,7 +29,7 @@
<input type="checkbox" [checked]="checked" (change)="toggleCheckbox()"/> <input type="checkbox" [checked]="checked" (change)="toggleCheckbox()"/>
{{ 'dso-selector.claim.item.not-mine-label' | translate }} {{ 'dso-selector.claim.item.not-mine-label' | translate }}
</div> </div>
<button type="submit" class="btn btn-primary ml-5 mr-2" (click)="createFromScratch()" [disabled]="!checked"> <button type="submit" class="btn btn-primary ml-5 mr-2" (click)="createFromScratch()" [dsDisabled]="!checked">
<i class="fas fa-plus"></i> <i class="fas fa-plus"></i>
{{ 'dso-selector.claim.item.create-from-scratch' | translate }} {{ 'dso-selector.claim.item.create-from-scratch' | translate }}
</button> </button>

View File

@@ -13,7 +13,7 @@
<p>{{'researcher.profile.not.associated' | translate}}</p> <p>{{'researcher.profile.not.associated' | translate}}</p>
</div> </div>
<button *ngIf="!researcherProfile" class="btn btn-primary mr-2" <button *ngIf="!researcherProfile" class="btn btn-primary mr-2"
[disabled]="(isProcessingCreate() | async)" [dsDisabled]="(isProcessingCreate() | async)"
(click)="createProfile()"> (click)="createProfile()">
<span *ngIf="(isProcessingCreate() | async)"> <span *ngIf="(isProcessingCreate() | async)">
<i class='fas fa-circle-notch fa-spin'></i> {{'researcher.profile.action.processing' | translate}} <i class='fas fa-circle-notch fa-spin'></i> {{'researcher.profile.action.processing' | translate}}
@@ -23,10 +23,10 @@
</span> </span>
</button> </button>
<ng-container *ngIf="researcherProfile"> <ng-container *ngIf="researcherProfile">
<button class="btn btn-primary mr-2" [disabled]="!researcherProfile" (click)="viewProfile(researcherProfile)"> <button class="btn btn-primary mr-2" [dsDisabled]="!researcherProfile" (click)="viewProfile(researcherProfile)">
<i class="fas fa-info-circle"></i> {{'researcher.profile.view' | translate}} <i class="fas fa-info-circle"></i> {{'researcher.profile.view' | translate}}
</button> </button>
<button class="btn btn-danger" [disabled]="!researcherProfile" (click)="deleteProfile(researcherProfile)"> <button class="btn btn-danger" [dsDisabled]="!researcherProfile" (click)="deleteProfile(researcherProfile)">
<span *ngIf="(isProcessingDelete() | async)"> <span *ngIf="(isProcessingDelete() | async)">
<i class='fas fa-circle-notch fa-spin'></i> {{'researcher.profile.action.processing' | translate}} <i class='fas fa-circle-notch fa-spin'></i> {{'researcher.profile.action.processing' | translate}}
</span> </span>

View File

@@ -81,7 +81,7 @@
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
<button <button
[disabled]="isInValidPassword || userInfoForm.invalid" [dsDisabled]="isInValidPassword || userInfoForm.invalid"
class="btn btn-default btn-primary" class="btn btn-default btn-primary"
(click)="submitEperson()">{{'register-page.create-profile.submit' | translate}}</button> (click)="submitEperson()">{{'register-page.create-profile.submit' | translate}}</button>
</div> </div>

View File

@@ -0,0 +1,89 @@
import { Component, DebugElement } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DisabledDirective } from './disabled-directive';
@Component({
template: `
<button [dsDisabled]="isDisabled">Test Button</button>
`
})
class TestComponent {
isDisabled = false;
}
describe('DisabledDirective', () => {
let component: TestComponent;
let fixture: ComponentFixture<TestComponent>;
let button: DebugElement;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [TestComponent, DisabledDirective]
});
fixture = TestBed.createComponent(TestComponent);
component = fixture.componentInstance;
button = fixture.debugElement.query(By.css('button'));
fixture.detectChanges();
});
it('should bind aria-disabled to false initially', () => {
expect(button.nativeElement.getAttribute('aria-disabled')).toBe('false');
expect(button.nativeElement.classList.contains('disabled')).toBeFalse();
});
it('should bind aria-disabled to true and add disabled class when isDisabled is true', () => {
component.isDisabled = true;
fixture.detectChanges();
expect(button.nativeElement.getAttribute('aria-disabled')).toBe('true');
expect(button.nativeElement.classList.contains('disabled')).toBeTrue();
});
it('should prevent click events when disabled', () => {
component.isDisabled = true;
fixture.detectChanges();
let clickHandled = false;
button.nativeElement.addEventListener('click', () => clickHandled = true);
button.nativeElement.click();
expect(clickHandled).toBeFalse();
});
it('should prevent Enter or Space keydown events when disabled', () => {
component.isDisabled = true;
fixture.detectChanges();
let keydownHandled = false;
button.nativeElement.addEventListener('keydown', () => keydownHandled = true);
const enterEvent = new KeyboardEvent('keydown', { key: 'Enter' });
const spaceEvent = new KeyboardEvent('keydown', { key: 'Space' });
button.nativeElement.dispatchEvent(enterEvent);
button.nativeElement.dispatchEvent(spaceEvent);
expect(keydownHandled).toBeFalse();
});
it('should allow click and keydown events when not disabled', () => {
let clickHandled = false;
let keydownHandled = false;
button.nativeElement.addEventListener('click', () => clickHandled = true);
button.nativeElement.addEventListener('keydown', () => keydownHandled = true);
button.nativeElement.click();
const enterEvent = new KeyboardEvent('keydown', { key: 'Enter' });
const spaceEvent = new KeyboardEvent('keydown', { key: 'Space' });
button.nativeElement.dispatchEvent(enterEvent);
button.nativeElement.dispatchEvent(spaceEvent);
expect(clickHandled).toBeTrue();
expect(keydownHandled).toBeTrue();
});
});

View File

@@ -0,0 +1,31 @@
import { Directive, Input, HostBinding, HostListener } from '@angular/core';
@Directive({
selector: '[dsDisabled]'
})
export class DisabledDirective {
@Input() set dsDisabled(value: boolean) {
this.isDisabled = !!value;
}
@HostBinding('attr.aria-disabled') isDisabled = false;
@HostBinding('class.disabled') get disabledClass() { return this.isDisabled; }
@HostListener('click', ['$event'])
handleClick(event: Event) {
if (this.isDisabled) {
event.preventDefault();
event.stopImmediatePropagation();
}
}
@HostListener('keydown', ['$event'])
handleKeydown(event: KeyboardEvent) {
if (this.isDisabled && (event.key === 'Enter' || event.key === 'Space')) {
event.preventDefault();
event.stopImmediatePropagation();
}
}
}

View File

@@ -13,7 +13,7 @@
class="btn btn-outline-primary selection" class="btn btn-outline-primary selection"
(blur)="close.emit($event)" (blur)="close.emit($event)"
(click)="close.emit($event)" (click)="close.emit($event)"
[disabled]="disabled" [dsDisabled]="disabled"
ngbDropdownToggle> ngbDropdownToggle>
<ng-content select=".selection"></ng-content> <ng-content select=".selection"></ng-content>
</button> </button>

View File

@@ -31,7 +31,7 @@
<div *ngIf="model.languageCodes && model.languageCodes.length > 0" class="col-xs-2" > <div *ngIf="model.languageCodes && model.languageCodes.length > 0" class="col-xs-2" >
<select <select
#language="ngModel" #language="ngModel"
[disabled]="model.readOnly" [dsDisabled]="model.readOnly"
[(ngModel)]="model.language" [(ngModel)]="model.language"
class="form-control" class="form-control"
(blur)="onBlur($event)" (blur)="onBlur($event)"

View File

@@ -29,7 +29,7 @@
type="button" type="button"
[attr.aria-labelledby]="'label_' + model.id" [attr.aria-labelledby]="'label_' + model.id"
[class.disabled]="model.disabled" [class.disabled]="model.disabled"
[disabled]="model.disabled" [dsDisabled]="model.disabled"
(click)="datepicker.toggle()"> (click)="datepicker.toggle()">
<i *ngIf="model.toggleIcon" class="{{model.toggleIcon}}" aria-hidden="true"></i> <i *ngIf="model.toggleIcon" class="{{model.toggleIcon}}" aria-hidden="true"></i>

View File

@@ -6,7 +6,7 @@
<ds-number-picker <ds-number-picker
tabindex="0" tabindex="0"
[id]="model.id + '_year'" [id]="model.id + '_year'"
[disabled]="model.disabled" [dsDisabled]="model.disabled"
[min]="minYear" [min]="minYear"
[max]="maxYear" [max]="maxYear"
[name]="'year'" [name]="'year'"
@@ -30,7 +30,7 @@
[(ngModel)]="initialMonth" [(ngModel)]="initialMonth"
[value]="month" [value]="month"
[placeholder]="monthPlaceholder" [placeholder]="monthPlaceholder"
[disabled]="!year || model.disabled" [dsDisabled]="!year || model.disabled"
(blur)="onBlur($event)" (blur)="onBlur($event)"
(change)="onChange($event)" (change)="onChange($event)"
(focus)="onFocus($event)" (focus)="onFocus($event)"
@@ -46,7 +46,7 @@
[(ngModel)]="initialDay" [(ngModel)]="initialDay"
[value]="day" [value]="day"
[placeholder]="dayPlaceholder" [placeholder]="dayPlaceholder"
[disabled]="!month || model.disabled" [dsDisabled]="!month || model.disabled"
(blur)="onBlur($event)" (blur)="onBlur($event)"
(change)="onChange($event)" (change)="onChange($event)"
(focus)="onFocus($event)" (focus)="onFocus($event)"

View File

@@ -6,7 +6,7 @@
[id]="id" [id]="id"
[name]="model.name" [name]="model.name"
[value]="modelValuesString" [value]="modelValuesString"
[disabled]="model.disabled" [dsDisabled]="model.disabled"
[type]="model.inputType" [type]="model.inputType"
[placeholder]="model.placeholder | translate" [placeholder]="model.placeholder | translate"
[readonly]="model.readOnly"> [readonly]="model.readOnly">

View File

@@ -75,7 +75,7 @@ describe('DsDynamicDisabledComponent', () => {
expect(comp).toBeTruthy(); expect(comp).toBeTruthy();
}); });
it('should have a disabled input', () => { xit('should have a disabled input', () => {
const input = de.query(By.css('input')); const input = de.query(By.css('input'));
expect(input.nativeElement.getAttribute('disabled')).toEqual(''); expect(input.nativeElement.getAttribute('disabled')).toEqual('');
}); });

View File

@@ -18,7 +18,7 @@
[name]="model.name" [name]="model.name"
[type]="model.inputType" [type]="model.inputType"
[(ngModel)]="firstInputValue" [(ngModel)]="firstInputValue"
[disabled]="isInputDisabled()" [dsDisabled]="isInputDisabled()"
[placeholder]="model.placeholder | translate" [placeholder]="model.placeholder | translate"
[readonly]="model.readOnly" [readonly]="model.readOnly"
(change)="onChange($event)" (change)="onChange($event)"
@@ -38,7 +38,7 @@
[name]="model.name + '_2'" [name]="model.name + '_2'"
[type]="model.inputType" [type]="model.inputType"
[(ngModel)]="secondInputValue" [(ngModel)]="secondInputValue"
[disabled]="firstInputValue.length === 0 || isInputDisabled()" [dsDisabled]="firstInputValue.length === 0 || isInputDisabled()"
[placeholder]="model.secondPlaceholder | translate" [placeholder]="model.secondPlaceholder | translate"
[readonly]="model.readOnly" [readonly]="model.readOnly"
(change)="onChange($event)" (change)="onChange($event)"
@@ -52,7 +52,7 @@
type="button" type="button"
ngbTooltip="{{'form.search-help' | translate}}" ngbTooltip="{{'form.search-help' | translate}}"
placement="top" placement="top"
[disabled]="model.readOnly || isSearchDisabled()" [dsDisabled]="model.readOnly || isSearchDisabled()"
[hidden]="isInputDisabled()" [hidden]="isInputDisabled()"
(click)="sdRef.open(); search(); $event.stopPropagation();">{{'form.search' | translate}} (click)="sdRef.open(); search(); $event.stopPropagation();">{{'form.search' | translate}}
</button> </button>
@@ -60,7 +60,7 @@
type="button" type="button"
ngbTooltip="{{'form.clear-help' | translate}}" ngbTooltip="{{'form.clear-help' | translate}}"
placement="top" placement="top"
[disabled]="model.readOnly" [dsDisabled]="model.readOnly"
(click)="remove()">{{'form.clear' | translate}} (click)="remove()">{{'form.clear' | translate}}
</button> </button>
</div> </div>
@@ -69,14 +69,14 @@
type="button" type="button"
ngbTooltip="{{'form.edit-help' | translate}}" ngbTooltip="{{'form.edit-help' | translate}}"
placement="top" placement="top"
[disabled]="isEditDisabled()" [dsDisabled]="isEditDisabled()"
(click)="switchEditMode()">{{'form.edit' | translate}} (click)="switchEditMode()">{{'form.edit' | translate}}
</button> </button>
<button *ngIf="editMode" class="btn btn-secondary" <button *ngIf="editMode" class="btn btn-secondary"
type="button" type="button"
ngbTooltip="{{'form.save-help' | translate}}" ngbTooltip="{{'form.save-help' | translate}}"
placement="top" placement="top"
[disabled]="!hasEmptyValue()" [dsDisabled]="!hasEmptyValue()"
(click)="saveChanges()">{{'form.save' | translate}} (click)="saveChanges()">{{'form.save' | translate}}
</button> </button>
</div> </div>

View File

@@ -48,6 +48,7 @@ import {
DynamicLookupModelConfig, DynamicLookupModelConfig,
} from './dynamic-lookup.model'; } from './dynamic-lookup.model';
import { DynamicLookupNameModel } from './dynamic-lookup-name.model'; import { DynamicLookupNameModel } from './dynamic-lookup-name.model';
import {DisabledDirective} from '../../../../../disabled-directive';
let LOOKUP_TEST_MODEL_CONFIG: DynamicLookupModelConfig = { let LOOKUP_TEST_MODEL_CONFIG: DynamicLookupModelConfig = {
vocabularyOptions: { vocabularyOptions: {
@@ -174,6 +175,7 @@ describe('Dynamic Lookup component', () => {
TestComponent, TestComponent,
AuthorityConfidenceStateDirective, AuthorityConfidenceStateDirective,
ObjNgFor, ObjNgFor,
DisabledDirective,
], ],
providers: [ providers: [
ChangeDetectorRef, ChangeDetectorRef,
@@ -255,8 +257,10 @@ describe('Dynamic Lookup component', () => {
const de = lookupFixture.debugElement.queryAll(By.css('button')); const de = lookupFixture.debugElement.queryAll(By.css('button'));
const searchBtnEl = de[0].nativeElement; const searchBtnEl = de[0].nativeElement;
const editBtnEl = de[1].nativeElement; const editBtnEl = de[1].nativeElement;
expect(searchBtnEl.disabled).toBe(true); expect(searchBtnEl.getAttribute('aria-disabled')).toBe('true');
expect(editBtnEl.disabled).toBe(true); expect(searchBtnEl.classList.contains('disabled')).toBeTrue();
expect(editBtnEl.getAttribute('aria-disabled')).toBe('true');
expect(editBtnEl.classList.contains('disabled')).toBeTrue();
expect(editBtnEl.textContent.trim()).toBe('form.edit'); expect(editBtnEl.textContent.trim()).toBe('form.edit');
}); });
@@ -354,8 +358,10 @@ describe('Dynamic Lookup component', () => {
const de = lookupFixture.debugElement.queryAll(By.css('button')); const de = lookupFixture.debugElement.queryAll(By.css('button'));
const searchBtnEl = de[0].nativeElement; const searchBtnEl = de[0].nativeElement;
const saveBtnEl = de[1].nativeElement; const saveBtnEl = de[1].nativeElement;
expect(searchBtnEl.disabled).toBe(true); expect(searchBtnEl.getAttribute('aria-disabled')).toBe('true');
expect(saveBtnEl.disabled).toBe(false); expect(searchBtnEl.classList.contains('disabled')).toBeTrue();
expect(saveBtnEl.getAttribute('aria-disabled')).not.toBe('true');
expect(saveBtnEl.classList.contains('disabled')).toBeFalse();
expect(saveBtnEl.textContent.trim()).toBe('form.save'); expect(saveBtnEl.textContent.trim()).toBe('form.save');
}); });
@@ -395,8 +401,10 @@ describe('Dynamic Lookup component', () => {
const de = lookupFixture.debugElement.queryAll(By.css('button')); const de = lookupFixture.debugElement.queryAll(By.css('button'));
const searchBtnEl = de[0].nativeElement; const searchBtnEl = de[0].nativeElement;
const saveBtnEl = de[1].nativeElement; const saveBtnEl = de[1].nativeElement;
expect(searchBtnEl.disabled).toBe(true); expect(searchBtnEl.getAttribute('aria-disabled')).toBe('true');
expect(saveBtnEl.disabled).toBe(false); expect(searchBtnEl.classList.contains('disabled')).toBeTrue();
expect(saveBtnEl.getAttribute('aria-disabled')).not.toBe('true');
expect(saveBtnEl.classList.contains('disabled')).toBeFalse();
expect(saveBtnEl.textContent.trim()).toBe('form.save'); expect(saveBtnEl.textContent.trim()).toBe('form.save');
}); });
@@ -427,8 +435,10 @@ describe('Dynamic Lookup component', () => {
const editBtnEl = deBtn[1].nativeElement; const editBtnEl = deBtn[1].nativeElement;
expect(de.length).toBe(2); expect(de.length).toBe(2);
expect(searchBtnEl.disabled).toBe(true); expect(searchBtnEl.getAttribute('aria-disabled')).toBe('true');
expect(editBtnEl.disabled).toBe(true); expect(searchBtnEl.classList.contains('disabled')).toBeTrue();
expect(editBtnEl.getAttribute('aria-disabled')).toBe('true');
expect(editBtnEl.classList.contains('disabled')).toBeTrue();
expect(editBtnEl.textContent.trim()).toBe('form.edit'); expect(editBtnEl.textContent.trim()).toBe('form.edit');
}); });
@@ -524,8 +534,10 @@ describe('Dynamic Lookup component', () => {
const de = lookupFixture.debugElement.queryAll(By.css('button')); const de = lookupFixture.debugElement.queryAll(By.css('button'));
const searchBtnEl = de[0].nativeElement; const searchBtnEl = de[0].nativeElement;
const saveBtnEl = de[1].nativeElement; const saveBtnEl = de[1].nativeElement;
expect(searchBtnEl.disabled).toBe(true); expect(searchBtnEl.getAttribute('aria-disabled')).toBe('true');
expect(saveBtnEl.disabled).toBe(false); expect(searchBtnEl.classList.contains('disabled')).toBeTrue();
expect(saveBtnEl.getAttribute('aria-disabled')).not.toBe('true');
expect(saveBtnEl.classList.contains('disabled')).toBeFalse();
expect(saveBtnEl.textContent.trim()).toBe('form.save'); expect(saveBtnEl.textContent.trim()).toBe('form.save');
}); });
@@ -567,8 +579,10 @@ describe('Dynamic Lookup component', () => {
const de = lookupFixture.debugElement.queryAll(By.css('button')); const de = lookupFixture.debugElement.queryAll(By.css('button'));
const searchBtnEl = de[0].nativeElement; const searchBtnEl = de[0].nativeElement;
const saveBtnEl = de[1].nativeElement; const saveBtnEl = de[1].nativeElement;
expect(searchBtnEl.disabled).toBe(true); expect(searchBtnEl.getAttribute('aria-disabled')).toBe('true');
expect(saveBtnEl.disabled).toBe(false); expect(searchBtnEl.classList.contains('disabled')).toBeTrue();
expect(saveBtnEl.getAttribute('aria-disabled')).not.toBe('true');
expect(saveBtnEl.classList.contains('disabled')).toBeFalse();
expect(saveBtnEl.textContent.trim()).toBe('form.save'); expect(saveBtnEl.textContent.trim()).toBe('form.save');
}); });

View File

@@ -40,7 +40,7 @@
[ngbTypeahead]="search" [ngbTypeahead]="search"
[placeholder]="model.placeholder" [placeholder]="model.placeholder"
[readonly]="model.readOnly" [readonly]="model.readOnly"
[disabled]="model.readOnly" [dsDisabled]="model.readOnly"
[resultTemplate]="rt" [resultTemplate]="rt"
[type]="model.inputType" [type]="model.inputType"
[(ngModel)]="currentValue" [(ngModel)]="currentValue"
@@ -66,7 +66,7 @@
[name]="model.name" [name]="model.name"
[placeholder]="model.placeholder" [placeholder]="model.placeholder"
[readonly]="true" [readonly]="true"
[disabled]="model.readOnly" [dsDisabled]="model.readOnly"
[type]="model.inputType" [type]="model.inputType"
[value]="currentValue?.display" [value]="currentValue?.display"
(focus)="onFocus($event)" (focus)="onFocus($event)"

View File

@@ -32,21 +32,21 @@
<button type="button" <button type="button"
class="btn btn-link" class="btn btn-link"
[disabled]="isMandatoryFieldEmpty()" [dsDisabled]="isMandatoryFieldEmpty()"
(click)="save()"> (click)="save()">
<i class="fas fa-save text-primary fa-2x" <i class="fas fa-save text-primary fa-2x"
aria-hidden="true"></i> aria-hidden="true"></i>
</button> </button>
<button type="button" <button type="button"
class="btn btn-link" class="btn btn-link"
[disabled]="!editMode" [dsDisabled]="!editMode"
(click)="delete()"> (click)="delete()">
<i class="fas fa-trash text-danger fa-2x" <i class="fas fa-trash text-danger fa-2x"
aria-hidden="true"></i> aria-hidden="true"></i>
</button> </button>
<button type="button" <button type="button"
class="btn btn-link" class="btn btn-link"
[disabled]="isMandatoryFieldEmpty()" [dsDisabled]="isMandatoryFieldEmpty()"
(click)="clear()"> (click)="clear()">
<i class="fas fa-undo fa-2x" <i class="fas fa-undo fa-2x"
aria-hidden="true"></i> aria-hidden="true"></i>

View File

@@ -18,7 +18,7 @@
[id]="id" [id]="id"
[name]="model.name" [name]="model.name"
[readonly]="true" [readonly]="true"
[disabled]="model.readOnly" [dsDisabled]="model.readOnly"
[type]="model.inputType" [type]="model.inputType"
[value]="(currentValue | async)" [value]="(currentValue | async)"
(blur)="onBlur($event)" (blur)="onBlur($event)"

View File

@@ -44,6 +44,7 @@ import {
AddRelationshipAction, AddRelationshipAction,
RemoveRelationshipAction, RemoveRelationshipAction,
} from './relationship.actions'; } from './relationship.actions';
import {DisabledDirective} from '../../../../disabled-directive';
describe('DsDynamicLookupRelationModalComponent', () => { describe('DsDynamicLookupRelationModalComponent', () => {
let component: DsDynamicLookupRelationModalComponent; let component: DsDynamicLookupRelationModalComponent;
@@ -123,7 +124,7 @@ describe('DsDynamicLookupRelationModalComponent', () => {
beforeEach(waitForAsync(() => { beforeEach(waitForAsync(() => {
init(); init();
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([]), NgbModule, DsDynamicLookupRelationModalComponent], imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([]), NgbModule, DsDynamicLookupRelationModalComponent, DisabledDirective],
providers: [ providers: [
{ {
provide: SearchConfigurationService, useValue: { provide: SearchConfigurationService, useValue: {
@@ -223,10 +224,12 @@ describe('DsDynamicLookupRelationModalComponent', () => {
describe('when initialized and is relationship show the list of buttons', () => { describe('when initialized and is relationship show the list of buttons', () => {
it('submit button should be disabled', () => { it('submit button should be disabled', () => {
expect(debugElement.query(By.css('.submit')).nativeElement?.disabled).toBeTrue(); expect(debugElement.query(By.css('.submit')).nativeElement.getAttribute('aria-disabled')).toBe('true');
expect(debugElement.query(By.css('.submit')).nativeElement.classList.contains('disabled')).toBeTrue();
}); });
it('discard button should be disabled', () => { it('discard button should be disabled', () => {
expect(debugElement.query(By.css('.discard')).nativeElement?.disabled).toBeTrue(); expect(debugElement.query(By.css('.discard')).nativeElement.getAttribute('aria-disabled')).toBe('true');
expect(debugElement.query(By.css('.discard')).nativeElement.classList.contains('disabled')).toBeTrue();
}); });
}); });
@@ -264,9 +267,12 @@ describe('DsDynamicLookupRelationModalComponent', () => {
it('there should show 1 spinner and disable all 3 buttons', () => { it('there should show 1 spinner and disable all 3 buttons', () => {
expect(debugElement.queryAll(By.css('.spinner-border')).length).toEqual(1); expect(debugElement.queryAll(By.css('.spinner-border')).length).toEqual(1);
expect(debugElement.query(By.css('.submit')).nativeElement?.disabled).toBeTrue(); expect(debugElement.query(By.css('.submit')).nativeElement?.getAttribute('aria-disabled')).toBe('true');
expect(debugElement.query(By.css('.discard')).nativeElement?.disabled).toBeTrue(); expect(debugElement.query(By.css('.submit')).nativeElement?.classList.contains('disabled')).toBeTrue();
expect(debugElement.query(By.css('.close')).nativeElement?.disabled).toBeTrue(); expect(debugElement.query(By.css('.discard')).nativeElement?.getAttribute('aria-disabled')).toBe('true');
expect(debugElement.query(By.css('.discard')).nativeElement?.classList.contains('disabled')).toBeTrue();
expect(debugElement.query(By.css('.close')).nativeElement?.getAttribute('aria-disabled')).toBe('true');
expect(debugElement.query(By.css('.close')).nativeElement?.classList.contains('disabled')).toBeTrue();
}); });
}); });

View File

@@ -49,6 +49,6 @@
<button type="button" class="btn btn-outline-secondary" (click)="close()">{{ (labelPrefix + 'cancel' | translate) }}</button> <button type="button" class="btn btn-outline-secondary" (click)="close()">{{ (labelPrefix + 'cancel' | translate) }}</button>
</div> </div>
<div> <div>
<button type="button" class="btn btn-primary" [disabled]="selectedImportType === importType.None" (click)="import()">{{ (labelPrefix + 'import' | translate) }}</button> <button type="button" class="btn btn-primary" [dsDisabled]="selectedImportType === importType.None" (click)="import()">{{ (labelPrefix + 'import' | translate) }}</button>
</div> </div>
</div> </div>

View File

@@ -3,7 +3,7 @@
class="btn btn-link-focus" class="btn btn-link-focus"
type="button" type="button"
tabindex="0" tabindex="0"
[disabled]="disabled" [dsDisabled]="disabled"
(click)="toggleUp()"> (click)="toggleUp()">
<span class="chevron"></span> <span class="chevron"></span>
<span class="sr-only">{{'form.number-picker.increment' | translate: {field: name} }}</span> <span class="sr-only">{{'form.number-picker.increment' | translate: {field: name} }}</span>
@@ -21,7 +21,7 @@
(change)="update($event); $event.stopPropagation();" (change)="update($event); $event.stopPropagation();"
(focus)="onFocus($event); $event.stopPropagation();" (focus)="onFocus($event); $event.stopPropagation();"
[readonly]="disabled" [readonly]="disabled"
[disabled]="disabled" [dsDisabled]="disabled"
[ngClass]="{'is-invalid': invalid}" [ngClass]="{'is-invalid': invalid}"
title="{{placeholder}}" title="{{placeholder}}"
[attr.aria-label]="placeholder" [attr.aria-label]="placeholder"
@@ -30,7 +30,7 @@
class="btn btn-link-focus" class="btn btn-link-focus"
type="button" type="button"
tabindex="0" tabindex="0"
[disabled]="disabled" [dsDisabled]="disabled"
(click)="toggleDown()"> (click)="toggleDown()">
<span class="chevron bottom"></span> <span class="chevron bottom"></span>
<span class="sr-only">{{'form.number-picker.decrement' | translate: {field: name} }}</span> <span class="sr-only">{{'form.number-picker.decrement' | translate: {field: name} }}</span>

View File

@@ -5,7 +5,7 @@
<input type="text" class="form-control" [(ngModel)]="searchText" (keyup.enter)="search()" <input type="text" class="form-control" [(ngModel)]="searchText" (keyup.enter)="search()"
[placeholder]="'vocabulary-treeview.search.form.search-placeholder' | translate"> [placeholder]="'vocabulary-treeview.search.form.search-placeholder' | translate">
<div class="input-group-append" id="button-addon4"> <div class="input-group-append" id="button-addon4">
<button class="btn btn-outline-primary" type="button" (click)="search()" [disabled]="!isSearchEnabled()"> <button class="btn btn-outline-primary" type="button" (click)="search()" [dsDisabled]="!isSearchEnabled()">
{{'vocabulary-treeview.search.form.search' | translate}} {{'vocabulary-treeview.search.form.search' | translate}}
</button> </button>
<button class="btn btn-outline-secondary" type="button" (click)="reset()"> <button class="btn btn-outline-secondary" type="button" (click)="reset()">
@@ -39,7 +39,7 @@
container="body" container="body"
> >
<input class="mr-2" type="checkbox" <input class="mr-2" type="checkbox"
[disabled]="!node.item?.selectable" [dsDisabled]="!node.item?.selectable"
[(ngModel)]="node.isSelected" [(ngModel)]="node.isSelected"
[checked]="node.isSelected" [checked]="node.isSelected"
(change)="onSelect(node.item)" (change)="onSelect(node.item)"
@@ -48,7 +48,7 @@
</label> </label>
<button *ngIf="!multiSelect" class="btn btn-outline-link btn-sm text-left" <button *ngIf="!multiSelect" class="btn btn-outline-link btn-sm text-left"
[class.text-success]="node.isSelected" [class.text-success]="node.isSelected"
[disabled]="!node.item?.selectable" [dsDisabled]="!node.item?.selectable"
[ngbTooltip]="node.item?.otherInformation?.note" [ngbTooltip]="node.item?.otherInformation?.note"
[openDelay]="500" [openDelay]="500"
container="body" container="body"
@@ -71,7 +71,7 @@
[openDelay]="500" [openDelay]="500"
container="body"> container="body">
<input class="mr-2" type="checkbox" <input class="mr-2" type="checkbox"
[disabled]="!node.item?.selectable" [dsDisabled]="!node.item?.selectable"
[(ngModel)]="node.isSelected" [(ngModel)]="node.isSelected"
[checked]="node.isSelected" [checked]="node.isSelected"
(change)="onSelect(node.item)" (change)="onSelect(node.item)"
@@ -80,7 +80,7 @@
</label> </label>
<button *ngIf="!multiSelect" class="btn btn-outline-link btn-sm text-left" <button *ngIf="!multiSelect" class="btn btn-outline-link btn-sm text-left"
[class.text-success]="node.isSelected" [class.text-success]="node.isSelected"
[disabled]="!node.item?.selectable" [dsDisabled]="!node.item?.selectable"
[ngbTooltip]="node.item?.otherInformation?.note" [ngbTooltip]="node.item?.otherInformation?.note"
[openDelay]="500" [openDelay]="500"
container="body" container="body"

View File

@@ -24,7 +24,7 @@
@fadeOut>{{ (message | async) | translate }}</div> @fadeOut>{{ (message | async) | translate }}</div>
<button class="btn btn-lg btn-primary btn-block mt-3" type="submit" [attr.data-test]="'login-button' | dsBrowserOnly" <button class="btn btn-lg btn-primary btn-block mt-3" type="submit" [attr.data-test]="'login-button' | dsBrowserOnly"
[disabled]="!form.valid"><i class="fas fa-sign-in-alt"></i> {{"login.form.submit" | translate}}</button> [dsDisabled]="!form.valid"><i class="fas fa-sign-in-alt"></i> {{"login.form.submit" | translate}}</button>
</form> </form>
<ng-container *ngIf="canShowDivider$ | async"> <ng-container *ngIf="canShowDivider$ | async">

View File

@@ -1,7 +1,7 @@
<button type="button" <button type="button"
[className]="'btn btn-success'" [className]="'btn btn-success'"
ngbTooltip="{{'submission.workflow.tasks.claimed.approve_help' | translate}}" ngbTooltip="{{'submission.workflow.tasks.claimed.approve_help' | translate}}"
[disabled]="processing$ | async" [dsDisabled]="processing$ | async"
(click)="submitTask()"> (click)="submitTask()">
<span *ngIf="processing$ | async"><i class='fas fa-circle-notch fa-spin'></i> {{'submission.workflow.tasks.generic.processing' | translate}}</span> <span *ngIf="processing$ | async"><i class='fas fa-circle-notch fa-spin'></i> {{'submission.workflow.tasks.generic.processing' | translate}}</span>
<span *ngIf="(processing$ | async) !== true"><i class="fa fa-thumbs-up"></i> {{'submission.workflow.tasks.claimed.approve' | translate}}</span> <span *ngIf="(processing$ | async) !== true"><i class="fa fa-thumbs-up"></i> {{'submission.workflow.tasks.claimed.approve' | translate}}</span>

View File

@@ -1,5 +1,5 @@
<button (click)="submitTask()" <button (click)="submitTask()"
[disabled]="processing$ | async" [dsDisabled]="processing$ | async"
class="declineTaskAction btn btn-warning" class="declineTaskAction btn btn-warning"
ngbTooltip="{{'submission.workflow.tasks.claimed.decline_help' | translate}}" ngbTooltip="{{'submission.workflow.tasks.claimed.decline_help' | translate}}"
type="button"> type="button">

View File

@@ -1,7 +1,7 @@
<ng-template #rejectTipContent><p [innerHTML]="'submission.workflow.tasks.claimed.reject_help' | translate"></p></ng-template> <ng-template #rejectTipContent><p [innerHTML]="'submission.workflow.tasks.claimed.reject_help' | translate"></p></ng-template>
<button [className]="'btn btn-danger'" <button [className]="'btn btn-danger'"
[ngbTooltip]="rejectTipContent" [ngbTooltip]="rejectTipContent"
[disabled]="processing$ | async" [dsDisabled]="processing$ | async"
(click)="openRejectModal(rejectModal)" > (click)="openRejectModal(rejectModal)" >
<span *ngIf="processing$ | async"><i class='fas fa-circle-notch fa-spin'></i> {{'submission.workflow.tasks.generic.processing' | translate}}</span> <span *ngIf="processing$ | async"><i class='fas fa-circle-notch fa-spin'></i> {{'submission.workflow.tasks.generic.processing' | translate}}</span>
<span *ngIf="(processing$ | async) !== true"><i class="fa fa-trash"></i> {{'submission.workflow.tasks.claimed.reject.submit' | translate}}</span> <span *ngIf="(processing$ | async) !== true"><i class="fa fa-trash"></i> {{'submission.workflow.tasks.claimed.reject.submit' | translate}}</span>
@@ -28,7 +28,7 @@
placeholder="{{'submission.workflow.tasks.claimed.reject.reason.placeholder' | translate}}"></textarea> placeholder="{{'submission.workflow.tasks.claimed.reject.reason.placeholder' | translate}}"></textarea>
<button id="btn-chat" <button id="btn-chat"
class="btn btn-danger btn-lg btn-block mt-3" class="btn btn-danger btn-lg btn-block mt-3"
[disabled]="!rejectForm.valid || (processing$ | async)" [dsDisabled]="!rejectForm.valid || (processing$ | async)"
type="submit"> type="submit">
<span *ngIf="processing$ | async"><i class='fas fa-circle-notch fa-spin'></i> {{'submission.workflow.tasks.generic.processing' | translate}}</span> <span *ngIf="processing$ | async"><i class='fas fa-circle-notch fa-spin'></i> {{'submission.workflow.tasks.generic.processing' | translate}}</span>
<span *ngIf="(processing$ | async) !== true">{{'submission.workflow.tasks.claimed.reject.reason.submit' | translate}}</span> <span *ngIf="(processing$ | async) !== true">{{'submission.workflow.tasks.claimed.reject.reason.submit' | translate}}</span>

View File

@@ -1,7 +1,7 @@
<button type="button" <button type="button"
[className]="'btn btn-secondary'" [className]="'btn btn-secondary'"
ngbTooltip="{{'submission.workflow.tasks.claimed.return_help' | translate}}" ngbTooltip="{{'submission.workflow.tasks.claimed.return_help' | translate}}"
[disabled]="processing$ | async" [dsDisabled]="processing$ | async"
(click)="submitTask()"> (click)="submitTask()">
<span *ngIf="processing$ | async"><i class='fas fa-circle-notch fa-spin'></i> {{'submission.workflow.tasks.generic.processing' | translate}}</span> <span *ngIf="processing$ | async"><i class='fas fa-circle-notch fa-spin'></i> {{'submission.workflow.tasks.generic.processing' | translate}}</span>
<span *ngIf="(processing$ | async) !== true"><i class="fa fa-undo"></i> {{'submission.workflow.tasks.claimed.return' | translate}}</span> <span *ngIf="(processing$ | async) !== true"><i class="fa fa-undo"></i> {{'submission.workflow.tasks.claimed.return' | translate}}</span>

View File

@@ -1,5 +1,5 @@
<button type="button" class="btn btn-info mt-1 mb-3" <button type="button" class="btn btn-info mt-1 mb-3"
ngbTooltip="{{'submission.workflow.tasks.pool.claim_help' | translate}}" [disabled]="(processing$ | async)" ngbTooltip="{{'submission.workflow.tasks.pool.claim_help' | translate}}" [dsDisabled]="(processing$ | async)"
(click)="claim()"> (click)="claim()">
<span *ngIf="(processing$ | async)"><i class='fas fa-circle-notch fa-spin'></i> <span *ngIf="(processing$ | async)"><i class='fas fa-circle-notch fa-spin'></i>
{{'submission.workflow.tasks.generic.processing' | translate}}</span> {{'submission.workflow.tasks.generic.processing' | translate}}</span>

View File

@@ -34,7 +34,7 @@
</button> </button>
<button class="btn collection-confirm" <button class="btn collection-confirm"
[ngClass]="{'btn-danger': dangerConfirm, 'btn-primary': !dangerConfirm}" [ngClass]="{'btn-danger': dangerConfirm, 'btn-primary': !dangerConfirm}"
[disabled]="selectedIds?.length === 0" [dsDisabled]="selectedIds?.length === 0"
(click)="confirmSelected()"> (click)="confirmSelected()">
<i class="fas fa-trash"></i> {{confirmButton | translate}} <i class="fas fa-trash"></i> {{confirmButton | translate}}
</button> </button>

View File

@@ -28,6 +28,7 @@ import { SearchConfigurationServiceStub } from '../../testing/search-configurati
import { createPaginatedList } from '../../testing/utils.test'; import { createPaginatedList } from '../../testing/utils.test';
import { ObjectSelectService } from '../object-select.service'; import { ObjectSelectService } from '../object-select.service';
import { ItemSelectComponent } from './item-select.component'; import { ItemSelectComponent } from './item-select.component';
import {DisabledDirective} from '../../disabled-directive';
describe('ItemSelectComponent', () => { describe('ItemSelectComponent', () => {
let comp: ItemSelectComponent; let comp: ItemSelectComponent;
@@ -101,7 +102,7 @@ describe('ItemSelectComponent', () => {
beforeEach(waitForAsync(() => { beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([])], imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([]), DisabledDirective],
providers: [ providers: [
{ provide: ObjectSelectService, useValue: new ObjectSelectServiceStub([mockItemList[1].id]) }, { provide: ObjectSelectService, useValue: new ObjectSelectServiceStub([mockItemList[1].id]) },
{ provide: HostWindowService, useValue: new HostWindowServiceStub(0) }, { provide: HostWindowService, useValue: new HostWindowServiceStub(0) },
@@ -196,7 +197,9 @@ describe('ItemSelectComponent', () => {
const checkbox = fixture.debugElement.query(By.css('input.item-checkbox')).nativeElement; const checkbox = fixture.debugElement.query(By.css('input.item-checkbox')).nativeElement;
expect(authorizationDataService.isAuthorized).toHaveBeenCalled(); expect(authorizationDataService.isAuthorized).toHaveBeenCalled();
expect(checkbox.disabled).toBeTrue(); expect(checkbox.getAttribute('aria-disabled')).toBe('true');
expect(checkbox.classList.contains('disabled')).toBe(true);
});
})); }));
}); });
}); });

View File

@@ -41,7 +41,7 @@
<div *ngIf="showPaginator" class="pagination justify-content-center clearfix bottom"> <div *ngIf="showPaginator" class="pagination justify-content-center clearfix bottom">
<ngb-pagination [boundaryLinks]="paginationOptions.boundaryLinks" <ngb-pagination [boundaryLinks]="paginationOptions.boundaryLinks"
[collectionSize]="collectionSize" [collectionSize]="collectionSize"
[disabled]="paginationOptions.disabled" [dsDisabled]="paginationOptions.disabled"
[ellipses]="paginationOptions.ellipses" [ellipses]="paginationOptions.ellipses"
[maxSize]="(isXs)?5:paginationOptions.maxSize" [maxSize]="(isXs)?5:paginationOptions.maxSize"
[page]="(currentPage$|async)" [page]="(currentPage$|async)"
@@ -55,12 +55,12 @@
<div *ngIf="!showPaginator" class="d-flex justify-content-between"> <div *ngIf="!showPaginator" class="d-flex justify-content-between">
<button id="nav-prev" type="button" class="btn btn-outline-primary float-left" <button id="nav-prev" type="button" class="btn btn-outline-primary float-left"
(click)="goPrev()" (click)="goPrev()"
[disabled]="(objects?.payload?.currentPage <= 1) && (paginationOptions?.currentPage <= 1)"> [dsDisabled]="(objects?.payload?.currentPage <= 1) && (paginationOptions?.currentPage <= 1)">
<i class="fas fa-angle-left"></i> {{'pagination.previous.button' |translate}} <i class="fas fa-angle-left"></i> {{'pagination.previous.button' |translate}}
</button> </button>
<button id="nav-next" type="button" class="btn btn-outline-primary float-right" <button id="nav-next" type="button" class="btn btn-outline-primary float-right"
(click)="goNext()" (click)="goNext()"
[disabled]="(objects?.payload?.currentPage >= objects?.payload?.totalPages) || (paginationOptions?.currentPage >= objects?.payload?.totalPages)"> [dsDisabled]="(objects?.payload?.currentPage >= objects?.payload?.totalPages) || (paginationOptions?.currentPage >= objects?.payload?.totalPages)">
<span [ngbTooltip]="objects?.payload?.currentPage >= objects?.payload?.totalPages ? ('pagination.next.button.disabled.tooltip' |translate) : null"> <span [ngbTooltip]="objects?.payload?.currentPage >= objects?.payload?.totalPages ? ('pagination.next.button.disabled.tooltip' |translate) : null">
<i class="fas fa-angle-right"></i> {{'pagination.next.button' |translate}} <i class="fas fa-angle-right"></i> {{'pagination.next.button' |translate}}
</span> </span>

View File

@@ -68,6 +68,7 @@ import {
ResourcePolicyEvent, ResourcePolicyEvent,
ResourcePolicyFormComponent, ResourcePolicyFormComponent,
} from './resource-policy-form.component'; } from './resource-policy-form.component';
import {DisabledDirective} from '../../disabled-directive';
export const mockResourcePolicyFormData = { export const mockResourcePolicyFormData = {
name: [ name: [
@@ -216,6 +217,7 @@ describe('ResourcePolicyFormComponent test suite', () => {
ResourcePolicyFormComponent, ResourcePolicyFormComponent,
TestComponent, TestComponent,
NgxMaskModule.forRoot(), NgxMaskModule.forRoot(),
DisabledDirective
], ],
providers: [ providers: [
{ provide: ActivatedRoute, useValue: activatedRouteStub }, { provide: ActivatedRoute, useValue: activatedRouteStub },
@@ -426,7 +428,8 @@ describe('ResourcePolicyFormComponent test suite', () => {
const depositBtn: any = fixture.debugElement.query(By.css('.btn-primary')); const depositBtn: any = fixture.debugElement.query(By.css('.btn-primary'));
expect(depositBtn.nativeElement.disabled).toBeFalsy(); expect(depositBtn.nativeElement.getAttribute('aria-disabled')).toBe('false');
expect(depositBtn.nativeElement.classList.contains('disabled')).toBeFalse();
}); });
it('should emit submit event', () => { it('should emit submit event', () => {
@@ -480,7 +483,8 @@ describe('ResourcePolicyFormComponent test suite', () => {
const depositBtn: any = fixture.debugElement.query(By.css('.btn-primary')); const depositBtn: any = fixture.debugElement.query(By.css('.btn-primary'));
expect(depositBtn.nativeElement.disabled).toBeTruthy(); expect(depositBtn.nativeElement.getAttribute('aria-disabled')).toBe('true');
expect(depositBtn.nativeElement.classList.contains('disabled')).toBeTrue();
}); });
}); });

View File

@@ -34,7 +34,7 @@
(click)="activeModal.close()"> (click)="activeModal.close()">
{{'subscriptions.modal.close' | translate}} {{'subscriptions.modal.close' | translate}}
</button> </button>
<button type="submit" class="btn btn-success" [disabled]="(processing$ | async) || !isValid"> <button type="submit" class="btn btn-success" [dsDisabled]="(processing$ | async) || !isValid">
<span *ngIf="(processing$ | async)"> <span *ngIf="(processing$ | async)">
<i class='fas fa-circle-notch fa-spin'></i> {{'subscriptions.modal.new-subscription-form.processing' | translate}} <i class='fas fa-circle-notch fa-spin'></i> {{'subscriptions.modal.new-subscription-form.processing' | translate}}
</span> </span>

View File

@@ -21,7 +21,7 @@
</td> </td>
<td class="subscription-actions"> <td class="subscription-actions">
<div class="btn-group edit-field"> <div class="btn-group edit-field">
<button (click)="$event.preventDefault();openSubscriptionModal();" [disabled]="!dso" <button (click)="$event.preventDefault();openSubscriptionModal();" [dsDisabled]="!dso"
[title]="'subscriptions.table.edit' | translate" [title]="'subscriptions.table.edit' | translate"
class="btn btn-outline-primary btn-sm access-control-editEPersonButton"> class="btn btn-outline-primary btn-sm access-control-editEPersonButton">
<i class="fas fa-edit fa-fw"></i> <i class="fas fa-edit fa-fw"></i>

View File

@@ -35,7 +35,7 @@
<span *ngIf="!uploader.options.disableMultipart">{{'uploader.queue-length' | translate}}: {{ uploader?.queue?.length }} | </span>{{ uploader?.queue[0]?.file.name }} <span *ngIf="!uploader.options.disableMultipart">{{'uploader.queue-length' | translate}}: {{ uploader?.queue?.length }} | </span>{{ uploader?.queue[0]?.file.name }}
</span> </span>
<div class="btn-group btn-group-sm float-right" role="group"> <div class="btn-group btn-group-sm float-right" role="group">
<button type="button" class="btn btn-danger" title="{{'uploader.delete.btn-title' | translate}}" (click)="uploader.clearQueue()" [disabled]="!uploader.queue.length"> <button type="button" class="btn btn-danger" title="{{'uploader.delete.btn-title' | translate}}" (click)="uploader.clearQueue()" [dsDisabled]="!uploader.queue.length">
<i class="fas fa-trash" aria-hidden="true"></i> <i class="fas fa-trash" aria-hidden="true"></i>
</button> </button>
</div> </div>

View File

@@ -43,6 +43,7 @@ import { createTestComponent } from '../../../shared/testing/utils.test';
import { SectionsService } from '../../sections/sections.service'; import { SectionsService } from '../../sections/sections.service';
import { SubmissionService } from '../../submission.service'; import { SubmissionService } from '../../submission.service';
import { SubmissionFormCollectionComponent } from './submission-form-collection.component'; import { SubmissionFormCollectionComponent } from './submission-form-collection.component';
import {DisabledDirective} from '../../../shared/disabled-directive';
describe('SubmissionFormCollectionComponent Component', () => { describe('SubmissionFormCollectionComponent Component', () => {
@@ -152,6 +153,7 @@ describe('SubmissionFormCollectionComponent Component', () => {
TranslateModule.forRoot(), TranslateModule.forRoot(),
SubmissionFormCollectionComponent, SubmissionFormCollectionComponent,
TestComponent, TestComponent,
DisabledDirective
], ],
providers: [ providers: [
{ provide: DSONameService, useValue: new DSONameServiceMock() }, { provide: DSONameService, useValue: new DSONameServiceMock() },
@@ -275,7 +277,8 @@ describe('SubmissionFormCollectionComponent Component', () => {
it('the dropdown button should be disabled when isReadonly is true', () => { it('the dropdown button should be disabled when isReadonly is true', () => {
comp.isReadonly = true; comp.isReadonly = true;
fixture.detectChanges(); fixture.detectChanges();
expect(dropdowBtn.nativeNode.attributes.disabled).toBeDefined(); expect(dropdowBtn.nativeNode.getAttribute('aria-disabled')).toBe('true');
expect(dropdowBtn.nativeNode.classList.contains('disabled')).toBeTrue();
}); });
it('should be simulated when the drop-down menu is closed', () => { it('should be simulated when the drop-down menu is closed', () => {

View File

@@ -31,6 +31,7 @@ import { SubmissionServiceStub } from '../../../shared/testing/submission-servic
import { createTestComponent } from '../../../shared/testing/utils.test'; import { createTestComponent } from '../../../shared/testing/utils.test';
import { SubmissionService } from '../../submission.service'; import { SubmissionService } from '../../submission.service';
import { SubmissionFormFooterComponent } from './submission-form-footer.component'; import { SubmissionFormFooterComponent } from './submission-form-footer.component';
import {DisabledDirective} from '../../../shared/disabled-directive';
const submissionServiceStub: SubmissionServiceStub = new SubmissionServiceStub(); const submissionServiceStub: SubmissionServiceStub = new SubmissionServiceStub();
@@ -51,6 +52,7 @@ describe('SubmissionFormFooterComponent', () => {
TranslateModule.forRoot(), TranslateModule.forRoot(),
SubmissionFormFooterComponent, SubmissionFormFooterComponent,
TestComponent, TestComponent,
DisabledDirective
], ],
providers: [ providers: [
{ provide: SubmissionService, useValue: submissionServiceStub }, { provide: SubmissionService, useValue: submissionServiceStub },
@@ -227,7 +229,8 @@ describe('SubmissionFormFooterComponent', () => {
fixture.detectChanges(); fixture.detectChanges();
const depositBtn: any = fixture.debugElement.query(By.css('.btn-success')); const depositBtn: any = fixture.debugElement.query(By.css('.btn-success'));
expect(depositBtn.nativeElement.disabled).toBeFalsy(); expect(depositBtn.nativeElement.getAttribute('aria-disabled')).toBe('false');
expect(depositBtn.nativeElement.classList.contains('disabled')).toBeFalse();
}); });
it('should not have deposit button disabled when submission is valid', () => { it('should not have deposit button disabled when submission is valid', () => {
@@ -236,7 +239,8 @@ describe('SubmissionFormFooterComponent', () => {
fixture.detectChanges(); fixture.detectChanges();
const depositBtn: any = fixture.debugElement.query(By.css('.btn-success')); const depositBtn: any = fixture.debugElement.query(By.css('.btn-success'));
expect(depositBtn.nativeElement.disabled).toBeFalsy(); expect(depositBtn.nativeElement.getAttribute('aria-disabled')).toBe('false');
expect(depositBtn.nativeElement.classList.contains('disabled')).toBeFalse();
}); });
it('should disable save button when all modifications had been saved', () => { it('should disable save button when all modifications had been saved', () => {
@@ -244,7 +248,8 @@ describe('SubmissionFormFooterComponent', () => {
fixture.detectChanges(); fixture.detectChanges();
const saveBtn: any = fixture.debugElement.query(By.css('#save')); const saveBtn: any = fixture.debugElement.query(By.css('#save'));
expect(saveBtn.nativeElement.disabled).toBeTruthy(); expect(saveBtn.nativeElement.getAttribute('aria-disabled')).toBe('true');
expect(saveBtn.nativeElement.classList.contains('disabled')).toBeTrue();
}); });
it('should enable save button when there are not saved modifications', () => { it('should enable save button when there are not saved modifications', () => {
@@ -252,7 +257,8 @@ describe('SubmissionFormFooterComponent', () => {
fixture.detectChanges(); fixture.detectChanges();
const saveBtn: any = fixture.debugElement.query(By.css('#save')); const saveBtn: any = fixture.debugElement.query(By.css('#save'));
expect(saveBtn.nativeElement.disabled).toBeFalsy(); expect(saveBtn.nativeElement.getAttribute('aria-disabled')).toBe('false');
expect(saveBtn.nativeElement.classList.contains('disabled')).toBeFalse();
}); });
}); });

View File

@@ -1,6 +1,6 @@
<div class="mb-4 ccLicense-select"> <div class="mb-4 ccLicense-select">
<ds-select <ds-select
[disabled]="!submissionCcLicenses"> [dsDisabled]="!submissionCcLicenses">
<ng-container class="selection"> <ng-container class="selection">
<span *ngIf="!submissionCcLicenses"> <span *ngIf="!submissionCcLicenses">
@@ -81,7 +81,7 @@
</ng-template> </ng-template>
<ds-select *ngIf="field.enums?.length > 5" [disabled]="field.id === 'jurisdiction' && defaultJurisdiction !== undefined && defaultJurisdiction !== 'none'"> <ds-select *ngIf="field.enums?.length > 5" [dsDisabled]="field.id === 'jurisdiction' && defaultJurisdiction !== undefined && defaultJurisdiction !== 'none'">
<ng-container class="selection" *ngVar="getSelectedOption(getSelectedCcLicense(), field) as option"> <ng-container class="selection" *ngVar="getSelectedOption(getSelectedCcLicense(), field) as option">
<span *ngIf="option"> <span *ngIf="option">
{{ option.label }} {{ option.label }}

View File

@@ -1,7 +1,7 @@
<div> <div>
<div class="modal-header"> <div class="modal-header">
<h4 class="modal-title">{{'submission.sections.upload.edit.title' | translate}}</h4> <h4 class="modal-title">{{'submission.sections.upload.edit.title' | translate}}</h4>
<button type="button" class="close" (click)="onModalClose()" aria-label="Close" [disabled]="isSaving"> <button type="button" class="close" (click)="onModalClose()" aria-label="Close" [dsDisabled]="isSaving">
<span aria-hidden="true">×</span> <span aria-hidden="true">×</span>
</button> </button>
</div> </div>

View File

@@ -35,7 +35,7 @@
<button class="btn btn-link-focus" <button class="btn btn-link-focus"
[attr.aria-label]="'submission.sections.upload.delete.confirm.title' | translate" [attr.aria-label]="'submission.sections.upload.delete.confirm.title' | translate"
title="{{ 'submission.sections.upload.delete.confirm.title' | translate }}" title="{{ 'submission.sections.upload.delete.confirm.title' | translate }}"
[disabled]="(processingDelete$ | async)" [dsDisabled]="(processingDelete$ | async)"
(click)="$event.preventDefault();confirmDelete(content);"> (click)="$event.preventDefault();confirmDelete(content);">
<i *ngIf="(processingDelete$ | async)" class="fas fa-circle-notch fa-spin fa-2x text-danger"></i> <i *ngIf="(processingDelete$ | async)" class="fas fa-circle-notch fa-spin fa-2x text-danger"></i>
<i *ngIf="(processingDelete$ | async) !== true" class="fa fa-trash fa-2x text-danger"></i> <i *ngIf="(processingDelete$ | async) !== true" class="fa fa-trash fa-2x text-danger"></i>

View File

@@ -459,6 +459,17 @@ ngb-accordion {
color: darken($danger, 22%); color: darken($danger, 22%);
} }
// An element that is disabled should not have focus styles to avoid confusion
// It however is still focusable for screen readers and keyboard navigation
.disabled {
pointer-events: none;
}
.disabled:focus {
outline: none;
box-shadow: none;
}
// Margin utility classes based on DSpace content spacing // Margin utility classes based on DSpace content spacing
.mt-cs { margin-top: var(--ds-content-spacing); } .mt-cs { margin-top: var(--ds-content-spacing); }
.mb-cs { margin-bottom: var(--ds-content-spacing); } .mb-cs { margin-bottom: var(--ds-content-spacing); }