117544: PR feedback

- added typedocs
- changed directive to only be present for buttons
- various other small fixes
This commit is contained in:
Jens Vannerum
2024-09-16 13:44:06 +02:00
parent a1f17dcf31
commit e7d050d3eb
23 changed files with 67 additions and 35 deletions

View File

@@ -12,7 +12,7 @@
</ng-template> </ng-template>
<ng-template ngbPanelContent> <ng-template ngbPanelContent>
<select id="collSel" name="collSel" class="form-control" multiple="multiple" size="10" formControlName="collections"> <select id="collSel" name="collSel" class="form-control" multiple="multiple" size="10" formControlName="collections">
<option *ngFor="let item of collections" [value]="item.id" [dsDisabled]="item.disabled">{{item.name$ | async}}</option> <option *ngFor="let item of collections" [value]="item.id" [disabled]="item.disabled">{{item.name$ | async}}</option>
</select> </select>
<div class="row"> <div class="row">
<span class="col-3"></span> <span class="col-3"></span>

View File

@@ -36,7 +36,7 @@
[authorityValue]="mdValue.newValue.confidence" [authorityValue]="mdValue.newValue.confidence"
[iconMode]="true" [iconMode]="true"
></i> ></i>
<input class="form-control form-outline" data-test="authority-input" [(ngModel)]="mdValue.newValue.authority" [dsDisabled]="!editingAuthority" <input class="form-control form-outline" data-test="authority-input" [(ngModel)]="mdValue.newValue.authority" [disabled]="!editingAuthority"
[attr.aria-label]="(dsoType + '.edit.metadata.edit.authority.key') | translate" [attr.aria-label]="(dsoType + '.edit.metadata.edit.authority.key') | translate"
(change)="onChangeAuthorityKey()" /> (change)="onChangeAuthorityKey()" />
<button class="btn btn-outline-secondary btn-sm ng-star-inserted" id="metadata-confirm-btn" *ngIf="!editingAuthority" <button class="btn btn-outline-secondary btn-sm ng-star-inserted" id="metadata-confirm-btn" *ngIf="!editingAuthority"

View File

@@ -462,8 +462,7 @@ describe('DsoEditMetadataValueComponent', () => {
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then(() => { fixture.whenStable().then(() => {
const inputElement = fixture.nativeElement.querySelector('input[data-test="authority-input"]'); const inputElement = fixture.nativeElement.querySelector('input[data-test="authority-input"]');
expect(inputElement.getAttribute('aria-disabled')).toBe('true'); expect(inputElement.disabled).toBeTruthy();
expect(inputElement.classList.contains('disabled')).toBeTrue();
done(); done();
}); });
}); });

View File

@@ -18,7 +18,7 @@
<div class="m-2" (click)="setSelected(typeDto.relationshipType, !selected)"> <div class="m-2" (click)="setSelected(typeDto.relationshipType, !selected)">
<label> <label>
<input type="checkbox" [checked]="selected" [dsDisabled]="isDeleting$ | async"> <input type="checkbox" [checked]="selected" [disabled]="isDeleting$ | async">
</label> </label>
</div> </div>

View File

@@ -13,7 +13,7 @@
</label> </label>
<select <select
id="accesscontroloption-{{control.id}}" id="accesscontroloption-{{control.id}}"
[dsDisabled]="ngForm.disabled" [disabled]="ngForm.disabled"
[(ngModel)]="control.itemName" [(ngModel)]="control.itemName"
(ngModelChange)="accessControlChanged(control, $event)" (ngModelChange)="accessControlChanged(control, $event)"
name="itemName-{{control.id}}" name="itemName-{{control.id}}"

View File

@@ -31,7 +31,7 @@
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="radio" <input class="form-check-input" type="radio"
name="itemMode" id="itemReplace" value="replace" name="itemMode" id="itemReplace" value="replace"
[dsDisabled]="!state.item.toggleStatus" [disabled]="!state.item.toggleStatus"
[(ngModel)]="state.item.accessMode"> [(ngModel)]="state.item.accessMode">
<label class="form-check-label" for="itemReplace"> <label class="form-check-label" for="itemReplace">
{{'access-control-replace-all' | translate}} {{'access-control-replace-all' | translate}}
@@ -40,7 +40,7 @@
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="radio" <input class="form-check-input" type="radio"
name="itemMode" id="itemAdd" value="add" name="itemMode" id="itemAdd" value="add"
[dsDisabled]="!state.item.toggleStatus" [disabled]="!state.item.toggleStatus"
[(ngModel)]="state.item.accessMode"> [(ngModel)]="state.item.accessMode">
<label class="form-check-label" for="itemAdd"> <label class="form-check-label" for="itemAdd">
{{'access-control-add-to-existing' | translate}} {{'access-control-add-to-existing' | translate}}
@@ -85,7 +85,7 @@
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="radio" <input class="form-check-input" type="radio"
name="changesLimit" id="processAll" value="all" name="changesLimit" id="processAll" value="all"
[dsDisabled]="!state.bitstream.toggleStatus" [disabled]="!state.bitstream.toggleStatus"
[(ngModel)]="state.bitstream.changesLimit"> [(ngModel)]="state.bitstream.changesLimit">
<label class="form-check-label" for="processAll"> <label class="form-check-label" for="processAll">
{{'access-control-process-all-bitstreams' | translate}} {{'access-control-process-all-bitstreams' | translate}}
@@ -94,7 +94,7 @@
<div class="form-check"> <div class="form-check">
<input class="form-check-input mt-2" type="radio" <input class="form-check-input mt-2" type="radio"
name="changesLimit" id="processSelected" value="selected" name="changesLimit" id="processSelected" value="selected"
[dsDisabled]="!state.bitstream.toggleStatus" [disabled]="!state.bitstream.toggleStatus"
[(ngModel)]="state.bitstream.changesLimit"> [(ngModel)]="state.bitstream.changesLimit">
<label class="form-check-label" for="processSelected"> <label class="form-check-label" for="processSelected">
{{ state.bitstream.selectedBitstreams.length }} {{ state.bitstream.selectedBitstreams.length }}
@@ -123,7 +123,7 @@
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="radio" <input class="form-check-input" type="radio"
name="bitstreamMode" id="bitstreamReplace" value="replace" name="bitstreamMode" id="bitstreamReplace" value="replace"
[dsDisabled]="!state.bitstream.toggleStatus" [disabled]="!state.bitstream.toggleStatus"
[(ngModel)]="state.bitstream.accessMode"> [(ngModel)]="state.bitstream.accessMode">
<label class="form-check-label" for="bitstreamReplace"> <label class="form-check-label" for="bitstreamReplace">
{{'access-control-replace-all' | translate}} {{'access-control-replace-all' | translate}}
@@ -132,7 +132,7 @@
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="radio" <input class="form-check-input" type="radio"
name="bitstreamMode" id="bitstreamAdd" value="add" name="bitstreamMode" id="bitstreamAdd" value="add"
[dsDisabled]="!state.bitstream.toggleStatus" [disabled]="!state.bitstream.toggleStatus"
[(ngModel)]="state.bitstream.accessMode"> [(ngModel)]="state.bitstream.accessMode">
<label class="form-check-label" for="bitstreamAdd"> <label class="form-check-label" for="bitstreamAdd">
{{'access-control-add-to-existing' | translate}} {{'access-control-add-to-existing' | translate}}

View File

@@ -48,6 +48,14 @@ describe('DisabledDirective', () => {
expect(button.nativeElement.classList.contains('disabled')).toBeTrue(); expect(button.nativeElement.classList.contains('disabled')).toBeTrue();
}); });
it('should bind aria-disabled to false and not have disabled class when isDisabled is false', () => {
component.isDisabled = false;
fixture.detectChanges();
expect(button.nativeElement.getAttribute('aria-disabled')).toBe('false');
expect(button.nativeElement.classList.contains('disabled')).toBeFalse();
});
it('should prevent click events when disabled', () => { it('should prevent click events when disabled', () => {
component.isDisabled = true; component.isDisabled = true;
fixture.detectChanges(); fixture.detectChanges();

View File

@@ -9,15 +9,36 @@ import {
selector: '[dsDisabled]', selector: '[dsDisabled]',
standalone: true, standalone: true,
}) })
/**
* This directive can be added to a html element to disable it (make it non-interactive).
* It acts as a replacement for HTML's disabled attribute.
*
* This directive should always be used instead of the HTML disabled attribute as it is more accessible.
*/
export class DisabledDirective { export class DisabledDirective {
@Input() set dsDisabled(value: boolean) { @Input() set dsDisabled(value: boolean) {
this.isDisabled = !!value; this.isDisabled = !!value;
} }
/**
* Binds the aria-disabled attribute to the directive's isDisabled property.
* This is used to make the element accessible to screen readers. If the element is disabled, the screen reader will announce it as such.
*/
@HostBinding('attr.aria-disabled') isDisabled = false; @HostBinding('attr.aria-disabled') isDisabled = false;
/**
* Binds the class attribute to the directive's isDisabled property.
* This is used to style the element when it is disabled (make it look disabled).
*/
@HostBinding('class.disabled') get disabledClass() { return this.isDisabled; } @HostBinding('class.disabled') get disabledClass() { return this.isDisabled; }
/**
* Prevents the default action and stops the event from propagating when the element is disabled.
* This is used to prevent the element from being interacted with when it is disabled (via mouse click).
* @param event The click event.
*/
@HostListener('click', ['$event']) @HostListener('click', ['$event'])
handleClick(event: Event) { handleClick(event: Event) {
if (this.isDisabled) { if (this.isDisabled) {
@@ -26,6 +47,11 @@ export class DisabledDirective {
} }
} }
/**
* Prevents the default action and stops the event from propagating when the element is disabled.
* This is used to prevent the element from being interacted with when it is disabled (via keystrokes).
* @param event The keydown event.
*/
@HostListener('keydown', ['$event']) @HostListener('keydown', ['$event'])
handleKeydown(event: KeyboardEvent) { handleKeydown(event: KeyboardEvent) {
if (this.isDisabled && (event.key === 'Enter' || event.key === 'Space')) { if (this.isDisabled && (event.key === 'Enter' || event.key === 'Space')) {

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"
[dsDisabled]="model.readOnly" [disabled]="model.readOnly"
[(ngModel)]="model.language" [(ngModel)]="model.language"
class="form-control" class="form-control"
(blur)="onBlur($event)" (blur)="onBlur($event)"

View File

@@ -6,7 +6,7 @@
<ds-number-picker <ds-number-picker
tabindex="0" tabindex="0"
[id]="model.id + '_year'" [id]="model.id + '_year'"
[dsDisabled]="model.disabled" [disabled]="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"
[dsDisabled]="!year || model.disabled" [disabled]="!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"
[dsDisabled]="!month || model.disabled" [disabled]="!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"
[dsDisabled]="model.disabled" [disabled]="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();
}); });
xit('should have a disabled input', () => { it('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"
[dsDisabled]="isInputDisabled()" [disabled]="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"
[dsDisabled]="firstInputValue.length === 0 || isInputDisabled()" [disabled]="firstInputValue.length === 0 || isInputDisabled()"
[placeholder]="model.secondPlaceholder | translate" [placeholder]="model.secondPlaceholder | translate"
[readonly]="model.readOnly" [readonly]="model.readOnly"
(change)="onChange($event)" (change)="onChange($event)"

View File

@@ -40,7 +40,7 @@
[ngbTypeahead]="search" [ngbTypeahead]="search"
[placeholder]="model.placeholder" [placeholder]="model.placeholder"
[readonly]="model.readOnly" [readonly]="model.readOnly"
[dsDisabled]="model.readOnly" [disabled]="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"
[dsDisabled]="model.readOnly" [disabled]="model.readOnly"
[type]="model.inputType" [type]="model.inputType"
[value]="currentValue?.display" [value]="currentValue?.display"
(focus)="onFocus($event)" (focus)="onFocus($event)"

View File

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

View File

@@ -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"
[dsDisabled]="disabled" [disabled]="disabled"
[ngClass]="{'is-invalid': invalid}" [ngClass]="{'is-invalid': invalid}"
title="{{placeholder}}" title="{{placeholder}}"
[attr.aria-label]="placeholder" [attr.aria-label]="placeholder"

View File

@@ -39,7 +39,7 @@
container="body" container="body"
> >
<input class="mr-2" type="checkbox" <input class="mr-2" type="checkbox"
[dsDisabled]="!node.item?.selectable" [disabled]="!node.item?.selectable"
[(ngModel)]="node.isSelected" [(ngModel)]="node.isSelected"
[checked]="node.isSelected" [checked]="node.isSelected"
(change)="onSelect(node.item)" (change)="onSelect(node.item)"
@@ -71,7 +71,7 @@
[openDelay]="500" [openDelay]="500"
container="body"> container="body">
<input class="mr-2" type="checkbox" <input class="mr-2" type="checkbox"
[dsDisabled]="!node.item?.selectable" [disabled]="!node.item?.selectable"
[(ngModel)]="node.isSelected" [(ngModel)]="node.isSelected"
[checked]="node.isSelected" [checked]="node.isSelected"
(change)="onSelect(node.item)" (change)="onSelect(node.item)"

View File

@@ -16,7 +16,7 @@
</thead> </thead>
<tbody> <tbody>
<tr *ngFor="let selectCollection of selectCollections$ | async"> <tr *ngFor="let selectCollection of selectCollections$ | async">
<td><input #selectCollectionBtn [attr.aria-label]="(selectCollectionBtn.checked ? 'collection.select.table.deselect' : 'collection.select.table.select') | translate" [dsDisabled]="(selectCollection.canSelect$ | async) === false" class="collection-checkbox" [ngModel]="selectCollection.selected$ | async" (change)="switch(selectCollection.dso.id)" type="checkbox" name="{{selectCollection.dso.id}}"></td> <td><input #selectCollectionBtn [attr.aria-label]="(selectCollectionBtn.checked ? 'collection.select.table.deselect' : 'collection.select.table.select') | translate" [disabled]="(selectCollection.canSelect$ | async) === false" class="collection-checkbox" [ngModel]="selectCollection.selected$ | async" (change)="switch(selectCollection.dso.id)" type="checkbox" name="{{selectCollection.dso.id}}"></td>
<td><a [routerLink]="selectCollection.route">{{ dsoNameService.getName(selectCollection.dso) }}</a></td> <td><a [routerLink]="selectCollection.route">{{ dsoNameService.getName(selectCollection.dso) }}</a></td>
</tr> </tr>
</tbody> </tbody>

View File

@@ -18,7 +18,7 @@
</thead> </thead>
<tbody> <tbody>
<tr *ngFor="let selectItem of selectItems$ | async"> <tr *ngFor="let selectItem of selectItems$ | async">
<td><input #selectItemBtn [attr.aria-label]="(selectItemBtn.checked ? 'item.select.table.deselect' : 'item.select.table.select') | translate" [dsDisabled]="(selectItem.canSelect$ | async) === false" class="item-checkbox" [ngModel]="selectItem.selected$ | async" (change)="switch(selectItem.dso.id)" type="checkbox" name="{{selectItem.dso.id}}"></td> <td><input #selectItemBtn [attr.aria-label]="(selectItemBtn.checked ? 'item.select.table.deselect' : 'item.select.table.select') | translate" [disabled]="(selectItem.canSelect$ | async) === false" class="item-checkbox" [ngModel]="selectItem.selected$ | async" (change)="switch(selectItem.dso.id)" type="checkbox" name="{{selectItem.dso.id}}"></td>
<td *ngIf="!hideCollection"> <td *ngIf="!hideCollection">
<span *ngVar="(selectItem.dso.owningCollection | async)?.payload as collection"> <span *ngVar="(selectItem.dso.owningCollection | async)?.payload as collection">
<a *ngIf="collection" [routerLink]="['/collections', collection?.id]"> <a *ngIf="collection" [routerLink]="['/collections', collection?.id]">

View File

@@ -197,8 +197,7 @@ 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.getAttribute('aria-disabled')).toBe('true'); expect(checkbox.disabled).toBeTrue();
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"
[dsDisabled]="paginationOptions.disabled" [disabled]="paginationOptions.disabled"
[ellipses]="paginationOptions.ellipses" [ellipses]="paginationOptions.ellipses"
[maxSize]="(isXs)?5:paginationOptions.maxSize" [maxSize]="(isXs)?5:paginationOptions.maxSize"
[page]="(currentPage$|async)" [page]="(currentPage$|async)"

View File

@@ -1,6 +1,6 @@
<div class="mb-4 ccLicense-select"> <div class="mb-4 ccLicense-select">
<ds-select <ds-select
[dsDisabled]="!submissionCcLicenses"> [disabled]="!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" [dsDisabled]="field.id === 'jurisdiction' && defaultJurisdiction !== undefined && defaultJurisdiction !== 'none'"> <ds-select *ngIf="field.enums?.length > 5" [disabled]="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

@@ -7,7 +7,7 @@
type="checkbox" type="checkbox"
class="custom-control-input" class="custom-control-input"
id="primaryBitstream{{fileIndex}}" id="primaryBitstream{{fileIndex}}"
[dsDisabled]="processingSaveStatus$ | async" [disabled]="processingSaveStatus$ | async"
[checked]="isPrimary" [checked]="isPrimary"
(change)="togglePrimaryBitstream($event)"> (change)="togglePrimaryBitstream($event)">
<label class="custom-control-label" for="primaryBitstream{{fileIndex}}"> <label class="custom-control-label" for="primaryBitstream{{fileIndex}}">