diff --git a/.eslintrc.json b/.eslintrc.json index 5fb4c121717..888c968b5c6 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -293,7 +293,8 @@ ], "rules": { // Custom DSpace Angular rules - "dspace-angular-html/themed-component-usages": "error" + "dspace-angular-html/themed-component-usages": "error", + "dspace-angular-html/no-disabled-attribute-on-button": "error" } }, { diff --git a/docs/lint/html/index.md b/docs/lint/html/index.md index 15d693843c0..e134e1070f4 100644 --- a/docs/lint/html/index.md +++ b/docs/lint/html/index.md @@ -2,3 +2,4 @@ _______ - [`dspace-angular-html/themed-component-usages`](./rules/themed-component-usages.md): Themeable components should be used via the selector of their `ThemedComponent` wrapper class +- [`dspace-angular-html/no-disabled-attribute-on-button`](./rules/no-disabled-attribute-on-button.md): Buttons should use the `dsBtnDisabled` directive instead of the HTML `disabled` attribute. diff --git a/docs/lint/html/rules/no-disabled-attribute-on-button.md b/docs/lint/html/rules/no-disabled-attribute-on-button.md new file mode 100644 index 00000000000..d9d39ce82ca --- /dev/null +++ b/docs/lint/html/rules/no-disabled-attribute-on-button.md @@ -0,0 +1,78 @@ +[DSpace ESLint plugins](../../../../lint/README.md) > [HTML rules](../index.md) > `dspace-angular-html/no-disabled-attribute-on-button` +_______ + +Buttons should use the `dsBtnDisabled` directive instead of the HTML `disabled` attribute. + This should be done to ensure that users with a screen reader are able to understand that the a button button is present, and that it is disabled. + The native html disabled attribute does not allow users to navigate to the button by keyboard, and thus they have no way of knowing that the button is present. + +_______ + +[Source code](../../../../lint/src/rules/html/no-disabled-attribute-on-button.ts) + +### Examples + + +#### Valid code + +##### should use [dsBtnDisabled] in HTML templates + +```html + +``` + +##### disabled attribute is still valid on non-button elements + +```html + +``` + +##### [disabled] attribute is still valid on non-button elements + +```html + +``` + +##### angular dynamic attributes that use disabled are still valid + +```html + +``` + + + + +#### Invalid code & automatic fixes + +##### should not use disabled attribute in HTML templates + +```html + +``` +Will produce the following error(s): +``` +Buttons should use the `dsBtnDisabled` directive instead of the `disabled` attribute. +``` + +Result of `yarn lint --fix`: +```html + +``` + + +##### should not use [disabled] attribute in HTML templates + +```html + +``` +Will produce the following error(s): +``` +Buttons should use the `dsBtnDisabled` directive instead of the `disabled` attribute. +``` + +Result of `yarn lint --fix`: +```html + +``` + + + diff --git a/lint/src/rules/html/index.ts b/lint/src/rules/html/index.ts index 7c1370ae2d4..3d425c3ad48 100644 --- a/lint/src/rules/html/index.ts +++ b/lint/src/rules/html/index.ts @@ -10,10 +10,13 @@ import { bundle, RuleExports, } from '../../util/structure'; +import * as noDisabledAttributeOnButton from './no-disabled-attribute-on-button'; import * as themedComponentUsages from './themed-component-usages'; const index = [ themedComponentUsages, + noDisabledAttributeOnButton, + ] as unknown as RuleExports[]; export = { diff --git a/lint/src/rules/html/no-disabled-attribute-on-button.ts b/lint/src/rules/html/no-disabled-attribute-on-button.ts new file mode 100644 index 00000000000..bf1a72d70d0 --- /dev/null +++ b/lint/src/rules/html/no-disabled-attribute-on-button.ts @@ -0,0 +1,147 @@ +import { + TmplAstBoundAttribute, + TmplAstTextAttribute, +} from '@angular-eslint/bundled-angular-compiler'; +import { TemplateParserServices } from '@angular-eslint/utils'; +import { + ESLintUtils, + TSESLint, +} from '@typescript-eslint/utils'; + +import { + DSpaceESLintRuleInfo, + NamedTests, +} from '../../util/structure'; +import { getSourceCode } from '../../util/typescript'; + +export enum Message { + USE_DSBTN_DISABLED = 'mustUseDsBtnDisabled', +} + +export const info = { + name: 'no-disabled-attribute-on-button', + meta: { + docs: { + description: `Buttons should use the \`dsBtnDisabled\` directive instead of the HTML \`disabled\` attribute. + This should be done to ensure that users with a screen reader are able to understand that the a button button is present, and that it is disabled. + The native html disabled attribute does not allow users to navigate to the button by keyboard, and thus they have no way of knowing that the button is present.`, + }, + type: 'problem', + fixable: 'code', + schema: [], + messages: { + [Message.USE_DSBTN_DISABLED]: 'Buttons should use the `dsBtnDisabled` directive instead of the `disabled` attribute.', + }, + }, + defaultOptions: [], +} as DSpaceESLintRuleInfo; + +export const rule = ESLintUtils.RuleCreator.withoutDocs({ + ...info, + create(context: TSESLint.RuleContext) { + const parserServices = getSourceCode(context).parserServices as TemplateParserServices; + + /** + * Some dynamic angular inputs will have disabled as name because of how Angular handles this internally (e.g [class.disabled]="isDisabled") + * But these aren't actually the disabled attribute we're looking for, we can determine this by checking the details of the keySpan + */ + function isOtherAttributeDisabled(node: TmplAstBoundAttribute | TmplAstTextAttribute): boolean { + // if the details are not null, and the details are not 'disabled', then it's not the disabled attribute we're looking for + return node.keySpan?.details !== null && node.keySpan?.details !== 'disabled'; + } + + /** + * Replace the disabled text with [dsBtnDisabled] in the template + */ + function replaceDisabledText(text: string ): string { + const hasBrackets = text.includes('[') && text.includes(']'); + const newDisabledText = hasBrackets ? 'dsBtnDisabled' : '[dsBtnDisabled]="true"'; + return text.replace('disabled', newDisabledText); + } + + function inputIsChildOfButton(node: any): boolean { + return (node.parent?.tagName === 'button' || node.parent?.name === 'button'); + } + + function reportAndFix(node: TmplAstBoundAttribute | TmplAstTextAttribute) { + if (!inputIsChildOfButton(node) || isOtherAttributeDisabled(node)) { + return; + } + + const sourceSpan = node.sourceSpan; + context.report({ + messageId: Message.USE_DSBTN_DISABLED, + loc: parserServices.convertNodeSourceSpanToLoc(sourceSpan), + fix(fixer) { + const templateText = sourceSpan.start.file.content; + const disabledText = templateText.slice(sourceSpan.start.offset, sourceSpan.end.offset); + const newText = replaceDisabledText(disabledText); + return fixer.replaceTextRange([sourceSpan.start.offset, sourceSpan.end.offset], newText); + }, + }); + } + + return { + 'BoundAttribute[name="disabled"]'(node: TmplAstBoundAttribute) { + reportAndFix(node); + }, + 'TextAttribute[name="disabled"]'(node: TmplAstTextAttribute) { + reportAndFix(node); + }, + }; + }, +}); + +export const tests = { + plugin: info.name, + valid: [ + { + name: 'should use [dsBtnDisabled] in HTML templates', + code: ` + + `, + }, + { + name: 'disabled attribute is still valid on non-button elements', + code: ` + + `, + }, + { + name: '[disabled] attribute is still valid on non-button elements', + code: ` + + `, + }, + { + name: 'angular dynamic attributes that use disabled are still valid', + code: ` + + `, + }, + ], + invalid: [ + { + name: 'should not use disabled attribute in HTML templates', + code: ` + + `, + errors: [{ messageId: Message.USE_DSBTN_DISABLED }], + output: ` + + `, + }, + { + name: 'should not use [disabled] attribute in HTML templates', + code: ` + + `, + errors: [{ messageId: Message.USE_DSBTN_DISABLED }], + output: ` + + `, + }, + ], +} as NamedTests; + +export default rule; diff --git a/src/app/access-control/bulk-access/bulk-access.component.html b/src/app/access-control/bulk-access/bulk-access.component.html index c164cc5c319..cda6b805bcc 100644 --- a/src/app/access-control/bulk-access/bulk-access.component.html +++ b/src/app/access-control/bulk-access/bulk-access.component.html @@ -10,7 +10,7 @@

{{ 'admin.access-control.bulk-access.title' | translate }}

- diff --git a/src/app/access-control/bulk-access/bulk-access.component.ts b/src/app/access-control/bulk-access/bulk-access.component.ts index bd8e893b599..a1608d27d07 100644 --- a/src/app/access-control/bulk-access/bulk-access.component.ts +++ b/src/app/access-control/bulk-access/bulk-access.component.ts @@ -14,6 +14,7 @@ import { } from 'rxjs/operators'; import { BulkAccessControlService } from '../../shared/access-control-form-container/bulk-access-control.service'; +import { BtnDisabledDirective } from '../../shared/btn-disabled.directive'; import { SelectableListState } from '../../shared/object-list/selectable-list/selectable-list.reducer'; import { SelectableListService } from '../../shared/object-list/selectable-list/selectable-list.service'; import { BulkAccessBrowseComponent } from './browse/bulk-access-browse.component'; @@ -27,6 +28,7 @@ import { BulkAccessSettingsComponent } from './settings/bulk-access-settings.com TranslateModule, BulkAccessSettingsComponent, BulkAccessBrowseComponent, + BtnDisabledDirective, ], standalone: true, }) diff --git a/src/app/access-control/epeople-registry/epeople-registry.component.spec.ts b/src/app/access-control/epeople-registry/epeople-registry.component.spec.ts index c636b72d561..cd7441022cb 100644 --- a/src/app/access-control/epeople-registry/epeople-registry.component.spec.ts +++ b/src/app/access-control/epeople-registry/epeople-registry.component.spec.ts @@ -42,6 +42,7 @@ import { EPersonDataService } from '../../core/eperson/eperson-data.service'; import { EPerson } from '../../core/eperson/models/eperson.model'; import { PaginationService } from '../../core/pagination/pagination.service'; import { PageInfo } from '../../core/shared/page-info.model'; +import { BtnDisabledDirective } from '../../shared/btn-disabled.directive'; import { FormBuilderService } from '../../shared/form/builder/form-builder.service'; import { ThemedLoadingComponent } from '../../shared/loading/themed-loading.component'; import { getMockFormBuilderService } from '../../shared/mocks/form-builder-service.mock'; @@ -151,7 +152,7 @@ describe('EPeopleRegistryComponent', () => { paginationService = new PaginationServiceStub(); TestBed.configureTestingModule({ imports: [CommonModule, NgbModule, FormsModule, ReactiveFormsModule, BrowserModule, RouterTestingModule.withRoutes([]), - TranslateModule.forRoot(), EPeopleRegistryComponent], + TranslateModule.forRoot(), EPeopleRegistryComponent, BtnDisabledDirective], providers: [ { provide: EPersonDataService, useValue: ePersonDataServiceStub }, { provide: NotificationsService, useValue: new NotificationsServiceStub() }, diff --git a/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.html b/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.html index ae1046be452..3aa4d66b051 100644 --- a/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.html +++ b/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.html @@ -25,7 +25,7 @@

{{messagePrefix + '.edit' | translate}}

-
diff --git a/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts b/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts index e61f95d6e5c..c5c94073777 100644 --- a/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts +++ b/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts @@ -43,6 +43,7 @@ import { GroupDataService } from '../../../core/eperson/group-data.service'; import { EPerson } from '../../../core/eperson/models/eperson.model'; import { PaginationService } from '../../../core/pagination/pagination.service'; import { PageInfo } from '../../../core/shared/page-info.model'; +import { BtnDisabledDirective } from '../../../shared/btn-disabled.directive'; import { FormBuilderService } from '../../../shared/form/builder/form-builder.service'; import { FormComponent } from '../../../shared/form/form.component'; import { ThemedLoadingComponent } from '../../../shared/loading/themed-loading.component'; @@ -221,7 +222,7 @@ describe('EPersonFormComponent', () => { route = new ActivatedRouteStub(); router = new RouterStub(); TestBed.configureTestingModule({ - imports: [CommonModule, NgbModule, FormsModule, ReactiveFormsModule, BrowserModule, + imports: [CommonModule, NgbModule, FormsModule, ReactiveFormsModule, BtnDisabledDirective, BrowserModule, RouterModule.forRoot([]), TranslateModule.forRoot(), EPersonFormComponent, @@ -516,7 +517,8 @@ describe('EPersonFormComponent', () => { // ePersonDataServiceStub.activeEPerson = eperson; spyOn(component.epersonService, 'deleteEPerson').and.returnValue(createSuccessfulRemoteDataObject$('No Content', 204)); const deleteButton = fixture.debugElement.query(By.css('.delete-button')); - expect(deleteButton.nativeElement.disabled).toBe(false); + expect(deleteButton.nativeElement.getAttribute('aria-disabled')).toBeNull(); + expect(deleteButton.nativeElement.classList.contains('disabled')).toBeFalse(); deleteButton.triggerEventHandler('click', null); fixture.detectChanges(); expect(component.epersonService.deleteEPerson).toHaveBeenCalledWith(eperson); diff --git a/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts b/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts index 942481c4fd9..c7b57c986d3 100644 --- a/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts +++ b/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts @@ -65,6 +65,7 @@ import { import { PageInfo } from '../../../core/shared/page-info.model'; import { Registration } from '../../../core/shared/registration.model'; import { TYPE_REQUEST_FORGOT } from '../../../register-email-form/register-email-form.component'; +import { BtnDisabledDirective } from '../../../shared/btn-disabled.directive'; import { ConfirmationModalComponent } from '../../../shared/confirmation-modal/confirmation-modal.component'; import { hasValue } from '../../../shared/empty.util'; import { FormBuilderService } from '../../../shared/form/builder/form-builder.service'; @@ -92,6 +93,7 @@ import { ValidateEmailNotTaken } from './validators/email-taken.validator'; PaginationComponent, RouterLink, HasNoValuePipe, + BtnDisabledDirective, ], standalone: true, }) diff --git a/src/app/access-control/group-registry/group-form/members-list/members-list.component.html b/src/app/access-control/group-registry/group-form/members-list/members-list.component.html index 591d80bdd59..d289d036bb4 100644 --- a/src/app/access-control/group-registry/group-form/members-list/members-list.component.html +++ b/src/app/access-control/group-registry/group-form/members-list/members-list.component.html @@ -35,14 +35,14 @@

{{messagePrefix + '.headMembers' | translate}}

  - +
@@ -158,8 +158,8 @@

{{'admin.reports.items.head' | transl {{'admin.reports.commons.page' | translate}} {{ currentPage + 1 }} {{'admin.reports.commons.of' | translate}} {{ pageCount() }}
- - + + diff --git a/src/app/admin/admin-reports/filtered-items/filtered-items.component.ts b/src/app/admin/admin-reports/filtered-items/filtered-items.component.ts index 4cb93aa9be7..04ee4894ec9 100644 --- a/src/app/admin/admin-reports/filtered-items/filtered-items.component.ts +++ b/src/app/admin/admin-reports/filtered-items/filtered-items.component.ts @@ -43,6 +43,7 @@ import { getFirstSucceededRemoteListPayload } from 'src/app/core/shared/operator import { isEmpty } from 'src/app/shared/empty.util'; import { environment } from 'src/environments/environment'; +import { BtnDisabledDirective } from '../../../shared/btn-disabled.directive'; import { FiltersComponent } from '../filters-section/filters-section.component'; import { FilteredItems } from './filtered-items-model'; import { OptionVO } from './option-vo.model'; @@ -64,6 +65,7 @@ import { QueryPredicate } from './query-predicate.model'; NgIf, NgForOf, FiltersComponent, + BtnDisabledDirective, ], standalone: true, }) diff --git a/src/app/collection-page/delete-collection-page/delete-collection-page.component.html b/src/app/collection-page/delete-collection-page/delete-collection-page.component.html index 53c3b45cfab..0cdba00f039 100644 --- a/src/app/collection-page/delete-collection-page/delete-collection-page.component.html +++ b/src/app/collection-page/delete-collection-page/delete-collection-page.component.html @@ -6,10 +6,10 @@

{{ 'collection.delete.head' | transla

{{ 'collection.delete.text' | translate:{ dso: dsoNameService.getName(dso) } }}

- - diff --git a/src/app/collection-page/delete-collection-page/delete-collection-page.component.ts b/src/app/collection-page/delete-collection-page/delete-collection-page.component.ts index dbb5f8846f1..acc716b52a7 100644 --- a/src/app/collection-page/delete-collection-page/delete-collection-page.component.ts +++ b/src/app/collection-page/delete-collection-page/delete-collection-page.component.ts @@ -15,6 +15,7 @@ import { import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; import { CollectionDataService } from '../../core/data/collection-data.service'; import { Collection } from '../../core/shared/collection.model'; +import { BtnDisabledDirective } from '../../shared/btn-disabled.directive'; import { DeleteComColPageComponent } from '../../shared/comcol/comcol-forms/delete-comcol-page/delete-comcol-page.component'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { VarDirective } from '../../shared/utils/var.directive'; @@ -31,6 +32,7 @@ import { VarDirective } from '../../shared/utils/var.directive'; AsyncPipe, NgIf, VarDirective, + BtnDisabledDirective, ], standalone: true, }) diff --git a/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.html b/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.html index 7edaadb0a1d..1e09758bd10 100644 --- a/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.html +++ b/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.html @@ -19,32 +19,32 @@

{{ 'collection.source.controls.head' | translate }}

diff --git a/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.spec.ts b/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.spec.ts index 0caf9b0418c..cbe3de14834 100644 --- a/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.spec.ts +++ b/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.spec.ts @@ -22,6 +22,7 @@ import { Collection } from '../../../../core/shared/collection.model'; import { ContentSource } from '../../../../core/shared/content-source.model'; import { ContentSourceSetSerializer } from '../../../../core/shared/content-source-set-serializer'; import { Process } from '../../../../process-page/processes/process.model'; +import { BtnDisabledDirective } from '../../../../shared/btn-disabled.directive'; import { NotificationsService } from '../../../../shared/notifications/notifications.service'; import { createSuccessfulRemoteDataObject$ } from '../../../../shared/remote-data.utils'; import { NotificationsServiceStub } from '../../../../shared/testing/notifications-service.stub'; @@ -104,7 +105,7 @@ describe('CollectionSourceControlsComponent', () => { requestService = jasmine.createSpyObj('requestService', ['removeByHrefSubstring', 'setStaleByHrefSubstring']); TestBed.configureTestingModule({ - imports: [TranslateModule.forRoot(), RouterTestingModule, CollectionSourceControlsComponent, VarDirective], + imports: [TranslateModule.forRoot(), RouterTestingModule, CollectionSourceControlsComponent, VarDirective, BtnDisabledDirective], providers: [ { provide: ScriptDataService, useValue: scriptDataService }, { provide: ProcessDataService, useValue: processDataService }, @@ -193,9 +194,10 @@ describe('CollectionSourceControlsComponent', () => { const buttons = fixture.debugElement.queryAll(By.css('button')); - expect(buttons[0].nativeElement.disabled).toBeTrue(); - expect(buttons[1].nativeElement.disabled).toBeTrue(); - expect(buttons[2].nativeElement.disabled).toBeTrue(); + buttons.forEach(button => { + expect(button.nativeElement.getAttribute('aria-disabled')).toBe('true'); + expect(button.nativeElement.classList.contains('disabled')).toBeTrue(); + }); }); it('should be enabled when isEnabled is true', () => { comp.shouldShow = true; @@ -205,9 +207,10 @@ describe('CollectionSourceControlsComponent', () => { const buttons = fixture.debugElement.queryAll(By.css('button')); - expect(buttons[0].nativeElement.disabled).toBeFalse(); - expect(buttons[1].nativeElement.disabled).toBeFalse(); - expect(buttons[2].nativeElement.disabled).toBeFalse(); + buttons.forEach(button => { + expect(button.nativeElement.getAttribute('aria-disabled')).toBe('false'); + expect(button.nativeElement.classList.contains('disabled')).toBeFalse(); + }); }); it('should call the corresponding button when clicked', () => { spyOn(comp, 'testConfiguration'); diff --git a/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.ts b/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.ts index fdd65a43c71..e35a64af16d 100644 --- a/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.ts +++ b/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.ts @@ -40,6 +40,7 @@ import { } from '../../../../core/shared/operators'; import { Process } from '../../../../process-page/processes/process.model'; import { ProcessStatus } from '../../../../process-page/processes/process-status.model'; +import { BtnDisabledDirective } from '../../../../shared/btn-disabled.directive'; import { hasValue } from '../../../../shared/empty.util'; import { NotificationsService } from '../../../../shared/notifications/notifications.service'; import { VarDirective } from '../../../../shared/utils/var.directive'; @@ -56,6 +57,7 @@ import { VarDirective } from '../../../../shared/utils/var.directive'; AsyncPipe, NgIf, VarDirective, + BtnDisabledDirective, ], standalone: true, }) diff --git a/src/app/collection-page/edit-collection-page/collection-source/collection-source.component.html b/src/app/collection-page/edit-collection-page/collection-source/collection-source.component.html index 8f807c1aee8..7aa1f1a8b78 100644 --- a/src/app/collection-page/edit-collection-page/collection-source/collection-source.component.html +++ b/src/app/collection-page/edit-collection-page/collection-source/collection-source.component.html @@ -1,7 +1,7 @@
- diff --git a/src/app/community-page/delete-community-page/delete-community-page.component.ts b/src/app/community-page/delete-community-page/delete-community-page.component.ts index f35e2d6bd23..9c19a5eb472 100644 --- a/src/app/community-page/delete-community-page/delete-community-page.component.ts +++ b/src/app/community-page/delete-community-page/delete-community-page.component.ts @@ -15,6 +15,7 @@ import { import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; import { CommunityDataService } from '../../core/data/community-data.service'; import { Community } from '../../core/shared/community.model'; +import { BtnDisabledDirective } from '../../shared/btn-disabled.directive'; import { DeleteComColPageComponent } from '../../shared/comcol/comcol-forms/delete-comcol-page/delete-comcol-page.component'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { VarDirective } from '../../shared/utils/var.directive'; @@ -31,6 +32,7 @@ import { VarDirective } from '../../shared/utils/var.directive'; AsyncPipe, VarDirective, NgIf, + BtnDisabledDirective, ], standalone: true, }) diff --git a/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value/dso-edit-metadata-value.component.html b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value/dso-edit-metadata-value.component.html index f3bbef3a4d9..22627826874 100644 --- a/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value/dso-edit-metadata-value.component.html +++ b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value/dso-edit-metadata-value.component.html @@ -77,29 +77,29 @@ - - @@ -77,13 +77,13 @@
- - diff --git a/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata.component.spec.ts b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata.component.spec.ts index 32e57e003d2..cbe2c187924 100644 --- a/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata.component.spec.ts +++ b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata.component.spec.ts @@ -22,6 +22,7 @@ import { Item } from '../../core/shared/item.model'; import { ITEM } from '../../core/shared/item.resource-type'; import { MetadataValue } from '../../core/shared/metadata.models'; import { AlertComponent } from '../../shared/alert/alert.component'; +import { BtnDisabledDirective } from '../../shared/btn-disabled.directive'; import { ThemedLoadingComponent } from '../../shared/loading/themed-loading.component'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { TestDataService } from '../../shared/testing/test-data-service.mock'; @@ -94,6 +95,7 @@ describe('DsoEditMetadataComponent', () => { RouterTestingModule.withRoutes([]), DsoEditMetadataComponent, VarDirective, + BtnDisabledDirective, ], providers: [ { provide: APP_DATA_SERVICES_MAP, useValue: mockDataServiceMap }, @@ -216,7 +218,13 @@ describe('DsoEditMetadataComponent', () => { }); 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 { it('should not exist', () => { diff --git a/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata.component.ts b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata.component.ts index 234a8539c4b..4a40e3c26a1 100644 --- a/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata.component.ts +++ b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata.component.ts @@ -47,6 +47,7 @@ import { getFirstCompletedRemoteData } from '../../core/shared/operators'; import { ResourceType } from '../../core/shared/resource-type'; import { AlertComponent } from '../../shared/alert/alert.component'; import { AlertType } from '../../shared/alert/alert-type'; +import { BtnDisabledDirective } from '../../shared/btn-disabled.directive'; import { hasNoValue, hasValue, @@ -66,7 +67,7 @@ import { MetadataFieldSelectorComponent } from './metadata-field-selector/metada styleUrls: ['./dso-edit-metadata.component.scss'], templateUrl: './dso-edit-metadata.component.html', standalone: true, - imports: [NgIf, DsoEditMetadataHeadersComponent, MetadataFieldSelectorComponent, DsoEditMetadataValueHeadersComponent, DsoEditMetadataValueComponent, NgFor, DsoEditMetadataFieldValuesComponent, AlertComponent, ThemedLoadingComponent, AsyncPipe, TranslateModule], + imports: [NgIf, DsoEditMetadataHeadersComponent, MetadataFieldSelectorComponent, DsoEditMetadataValueHeadersComponent, DsoEditMetadataValueComponent, NgFor, DsoEditMetadataFieldValuesComponent, AlertComponent, ThemedLoadingComponent, AsyncPipe, TranslateModule, BtnDisabledDirective], }) /** * Component showing a table of all metadata on a DSpaceObject and options to modify them diff --git a/src/app/forgot-password/forgot-password-form/forgot-password-form.component.html b/src/app/forgot-password/forgot-password-form/forgot-password-form.component.html index 189d0bee40c..6cff2903b94 100644 --- a/src/app/forgot-password/forgot-password-form/forgot-password-form.component.html +++ b/src/app/forgot-password/forgot-password-form/forgot-password-form.component.html @@ -28,7 +28,7 @@

{{'forgot-password.form.head' | translate}}

diff --git a/src/app/forgot-password/forgot-password-form/forgot-password-form.component.ts b/src/app/forgot-password/forgot-password-form/forgot-password-form.component.ts index 7b2b1beedd6..68f0f9d203c 100644 --- a/src/app/forgot-password/forgot-password-form/forgot-password-form.component.ts +++ b/src/app/forgot-password/forgot-password-form/forgot-password-form.component.ts @@ -29,6 +29,7 @@ import { } from '../../core/shared/operators'; import { Registration } from '../../core/shared/registration.model'; import { ProfilePageSecurityFormComponent } from '../../profile-page/profile-page-security-form/profile-page-security-form.component'; +import { BtnDisabledDirective } from '../../shared/btn-disabled.directive'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { BrowserOnlyPipe } from '../../shared/utils/browser-only.pipe'; @@ -42,6 +43,7 @@ import { BrowserOnlyPipe } from '../../shared/utils/browser-only.pipe'; ProfilePageSecurityFormComponent, AsyncPipe, NgIf, + BtnDisabledDirective, ], standalone: true, }) diff --git a/src/app/info/end-user-agreement/end-user-agreement.component.html b/src/app/info/end-user-agreement/end-user-agreement.component.html index 2ab0005c69f..ceb2ad23a4c 100644 --- a/src/app/info/end-user-agreement/end-user-agreement.component.html +++ b/src/app/info/end-user-agreement/end-user-agreement.component.html @@ -7,7 +7,7 @@
- +
diff --git a/src/app/info/end-user-agreement/end-user-agreement.component.spec.ts b/src/app/info/end-user-agreement/end-user-agreement.component.spec.ts index fbb5ebc49b2..88cb46e3777 100644 --- a/src/app/info/end-user-agreement/end-user-agreement.component.spec.ts +++ b/src/app/info/end-user-agreement/end-user-agreement.component.spec.ts @@ -16,6 +16,7 @@ import { of as observableOf } from 'rxjs'; import { LogOutAction } from '../../core/auth/auth.actions'; import { AuthService } from '../../core/auth/auth.service'; import { EndUserAgreementService } from '../../core/end-user-agreement/end-user-agreement.service'; +import { BtnDisabledDirective } from '../../shared/btn-disabled.directive'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { ActivatedRouteStub } from '../../shared/testing/active-router.stub'; import { EndUserAgreementComponent } from './end-user-agreement.component'; @@ -57,7 +58,7 @@ describe('EndUserAgreementComponent', () => { beforeEach(waitForAsync(() => { init(); TestBed.configureTestingModule({ - imports: [TranslateModule.forRoot(), EndUserAgreementComponent], + imports: [TranslateModule.forRoot(), EndUserAgreementComponent, BtnDisabledDirective], providers: [ { provide: EndUserAgreementService, useValue: endUserAgreementService }, { provide: NotificationsService, useValue: notificationsService }, @@ -95,7 +96,8 @@ describe('EndUserAgreementComponent', () => { it('should disable the save button', () => { 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(); }); }); diff --git a/src/app/info/end-user-agreement/end-user-agreement.component.ts b/src/app/info/end-user-agreement/end-user-agreement.component.ts index 5c10c024322..e9835945f50 100644 --- a/src/app/info/end-user-agreement/end-user-agreement.component.ts +++ b/src/app/info/end-user-agreement/end-user-agreement.component.ts @@ -23,6 +23,7 @@ import { AppState } from '../../app.reducer'; import { LogOutAction } from '../../core/auth/auth.actions'; import { AuthService } from '../../core/auth/auth.service'; import { EndUserAgreementService } from '../../core/end-user-agreement/end-user-agreement.service'; +import { BtnDisabledDirective } from '../../shared/btn-disabled.directive'; import { isNotEmpty } from '../../shared/empty.util'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { EndUserAgreementContentComponent } from './end-user-agreement-content/end-user-agreement-content.component'; @@ -32,7 +33,7 @@ import { EndUserAgreementContentComponent } from './end-user-agreement-content/e templateUrl: './end-user-agreement.component.html', styleUrls: ['./end-user-agreement.component.scss'], standalone: true, - imports: [EndUserAgreementContentComponent, FormsModule, TranslateModule], + imports: [EndUserAgreementContentComponent, FormsModule, TranslateModule, BtnDisabledDirective], }) /** * Component displaying the End User Agreement and an option to accept it diff --git a/src/app/info/feedback/feedback-form/feedback-form.component.html b/src/app/info/feedback/feedback-form/feedback-form.component.html index a3437d1f0e4..c5c4b460a23 100644 --- a/src/app/info/feedback/feedback-form/feedback-form.component.html +++ b/src/app/info/feedback/feedback-form/feedback-form.component.html @@ -41,7 +41,7 @@

{{ 'info.feedback.head' | translate }}

- +
diff --git a/src/app/info/feedback/feedback-form/feedback-form.component.spec.ts b/src/app/info/feedback/feedback-form/feedback-form.component.spec.ts index 4329c923c4c..56275ad5a35 100644 --- a/src/app/info/feedback/feedback-form/feedback-form.component.spec.ts +++ b/src/app/info/feedback/feedback-form/feedback-form.component.spec.ts @@ -18,6 +18,7 @@ import { FeedbackDataService } from '../../../core/feedback/feedback-data.servic import { Feedback } from '../../../core/feedback/models/feedback.model'; import { RouteService } from '../../../core/services/route.service'; import { NativeWindowService } from '../../../core/services/window.service'; +import { BtnDisabledDirective } from '../../../shared/btn-disabled.directive'; import { NativeWindowMockFactory } from '../../../shared/mocks/mock-native-window-ref'; import { RouterMock } from '../../../shared/mocks/router.mock'; import { NotificationsService } from '../../../shared/notifications/notifications.service'; @@ -45,7 +46,7 @@ describe('FeedbackFormComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ - imports: [TranslateModule.forRoot(), FeedbackFormComponent], + imports: [TranslateModule.forRoot(), FeedbackFormComponent, BtnDisabledDirective], providers: [ { provide: RouteService, useValue: routeServiceStub }, { provide: UntypedFormBuilder, useValue: new UntypedFormBuilder() }, @@ -79,7 +80,8 @@ describe('FeedbackFormComponent', () => { }); 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', () => { @@ -90,7 +92,8 @@ describe('FeedbackFormComponent', () => { }); 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', () => { diff --git a/src/app/info/feedback/feedback-form/feedback-form.component.ts b/src/app/info/feedback/feedback-form/feedback-form.component.ts index 59be526f175..db98a2ee011 100644 --- a/src/app/info/feedback/feedback-form/feedback-form.component.ts +++ b/src/app/info/feedback/feedback-form/feedback-form.component.ts @@ -30,6 +30,7 @@ import { import { NoContent } from '../../../core/shared/NoContent.model'; import { getFirstCompletedRemoteData } from '../../../core/shared/operators'; import { URLCombiner } from '../../../core/url-combiner/url-combiner'; +import { BtnDisabledDirective } from '../../../shared/btn-disabled.directive'; import { ErrorComponent } from '../../../shared/error/error.component'; import { NotificationsService } from '../../../shared/notifications/notifications.service'; @@ -38,7 +39,7 @@ import { NotificationsService } from '../../../shared/notifications/notification templateUrl: './feedback-form.component.html', styleUrls: ['./feedback-form.component.scss'], standalone: true, - imports: [FormsModule, ReactiveFormsModule, NgIf, ErrorComponent, TranslateModule], + imports: [FormsModule, ReactiveFormsModule, NgIf, ErrorComponent, TranslateModule, BtnDisabledDirective], }) /** * Component displaying the contents of the Feedback Statement diff --git a/src/app/item-page/bitstreams/request-a-copy/bitstream-request-a-copy-page.component.html b/src/app/item-page/bitstreams/request-a-copy/bitstream-request-a-copy-page.component.html index 3c08c2fb655..5f42ccee74a 100644 --- a/src/app/item-page/bitstreams/request-a-copy/bitstream-request-a-copy-page.component.html +++ b/src/app/item-page/bitstreams/request-a-copy/bitstream-request-a-copy-page.component.html @@ -79,7 +79,7 @@

{{'bitstream-request-a-copy.header' | translate}}

diff --git a/src/app/item-page/bitstreams/request-a-copy/bitstream-request-a-copy-page.component.ts b/src/app/item-page/bitstreams/request-a-copy/bitstream-request-a-copy-page.component.ts index bd12d3675ec..454725f2e65 100644 --- a/src/app/item-page/bitstreams/request-a-copy/bitstream-request-a-copy-page.component.ts +++ b/src/app/item-page/bitstreams/request-a-copy/bitstream-request-a-copy-page.component.ts @@ -55,6 +55,7 @@ import { getFirstCompletedRemoteData, getFirstSucceededRemoteDataPayload, } from '../../../core/shared/operators'; +import { BtnDisabledDirective } from '../../../shared/btn-disabled.directive'; import { hasValue, isNotEmpty, @@ -71,6 +72,7 @@ import { getItemPageRoute } from '../../item-page-routing-paths'; AsyncPipe, ReactiveFormsModule, NgIf, + BtnDisabledDirective, ], standalone: true, }) diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.html b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.html index 6ac44b5ac20..24a6a7509aa 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.html +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.html @@ -16,7 +16,7 @@ class="fas fa-undo-alt">  {{"item.edit.bitstreams.reinstate-button" | translate}} - - - - - diff --git a/src/app/item-page/edit-item-page/item-delete/item-delete.component.ts b/src/app/item-page/edit-item-page/item-delete/item-delete.component.ts index ae791ccd8e7..c044f62e65e 100644 --- a/src/app/item-page/edit-item-page/item-delete/item-delete.component.ts +++ b/src/app/item-page/edit-item-page/item-delete/item-delete.component.ts @@ -56,6 +56,7 @@ import { getRemoteDataPayload, } from '../../../core/shared/operators'; import { ViewMode } from '../../../core/shared/view-mode.model'; +import { BtnDisabledDirective } from '../../../shared/btn-disabled.directive'; import { hasValue, isNotEmpty, @@ -109,6 +110,7 @@ class RelationshipDTO { VarDirective, NgForOf, RouterLink, + BtnDisabledDirective, ], standalone: true, }) diff --git a/src/app/item-page/edit-item-page/item-move/item-move.component.html b/src/app/item-page/edit-item-page/item-move/item-move.component.html index 8c47d4f781a..63378f5afe2 100644 --- a/src/app/item-page/edit-item-page/item-move/item-move.component.html +++ b/src/app/item-page/edit-item-page/item-move/item-move.component.html @@ -40,7 +40,7 @@

{{'item.edit.move.head' | translate: {id: (itemRD$ | async)?.payload?.handle - -

diff --git a/src/app/item-page/edit-item-page/item-move/item-move.component.ts b/src/app/item-page/edit-item-page/item-move/item-move.component.ts index 1115260901a..07098aab11b 100644 --- a/src/app/item-page/edit-item-page/item-move/item-move.component.ts +++ b/src/app/item-page/edit-item-page/item-move/item-move.component.ts @@ -37,6 +37,7 @@ import { getRemoteDataPayload, } from '../../../core/shared/operators'; import { SearchService } from '../../../core/shared/search/search.service'; +import { BtnDisabledDirective } from '../../../shared/btn-disabled.directive'; import { AuthorizedCollectionSelectorComponent } from '../../../shared/dso-selector/dso-selector/authorized-collection-selector/authorized-collection-selector.component'; import { NotificationsService } from '../../../shared/notifications/notifications.service'; import { followLink } from '../../../shared/utils/follow-link-config.model'; @@ -56,6 +57,7 @@ import { AsyncPipe, AuthorizedCollectionSelectorComponent, NgIf, + BtnDisabledDirective, ], standalone: true, }) diff --git a/src/app/item-page/edit-item-page/item-operation/item-operation.component.html b/src/app/item-page/edit-item-page/item-operation/item-operation.component.html index 0977876a195..06f5e2901f4 100644 --- a/src/app/item-page/edit-item-page/item-operation/item-operation.component.html +++ b/src/app/item-page/edit-item-page/item-operation/item-operation.component.html @@ -5,12 +5,12 @@
- - diff --git a/src/app/item-page/edit-item-page/item-operation/item-operation.component.spec.ts b/src/app/item-page/edit-item-page/item-operation/item-operation.component.spec.ts index e2e6da826fd..85ad62de938 100644 --- a/src/app/item-page/edit-item-page/item-operation/item-operation.component.spec.ts +++ b/src/app/item-page/edit-item-page/item-operation/item-operation.component.spec.ts @@ -6,6 +6,7 @@ import { By } from '@angular/platform-browser'; import { RouterTestingModule } from '@angular/router/testing'; import { TranslateModule } from '@ngx-translate/core'; +import { BtnDisabledDirective } from '../../../shared/btn-disabled.directive'; import { ItemOperationComponent } from './item-operation.component'; import { ItemOperation } from './itemOperation.model'; @@ -17,7 +18,7 @@ describe('ItemOperationComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ - imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([]), ItemOperationComponent], + imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([]), ItemOperationComponent, BtnDisabledDirective], }).compileComponents(); })); @@ -43,7 +44,8 @@ describe('ItemOperationComponent', () => { const span = fixture.debugElement.query(By.css('.action-label span')).nativeElement; expect(span.textContent).toContain('item.edit.tabs.status.buttons.key1.label'); 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'); }); }); diff --git a/src/app/item-page/edit-item-page/item-operation/item-operation.component.ts b/src/app/item-page/edit-item-page/item-operation/item-operation.component.ts index 9cf4b30e6cd..7c3793cc571 100644 --- a/src/app/item-page/edit-item-page/item-operation/item-operation.component.ts +++ b/src/app/item-page/edit-item-page/item-operation/item-operation.component.ts @@ -7,6 +7,7 @@ import { RouterLink } from '@angular/router'; import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap'; import { TranslateModule } from '@ngx-translate/core'; +import { BtnDisabledDirective } from '../../../shared/btn-disabled.directive'; import { ItemOperation } from './itemOperation.model'; @Component({ @@ -17,6 +18,7 @@ import { ItemOperation } from './itemOperation.model'; RouterLink, NgbTooltipModule, NgIf, + BtnDisabledDirective, ], standalone: true, }) diff --git a/src/app/item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.html b/src/app/item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.html index e32938590f2..06e3a4ace7b 100644 --- a/src/app/item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.html +++ b/src/app/item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.html @@ -1,6 +1,6 @@

{{relationshipMessageKey$ | async | translate}} - diff --git a/src/app/item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.spec.ts b/src/app/item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.spec.ts index 73405c83f6a..f470b09341d 100644 --- a/src/app/item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.spec.ts +++ b/src/app/item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.spec.ts @@ -388,7 +388,8 @@ describe('EditRelationshipListComponent', () => { comp.hasChanges = observableOf(true); fixture.detectChanges(); 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(); }); }); diff --git a/src/app/item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.ts b/src/app/item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.ts index 7f1d9fc5db7..be74be2fa0c 100644 --- a/src/app/item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.ts +++ b/src/app/item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.ts @@ -65,6 +65,7 @@ import { getFirstSucceededRemoteDataPayload, getRemoteDataPayload, } from '../../../../core/shared/operators'; +import { BtnDisabledDirective } from '../../../../shared/btn-disabled.directive'; import { hasNoValue, hasValue, @@ -100,6 +101,7 @@ import { EditRelationshipComponent } from '../edit-relationship/edit-relationshi TranslateModule, NgClass, ThemedLoadingComponent, + BtnDisabledDirective, ], standalone: true, }) diff --git a/src/app/item-page/edit-item-page/item-relationships/edit-relationship/edit-relationship.component.html b/src/app/item-page/edit-item-page/item-relationships/edit-relationship/edit-relationship.component.html index e65cd237a34..8cd86d597b8 100644 --- a/src/app/item-page/edit-item-page/item-relationships/edit-relationship/edit-relationship.component.html +++ b/src/app/item-page/edit-item-page/item-relationships/edit-relationship/edit-relationship.component.html @@ -9,12 +9,12 @@

- -
@@ -8,7 +8,7 @@ ngbDropdown *ngIf="(moreThanOne$ | async)"> - + +
@@ -66,6 +66,6 @@

{{ (labelPrefix + label + '.select' | translate) }}

- +
diff --git a/src/app/notifications/qa/project-entry-import-modal/project-entry-import-modal.component.ts b/src/app/notifications/qa/project-entry-import-modal/project-entry-import-modal.component.ts index 99f57b84ca6..4954a327474 100644 --- a/src/app/notifications/qa/project-entry-import-modal/project-entry-import-modal.component.ts +++ b/src/app/notifications/qa/project-entry-import-modal/project-entry-import-modal.component.ts @@ -30,6 +30,7 @@ import { DSpaceObject } from '../../../core/shared/dspace-object.model'; import { Item } from '../../../core/shared/item.model'; import { SearchService } from '../../../core/shared/search/search.service'; import { AlertComponent } from '../../../shared/alert/alert.component'; +import { BtnDisabledDirective } from '../../../shared/btn-disabled.directive'; import { hasValue, isNotEmpty, @@ -105,7 +106,7 @@ export interface QualityAssuranceEventData { styleUrls: ['./project-entry-import-modal.component.scss'], templateUrl: './project-entry-import-modal.component.html', standalone: true, - imports: [RouterLink, NgIf, FormsModule, ThemedLoadingComponent, ThemedSearchResultsComponent, AlertComponent, AsyncPipe, TranslateModule], + imports: [RouterLink, NgIf, FormsModule, ThemedLoadingComponent, ThemedSearchResultsComponent, AlertComponent, AsyncPipe, TranslateModule, BtnDisabledDirective], }) /** * Component to display a modal window for linking a project to an Quality Assurance event diff --git a/src/app/notifications/suggestion-actions/suggestion-actions.component.html b/src/app/notifications/suggestion-actions/suggestion-actions.component.html index 2a46191deea..342919d4aee 100644 --- a/src/app/notifications/suggestion-actions/suggestion-actions.component.html +++ b/src/app/notifications/suggestion-actions/suggestion-actions.component.html @@ -21,7 +21,7 @@ - diff --git a/src/app/process-page/overview/process-overview.component.ts b/src/app/process-page/overview/process-overview.component.ts index 1d10a87b646..47efe888bf1 100644 --- a/src/app/process-page/overview/process-overview.component.ts +++ b/src/app/process-page/overview/process-overview.component.ts @@ -16,6 +16,7 @@ import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { TranslateModule } from '@ngx-translate/core'; import { Subscription } from 'rxjs'; +import { BtnDisabledDirective } from '../../shared/btn-disabled.directive'; import { hasValue } from '../../shared/empty.util'; import { PaginationComponent } from '../../shared/pagination/pagination.component'; import { VarDirective } from '../../shared/utils/var.directive'; @@ -31,7 +32,7 @@ import { ProcessOverviewTableComponent } from './table/process-overview-table.co selector: 'ds-process-overview', templateUrl: './process-overview.component.html', standalone: true, - imports: [NgIf, RouterLink, PaginationComponent, NgFor, VarDirective, AsyncPipe, DatePipe, TranslateModule, NgTemplateOutlet, ProcessOverviewTableComponent], + imports: [NgIf, RouterLink, PaginationComponent, NgFor, VarDirective, AsyncPipe, DatePipe, TranslateModule, NgTemplateOutlet, ProcessOverviewTableComponent, BtnDisabledDirective], }) /** * Component displaying a list of all processes in a paginated table diff --git a/src/app/profile-page/profile-claim-item-modal/profile-claim-item-modal.component.html b/src/app/profile-page/profile-claim-item-modal/profile-claim-item-modal.component.html index a5bbb38d025..3233aa55550 100644 --- a/src/app/profile-page/profile-claim-item-modal/profile-claim-item-modal.component.html +++ b/src/app/profile-page/profile-claim-item-modal/profile-claim-item-modal.component.html @@ -29,7 +29,7 @@ {{ 'dso-selector.claim.item.not-mine-label' | translate }} - diff --git a/src/app/profile-page/profile-claim-item-modal/profile-claim-item-modal.component.ts b/src/app/profile-page/profile-claim-item-modal/profile-claim-item-modal.component.ts index 4ff9b637f63..7730b1e4dd8 100644 --- a/src/app/profile-page/profile-claim-item-modal/profile-claim-item-modal.component.ts +++ b/src/app/profile-page/profile-claim-item-modal/profile-claim-item-modal.component.ts @@ -25,6 +25,7 @@ import { Item } from '../../core/shared/item.model'; import { getFirstCompletedRemoteData } from '../../core/shared/operators'; import { ViewMode } from '../../core/shared/view-mode.model'; import { getItemPageRoute } from '../../item-page/item-page-routing-paths'; +import { BtnDisabledDirective } from '../../shared/btn-disabled.directive'; import { DSOSelectorModalWrapperComponent } from '../../shared/dso-selector/modal-wrappers/dso-selector-modal-wrapper.component'; import { CollectionElementLinkType } from '../../shared/object-collection/collection-element-link.type'; import { ListableObjectComponentLoaderComponent } from '../../shared/object-collection/shared/listable-object/listable-object-component-loader.component'; @@ -42,6 +43,7 @@ import { ProfileClaimService } from '../profile-claim/profile-claim.service'; AsyncPipe, TranslateModule, NgForOf, + BtnDisabledDirective, ], standalone: true, }) diff --git a/src/app/profile-page/profile-page-researcher-form/profile-page-researcher-form.component.html b/src/app/profile-page/profile-page-researcher-form/profile-page-researcher-form.component.html index 8d48da08d98..d0279d9c5ff 100644 --- a/src/app/profile-page/profile-page-researcher-form/profile-page-researcher-form.component.html +++ b/src/app/profile-page/profile-page-researcher-form/profile-page-researcher-form.component.html @@ -13,7 +13,7 @@

{{'researcher.profile.not.associated' | translate}}

- - - diff --git a/src/app/register-email-form/register-email-form.component.ts b/src/app/register-email-form/register-email-form.component.ts index d96de8461b9..a2c8957a9f9 100644 --- a/src/app/register-email-form/register-email-form.component.ts +++ b/src/app/register-email-form/register-email-form.component.ts @@ -54,6 +54,7 @@ import { import { Registration } from '../core/shared/registration.model'; import { AlertComponent } from '../shared/alert/alert.component'; import { AlertType } from '../shared/alert/alert-type'; +import { BtnDisabledDirective } from '../shared/btn-disabled.directive'; import { OrejimeService } from '../shared/cookies/orejime.service'; import { isNotEmpty } from '../shared/empty.util'; import { GoogleRecaptchaComponent } from '../shared/google-recaptcha/google-recaptcha.component'; @@ -66,7 +67,7 @@ export const TYPE_REQUEST_REGISTER = 'register'; selector: 'ds-base-register-email-form', templateUrl: './register-email-form.component.html', standalone: true, - imports: [NgIf, FormsModule, ReactiveFormsModule, AlertComponent, GoogleRecaptchaComponent, AsyncPipe, TranslateModule], + imports: [NgIf, FormsModule, ReactiveFormsModule, AlertComponent, GoogleRecaptchaComponent, AsyncPipe, TranslateModule, BtnDisabledDirective], }) /** * Component responsible to render an email registration form. diff --git a/src/app/register-page/create-profile/create-profile.component.html b/src/app/register-page/create-profile/create-profile.component.html index dd9528925a7..14c06fd8db3 100644 --- a/src/app/register-page/create-profile/create-profile.component.html +++ b/src/app/register-page/create-profile/create-profile.component.html @@ -81,7 +81,7 @@

{{'register-page.create-profile.header' | translate}}

diff --git a/src/app/register-page/create-profile/create-profile.component.ts b/src/app/register-page/create-profile/create-profile.component.ts index 7e88c5a1c01..43ee0c8cd39 100644 --- a/src/app/register-page/create-profile/create-profile.component.ts +++ b/src/app/register-page/create-profile/create-profile.component.ts @@ -43,6 +43,7 @@ import { } from '../../core/shared/operators'; import { Registration } from '../../core/shared/registration.model'; import { ProfilePageSecurityFormComponent } from '../../profile-page/profile-page-security-form/profile-page-security-form.component'; +import { BtnDisabledDirective } from '../../shared/btn-disabled.directive'; import { isEmpty } from '../../shared/empty.util'; import { NotificationsService } from '../../shared/notifications/notifications.service'; @@ -60,6 +61,7 @@ import { NotificationsService } from '../../shared/notifications/notifications.s AsyncPipe, ReactiveFormsModule, NgForOf, + BtnDisabledDirective, ], standalone: true, }) diff --git a/src/app/request-copy/email-request-copy/email-request-copy.component.html b/src/app/request-copy/email-request-copy/email-request-copy.component.html index 286e87554b8..b2227bfbaee 100644 --- a/src/app/request-copy/email-request-copy/email-request-copy.component.html +++ b/src/app/request-copy/email-request-copy/email-request-copy.component.html @@ -13,7 +13,7 @@
@@ -76,7 +76,7 @@
@@ -93,7 +93,7 @@ @@ -103,7 +103,7 @@ diff --git a/src/app/shared/access-control-form-container/access-control-form-container.component.ts b/src/app/shared/access-control-form-container/access-control-form-container.component.ts index 56a6e92323a..46895b8532b 100644 --- a/src/app/shared/access-control-form-container/access-control-form-container.component.ts +++ b/src/app/shared/access-control-form-container/access-control-form-container.component.ts @@ -31,6 +31,7 @@ import { Item } from '../../core/shared/item.model'; import { getFirstCompletedRemoteData } from '../../core/shared/operators'; import { AlertComponent } from '../alert/alert.component'; import { AlertType } from '../alert/alert-type'; +import { BtnDisabledDirective } from '../btn-disabled.directive'; import { SelectableListService } from '../object-list/selectable-list/selectable-list.service'; import { AccessControlArrayFormComponent } from './access-control-array-form/access-control-array-form.component'; import { createAccessControlInitialFormState } from './access-control-form-container-intial-state'; @@ -46,7 +47,7 @@ import { styleUrls: ['./access-control-form-container.component.scss'], exportAs: 'dsAccessControlForm', standalone: true, - imports: [NgIf, AlertComponent, UiSwitchModule, FormsModule, AccessControlArrayFormComponent, AsyncPipe, TranslateModule], + imports: [NgIf, AlertComponent, UiSwitchModule, FormsModule, AccessControlArrayFormComponent, AsyncPipe, TranslateModule, BtnDisabledDirective], }) export class AccessControlFormContainerComponent implements OnDestroy { diff --git a/src/app/shared/btn-disabled.directive.ts b/src/app/shared/btn-disabled.directive.ts new file mode 100644 index 00000000000..427d1654da0 --- /dev/null +++ b/src/app/shared/btn-disabled.directive.ts @@ -0,0 +1,63 @@ +import { + Directive, + HostBinding, + HostListener, + Input, +} from '@angular/core'; + +@Directive({ + selector: '[dsBtnDisabled]', + 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 BtnDisabledDirective { + + @Input() set dsBtnDisabled(value: boolean) { + 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; + + /** + * 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; } + + /** + * 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']) + handleClick(event: Event) { + if (this.isDisabled) { + event.preventDefault(); + event.stopImmediatePropagation(); + } + } + + /** + * 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']) + handleKeydown(event: KeyboardEvent) { + if (this.isDisabled && (event.key === 'Enter' || event.key === 'Space')) { + event.preventDefault(); + event.stopImmediatePropagation(); + } + } +} + diff --git a/src/app/shared/disabled-directive.spec.ts b/src/app/shared/disabled-directive.spec.ts new file mode 100644 index 00000000000..96f760fc466 --- /dev/null +++ b/src/app/shared/disabled-directive.spec.ts @@ -0,0 +1,105 @@ +import { + Component, + DebugElement, +} from '@angular/core'; +import { + ComponentFixture, + TestBed, +} from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; + +import { BtnDisabledDirective } from './btn-disabled.directive'; + +@Component({ + template: ` + + `, +}) +class TestComponent { + isDisabled = false; +} + +describe('DisabledDirective', () => { + let component: TestComponent; + let fixture: ComponentFixture; + let button: DebugElement; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [BtnDisabledDirective], + declarations: [TestComponent], + }); + 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 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', () => { + 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(); + }); +}); diff --git a/src/app/shared/ds-select/ds-select.component.html b/src/app/shared/ds-select/ds-select.component.html index c12ea613472..e87b7c5cc6c 100644 --- a/src/app/shared/ds-select/ds-select.component.html +++ b/src/app/shared/ds-select/ds-select.component.html @@ -13,7 +13,7 @@ class="btn btn-outline-primary selection" (blur)="close.emit($event)" (click)="close.emit($event)" - [disabled]="disabled" + [dsBtnDisabled]="disabled" ngbDropdownToggle> diff --git a/src/app/shared/ds-select/ds-select.component.ts b/src/app/shared/ds-select/ds-select.component.ts index d82378d4e5d..2616ce4a9fc 100644 --- a/src/app/shared/ds-select/ds-select.component.ts +++ b/src/app/shared/ds-select/ds-select.component.ts @@ -8,6 +8,8 @@ import { import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'; import { TranslateModule } from '@ngx-translate/core'; +import { BtnDisabledDirective } from '../btn-disabled.directive'; + /** * Component which represent a DSpace dropdown selector. */ @@ -16,7 +18,7 @@ import { TranslateModule } from '@ngx-translate/core'; templateUrl: './ds-select.component.html', styleUrls: ['./ds-select.component.scss'], standalone: true, - imports: [NgbDropdownModule, NgIf, TranslateModule], + imports: [NgbDropdownModule, NgIf, TranslateModule, BtnDisabledDirective], }) export class DsSelectComponent { diff --git a/src/app/shared/dso-page/dso-edit-menu/dso-edit-expandable-menu-section/dso-edit-menu-expandable-section.component.html b/src/app/shared/dso-page/dso-edit-menu/dso-edit-expandable-menu-section/dso-edit-menu-expandable-section.component.html index 6e427400eec..05032813296 100644 --- a/src/app/shared/dso-page/dso-edit-menu/dso-edit-expandable-menu-section/dso-edit-menu-expandable-section.component.html +++ b/src/app/shared/dso-page/dso-edit-menu/dso-edit-expandable-menu-section/dso-edit-menu-expandable-section.component.html @@ -1,7 +1,7 @@
-
    diff --git a/src/app/shared/dso-page/dso-edit-menu/dso-edit-expandable-menu-section/dso-edit-menu-expandable-section.component.ts b/src/app/shared/dso-page/dso-edit-menu/dso-edit-expandable-menu-section/dso-edit-menu-expandable-section.component.ts index 94d0ddd2605..fadaee8b22f 100644 --- a/src/app/shared/dso-page/dso-edit-menu/dso-edit-expandable-menu-section/dso-edit-menu-expandable-section.component.ts +++ b/src/app/shared/dso-page/dso-edit-menu/dso-edit-expandable-menu-section/dso-edit-menu-expandable-section.component.ts @@ -22,6 +22,7 @@ import { MenuID } from 'src/app/shared/menu/menu-id.model'; import { MenuSection } from 'src/app/shared/menu/menu-section.model'; import { MenuSectionComponent } from 'src/app/shared/menu/menu-section/menu-section.component'; +import { BtnDisabledDirective } from '../../../btn-disabled.directive'; import { hasValue } from '../../../empty.util'; import { MenuService } from '../../../menu/menu.service'; @@ -33,7 +34,7 @@ import { MenuService } from '../../../menu/menu.service'; templateUrl: './dso-edit-menu-expandable-section.component.html', styleUrls: ['./dso-edit-menu-expandable-section.component.scss'], standalone: true, - imports: [NgbDropdownModule, NgbTooltipModule, NgFor, NgIf, NgComponentOutlet, TranslateModule, AsyncPipe], + imports: [NgbDropdownModule, NgbTooltipModule, NgFor, NgIf, NgComponentOutlet, TranslateModule, AsyncPipe, BtnDisabledDirective], }) export class DsoEditMenuExpandableSectionComponent extends MenuSectionComponent implements OnInit { diff --git a/src/app/shared/dso-page/dso-edit-menu/dso-edit-menu-section/dso-edit-menu-section.component.html b/src/app/shared/dso-page/dso-edit-menu/dso-edit-menu-section/dso-edit-menu-section.component.html index bbc388a6014..0738e2f28ab 100644 --- a/src/app/shared/dso-page/dso-edit-menu/dso-edit-menu-section/dso-edit-menu-section.component.html +++ b/src/app/shared/dso-page/dso-edit-menu/dso-edit-menu-section/dso-edit-menu-section.component.html @@ -5,7 +5,7 @@ {{itemModel.text | translate}} - @@ -13,7 +13,7 @@
    - @@ -60,7 +60,7 @@ type="button" ngbTooltip="{{'form.clear-help' | translate}}" placement="top" - [disabled]="model.readOnly" + [dsBtnDisabled]="model.readOnly" (click)="remove()">{{'form.clear' | translate}}
    @@ -69,14 +69,14 @@ type="button" ngbTooltip="{{'form.edit-help' | translate}}" placement="top" - [disabled]="isEditDisabled()" + [dsBtnDisabled]="isEditDisabled()" (click)="switchEditMode()">{{'form.edit' | translate}}
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/lookup/dynamic-lookup.component.spec.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/models/lookup/dynamic-lookup.component.spec.ts index 24122373df9..493c56e29d6 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/lookup/dynamic-lookup.component.spec.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/lookup/dynamic-lookup.component.spec.ts @@ -33,6 +33,7 @@ import { of as observableOf } from 'rxjs'; import { VocabularyEntry } from '../../../../../../core/submission/vocabularies/models/vocabulary-entry.model'; import { VocabularyOptions } from '../../../../../../core/submission/vocabularies/models/vocabulary-options.model'; import { VocabularyService } from '../../../../../../core/submission/vocabularies/vocabulary.service'; +import { BtnDisabledDirective } from '../../../../../btn-disabled.directive'; import { mockDynamicFormLayoutService, mockDynamicFormValidationService, @@ -174,6 +175,7 @@ describe('Dynamic Lookup component', () => { TestComponent, AuthorityConfidenceStateDirective, ObjNgFor, + BtnDisabledDirective, ], providers: [ ChangeDetectorRef, @@ -255,8 +257,10 @@ describe('Dynamic Lookup component', () => { const de = lookupFixture.debugElement.queryAll(By.css('button')); const searchBtnEl = de[0].nativeElement; const editBtnEl = de[1].nativeElement; - expect(searchBtnEl.disabled).toBe(true); - expect(editBtnEl.disabled).toBe(true); + expect(searchBtnEl.getAttribute('aria-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'); }); @@ -354,8 +358,10 @@ describe('Dynamic Lookup component', () => { const de = lookupFixture.debugElement.queryAll(By.css('button')); const searchBtnEl = de[0].nativeElement; const saveBtnEl = de[1].nativeElement; - expect(searchBtnEl.disabled).toBe(true); - expect(saveBtnEl.disabled).toBe(false); + expect(searchBtnEl.getAttribute('aria-disabled')).toBe('true'); + 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'); }); @@ -395,8 +401,10 @@ describe('Dynamic Lookup component', () => { const de = lookupFixture.debugElement.queryAll(By.css('button')); const searchBtnEl = de[0].nativeElement; const saveBtnEl = de[1].nativeElement; - expect(searchBtnEl.disabled).toBe(true); - expect(saveBtnEl.disabled).toBe(false); + expect(searchBtnEl.getAttribute('aria-disabled')).toBe('true'); + 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'); }); @@ -427,8 +435,10 @@ describe('Dynamic Lookup component', () => { const editBtnEl = deBtn[1].nativeElement; expect(de.length).toBe(2); - expect(searchBtnEl.disabled).toBe(true); - expect(editBtnEl.disabled).toBe(true); + expect(searchBtnEl.getAttribute('aria-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'); }); @@ -524,8 +534,10 @@ describe('Dynamic Lookup component', () => { const de = lookupFixture.debugElement.queryAll(By.css('button')); const searchBtnEl = de[0].nativeElement; const saveBtnEl = de[1].nativeElement; - expect(searchBtnEl.disabled).toBe(true); - expect(saveBtnEl.disabled).toBe(false); + expect(searchBtnEl.getAttribute('aria-disabled')).toBe('true'); + 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'); }); @@ -567,8 +579,10 @@ describe('Dynamic Lookup component', () => { const de = lookupFixture.debugElement.queryAll(By.css('button')); const searchBtnEl = de[0].nativeElement; const saveBtnEl = de[1].nativeElement; - expect(searchBtnEl.disabled).toBe(true); - expect(saveBtnEl.disabled).toBe(false); + expect(searchBtnEl.getAttribute('aria-disabled')).toBe('true'); + 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'); }); diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/lookup/dynamic-lookup.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/models/lookup/dynamic-lookup.component.ts index 7e4bc3a0028..39c39cf8bc6 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/lookup/dynamic-lookup.component.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/lookup/dynamic-lookup.component.ts @@ -46,6 +46,7 @@ import { getFirstSucceededRemoteDataPayload } from '../../../../../../core/share import { PageInfo } from '../../../../../../core/shared/page-info.model'; import { VocabularyEntry } from '../../../../../../core/submission/vocabularies/models/vocabulary-entry.model'; import { VocabularyService } from '../../../../../../core/submission/vocabularies/vocabulary.service'; +import { BtnDisabledDirective } from '../../../../../btn-disabled.directive'; import { hasValue, isEmpty, @@ -76,6 +77,7 @@ import { DynamicLookupNameModel } from './dynamic-lookup-name.model'; NgForOf, NgTemplateOutlet, ObjNgFor, + BtnDisabledDirective, ], standalone: true, }) diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/onebox/dynamic-onebox.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/models/onebox/dynamic-onebox.component.ts index 22f8dce2444..de6b50d589b 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/onebox/dynamic-onebox.component.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/onebox/dynamic-onebox.component.ts @@ -59,6 +59,7 @@ import { Vocabulary } from '../../../../../../core/submission/vocabularies/model import { VocabularyEntry } from '../../../../../../core/submission/vocabularies/models/vocabulary-entry.model'; import { VocabularyEntryDetail } from '../../../../../../core/submission/vocabularies/models/vocabulary-entry-detail.model'; import { VocabularyService } from '../../../../../../core/submission/vocabularies/vocabulary.service'; +import { BtnDisabledDirective } from '../../../../../btn-disabled.directive'; import { hasValue, isEmpty, @@ -90,6 +91,7 @@ import { DynamicOneboxModel } from './dynamic-onebox.model'; ObjNgFor, NgForOf, FormsModule, + BtnDisabledDirective, ], standalone: true, }) diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/relation-group/dynamic-relation-group.component.html b/src/app/shared/form/builder/ds-dynamic-form-ui/models/relation-group/dynamic-relation-group.component.html index 38f15a5ed1c..1cb8ee1e1a2 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/relation-group/dynamic-relation-group.component.html +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/relation-group/dynamic-relation-group.component.html @@ -32,21 +32,21 @@ @@ -73,18 +73,18 @@
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/external-source-tab/external-source-entry-import-modal/external-source-entry-import-modal.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/external-source-tab/external-source-entry-import-modal/external-source-entry-import-modal.component.ts index c2516ca9a81..e0cc90ceaa6 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/external-source-tab/external-source-entry-import-modal/external-source-entry-import-modal.component.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/external-source-tab/external-source-entry-import-modal/external-source-entry-import-modal.component.ts @@ -37,6 +37,7 @@ import { getRemoteDataPayload, } from '../../../../../../../core/shared/operators'; import { SubmissionImportExternalCollectionComponent } from '../../../../../../../submission/import-external/import-external-collection/submission-import-external-collection.component'; +import { BtnDisabledDirective } from '../../../../../../btn-disabled.directive'; import { CollectionListEntry } from '../../../../../../collection-dropdown/collection-dropdown.component'; import { NotificationsService } from '../../../../../../notifications/notifications.service'; import { CollectionElementLinkType } from '../../../../../../object-collection/collection-element-link.type'; @@ -69,6 +70,7 @@ export enum ImportType { ThemedSearchResultsComponent, NgIf, AsyncPipe, + BtnDisabledDirective, ], standalone: true, }) diff --git a/src/app/shared/form/form.component.html b/src/app/shared/form/form.component.html index ef75f34b9b1..85e32e66ea8 100644 --- a/src/app/shared/form/form.component.html +++ b/src/app/shared/form/form.component.html @@ -41,7 +41,7 @@ title="{{'form.discard' | translate}}" attr.aria-label="{{'form.discard' | translate}}" (click)="removeItem($event, context, index)" - [disabled]="group.context.groups.length === 1 || isItemReadOnly(context, index)"> + [dsBtnDisabled]="group.context.groups.length === 1 || isItemReadOnly(context, index)"> {{'form.discard' | translate}}
@@ -62,7 +62,7 @@ diff --git a/src/app/shared/form/form.component.ts b/src/app/shared/form/form.component.ts index e1d47da7bd1..a757239aea1 100644 --- a/src/app/shared/form/form.component.ts +++ b/src/app/shared/form/form.component.ts @@ -39,6 +39,7 @@ import { map, } from 'rxjs/operators'; +import { BtnDisabledDirective } from '../btn-disabled.directive'; import { hasValue, isNotEmpty, @@ -69,6 +70,7 @@ import { FormService } from './form.service'; DynamicFormsCoreModule, NgIf, AsyncPipe, + BtnDisabledDirective, ], standalone: true, }) diff --git a/src/app/shared/form/number-picker/number-picker.component.html b/src/app/shared/form/number-picker/number-picker.component.html index 12e29fa70e4..9b9d038e868 100644 --- a/src/app/shared/form/number-picker/number-picker.component.html +++ b/src/app/shared/form/number-picker/number-picker.component.html @@ -3,7 +3,7 @@ class="btn btn-link-focus" type="button" tabindex="0" - [disabled]="disabled" + [dsBtnDisabled]="disabled" (click)="toggleUp()"> {{'form.number-picker.increment' | translate: {field: name} }} @@ -30,7 +30,7 @@ class="btn btn-link-focus" type="button" tabindex="0" - [disabled]="disabled" + [dsBtnDisabled]="disabled" (click)="toggleDown()"> {{'form.number-picker.decrement' | translate: {field: name} }} diff --git a/src/app/shared/form/number-picker/number-picker.component.ts b/src/app/shared/form/number-picker/number-picker.component.ts index b0972366591..ef35e1ee796 100644 --- a/src/app/shared/form/number-picker/number-picker.component.ts +++ b/src/app/shared/form/number-picker/number-picker.component.ts @@ -20,6 +20,7 @@ import { TranslateService, } from '@ngx-translate/core'; +import { BtnDisabledDirective } from '../../btn-disabled.directive'; import { isEmpty } from '../../empty.util'; @Component({ @@ -33,6 +34,7 @@ import { isEmpty } from '../../empty.util'; NgClass, FormsModule, TranslateModule, + BtnDisabledDirective, ], standalone: true, }) diff --git a/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.html b/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.html index f55f26aa685..1778291a631 100644 --- a/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.html +++ b/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.html @@ -6,7 +6,7 @@ [attr.aria-label]="'vocabulary-treeview.search.form.search-placeholder' | translate" [placeholder]="'vocabulary-treeview.search.form.search-placeholder' | translate">
- @@ -18,7 +18,7 @@ [attr.aria-label]="'vocabulary-treeview.search.form.add' | translate"> {{'vocabulary-treeview.search.form.add' | translate}} - @@ -55,7 +55,7 @@

{{ (message | async) | translate }}

+ [dsBtnDisabled]="!form.valid"> {{"login.form.submit" | translate}} diff --git a/src/app/shared/log-in/methods/password/log-in-password.component.ts b/src/app/shared/log-in/methods/password/log-in-password.component.ts index bcc2048dee2..25ec0cba740 100644 --- a/src/app/shared/log-in/methods/password/log-in-password.component.ts +++ b/src/app/shared/log-in/methods/password/log-in-password.component.ts @@ -49,6 +49,7 @@ import { AuthorizationDataService } from '../../../../core/data/feature-authoriz import { FeatureID } from '../../../../core/data/feature-authorization/feature-id'; import { HardRedirectService } from '../../../../core/services/hard-redirect.service'; import { fadeOut } from '../../../animations/fade'; +import { BtnDisabledDirective } from '../../../btn-disabled.directive'; import { isNotEmpty } from '../../../empty.util'; import { BrowserOnlyPipe } from '../../../utils/browser-only.pipe'; @@ -62,7 +63,7 @@ import { BrowserOnlyPipe } from '../../../utils/browser-only.pipe'; styleUrls: ['./log-in-password.component.scss'], animations: [fadeOut], standalone: true, - imports: [FormsModule, ReactiveFormsModule, NgIf, RouterLink, AsyncPipe, TranslateModule, BrowserOnlyPipe], + imports: [FormsModule, ReactiveFormsModule, NgIf, RouterLink, AsyncPipe, TranslateModule, BrowserOnlyPipe, BtnDisabledDirective], }) export class LogInPasswordComponent implements OnInit { diff --git a/src/app/shared/mydspace-actions/claimed-task/approve/claimed-task-actions-approve.component.html b/src/app/shared/mydspace-actions/claimed-task/approve/claimed-task-actions-approve.component.html index 9e364ac8085..affbec108cb 100644 --- a/src/app/shared/mydspace-actions/claimed-task/approve/claimed-task-actions-approve.component.html +++ b/src/app/shared/mydspace-actions/claimed-task/approve/claimed-task-actions-approve.component.html @@ -1,7 +1,7 @@ diff --git a/src/app/shared/object-select/collection-select/collection-select.component.ts b/src/app/shared/object-select/collection-select/collection-select.component.ts index a4fa227e6b7..1d1b8ed06be 100644 --- a/src/app/shared/object-select/collection-select/collection-select.component.ts +++ b/src/app/shared/object-select/collection-select/collection-select.component.ts @@ -20,6 +20,7 @@ import { getCollectionPageRoute } from '../../../collection-page/collection-page import { PaginatedList } from '../../../core/data/paginated-list.model'; import { Collection } from '../../../core/shared/collection.model'; import { getAllSucceededRemoteDataPayload } from '../../../core/shared/operators'; +import { BtnDisabledDirective } from '../../btn-disabled.directive'; import { hasValueOperator, isNotEmpty, @@ -36,7 +37,7 @@ import { ObjectSelectComponent } from '../object-select/object-select.component' templateUrl: './collection-select.component.html', styleUrls: ['./collection-select.component.scss'], standalone: true, - imports: [VarDirective, NgIf, PaginationComponent, NgFor, FormsModule, RouterLink, ErrorComponent, ThemedLoadingComponent, NgClass, AsyncPipe, TranslateModule], + imports: [VarDirective, NgIf, PaginationComponent, NgFor, FormsModule, RouterLink, ErrorComponent, ThemedLoadingComponent, NgClass, AsyncPipe, TranslateModule, BtnDisabledDirective], }) /** diff --git a/src/app/shared/object-select/item-select/item-select.component.html b/src/app/shared/object-select/item-select/item-select.component.html index e0e71fb9392..6f3d31703d1 100644 --- a/src/app/shared/object-select/item-select/item-select.component.html +++ b/src/app/shared/object-select/item-select/item-select.component.html @@ -42,7 +42,7 @@ diff --git a/src/app/shared/object-select/item-select/item-select.component.spec.ts b/src/app/shared/object-select/item-select/item-select.component.spec.ts index 86d4b5c8d43..656e0716e94 100644 --- a/src/app/shared/object-select/item-select/item-select.component.spec.ts +++ b/src/app/shared/object-select/item-select/item-select.component.spec.ts @@ -18,6 +18,7 @@ import { LinkHeadService } from '../../../core/services/link-head.service'; import { ConfigurationProperty } from '../../../core/shared/configuration-property.model'; import { Item } from '../../../core/shared/item.model'; import { SearchConfigurationService } from '../../../core/shared/search/search-configuration.service'; +import { BtnDisabledDirective } from '../../btn-disabled.directive'; import { HostWindowService } from '../../host-window.service'; import { PaginationComponentOptions } from '../../pagination/pagination-component-options.model'; import { createSuccessfulRemoteDataObject$ } from '../../remote-data.utils'; @@ -101,7 +102,7 @@ describe('ItemSelectComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ - imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([])], + imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([]), BtnDisabledDirective], providers: [ { provide: ObjectSelectService, useValue: new ObjectSelectServiceStub([mockItemList[1].id]) }, { provide: HostWindowService, useValue: new HostWindowServiceStub(0) }, diff --git a/src/app/shared/object-select/item-select/item-select.component.ts b/src/app/shared/object-select/item-select/item-select.component.ts index 285a790c2a4..2267a656862 100644 --- a/src/app/shared/object-select/item-select/item-select.component.ts +++ b/src/app/shared/object-select/item-select/item-select.component.ts @@ -19,6 +19,7 @@ import { PaginatedList } from '../../../core/data/paginated-list.model'; import { Item } from '../../../core/shared/item.model'; import { getAllSucceededRemoteDataPayload } from '../../../core/shared/operators'; import { getItemPageRoute } from '../../../item-page/item-page-routing-paths'; +import { BtnDisabledDirective } from '../../btn-disabled.directive'; import { hasValueOperator, isNotEmpty, @@ -34,7 +35,7 @@ import { ObjectSelectComponent } from '../object-select/object-select.component' selector: 'ds-item-select', templateUrl: './item-select.component.html', standalone: true, - imports: [VarDirective, NgIf, PaginationComponent, NgFor, FormsModule, RouterLink, ErrorComponent, ThemedLoadingComponent, NgClass, AsyncPipe, TranslateModule], + imports: [VarDirective, NgIf, PaginationComponent, NgFor, FormsModule, RouterLink, ErrorComponent, ThemedLoadingComponent, NgClass, AsyncPipe, TranslateModule, BtnDisabledDirective], }) /** diff --git a/src/app/shared/pagination/pagination.component.html b/src/app/shared/pagination/pagination.component.html index 87d312f9ece..605ac4ef451 100644 --- a/src/app/shared/pagination/pagination.component.html +++ b/src/app/shared/pagination/pagination.component.html @@ -56,12 +56,12 @@
diff --git a/src/app/shared/search/advanced-search/advanced-search.component.ts b/src/app/shared/search/advanced-search/advanced-search.component.ts index f8ebb51dad6..99eaa23a0d7 100644 --- a/src/app/shared/search/advanced-search/advanced-search.component.ts +++ b/src/app/shared/search/advanced-search/advanced-search.component.ts @@ -31,6 +31,7 @@ import { SearchService } from '../../../core/shared/search/search.service'; import { SearchConfigurationService } from '../../../core/shared/search/search-configuration.service'; import { SearchFilterService } from '../../../core/shared/search/search-filter.service'; import { FilterConfig } from '../../../core/shared/search/search-filters/search-config.model'; +import { BtnDisabledDirective } from '../../btn-disabled.directive'; import { hasValue, isNotEmpty, @@ -55,6 +56,7 @@ import { SearchFilterConfig } from '../models/search-filter-config.model'; KeyValuePipe, NgForOf, TranslateModule, + BtnDisabledDirective, ], }) export class AdvancedSearchComponent implements OnInit, OnDestroy { diff --git a/src/app/shared/subscriptions/subscription-modal/subscription-modal.component.html b/src/app/shared/subscriptions/subscription-modal/subscription-modal.component.html index e0bbc1a6095..37a7a3f431d 100644 --- a/src/app/shared/subscriptions/subscription-modal/subscription-modal.component.html +++ b/src/app/shared/subscriptions/subscription-modal/subscription-modal.component.html @@ -34,7 +34,7 @@ (click)="activeModal.close()"> {{'subscriptions.modal.close' | translate}} - diff --git a/src/app/shared/upload/uploader/uploader.component.ts b/src/app/shared/upload/uploader/uploader.component.ts index fbab09be3e3..fbafe811ebc 100644 --- a/src/app/shared/upload/uploader/uploader.component.ts +++ b/src/app/shared/upload/uploader/uploader.component.ts @@ -27,6 +27,7 @@ import { XSRF_REQUEST_HEADER, XSRF_RESPONSE_HEADER, } from '../../../core/xsrf/xsrf.constants'; +import { BtnDisabledDirective } from '../../btn-disabled.directive'; import { hasValue, isNotEmpty, @@ -42,7 +43,7 @@ import { UploaderProperties } from './uploader-properties.model'; changeDetection: ChangeDetectionStrategy.Default, encapsulation: ViewEncapsulation.Emulated, standalone: true, - imports: [TranslateModule, FileUploadModule, CommonModule], + imports: [TranslateModule, FileUploadModule, CommonModule, BtnDisabledDirective], }) export class UploaderComponent implements OnInit, AfterViewInit { diff --git a/src/app/submission/form/collection/submission-form-collection.component.html b/src/app/submission/form/collection/submission-form-collection.component.html index 16370a5873b..db33afc76ac 100644 --- a/src/app/submission/form/collection/submission-form-collection.component.html +++ b/src/app/submission/form/collection/submission-form-collection.component.html @@ -25,7 +25,7 @@ class="btn btn-outline-primary" (blur)="onClose()" (click)="onClose()" - [disabled]="(processingChange$ | async) || collectionModifiable === false || isReadonly" + [dsBtnDisabled]="(processingChange$ | async) || collectionModifiable === false || isReadonly" ngbDropdownToggle> {{ selectedCollectionName$ | async }} diff --git a/src/app/submission/form/collection/submission-form-collection.component.spec.ts b/src/app/submission/form/collection/submission-form-collection.component.spec.ts index 540c2ba561a..3a5ae8a18d3 100644 --- a/src/app/submission/form/collection/submission-form-collection.component.spec.ts +++ b/src/app/submission/form/collection/submission-form-collection.component.spec.ts @@ -30,6 +30,7 @@ import { JsonPatchOperationPathCombiner } from '../../../core/json-patch/builder import { JsonPatchOperationsBuilder } from '../../../core/json-patch/builder/json-patch-operations-builder'; import { Collection } from '../../../core/shared/collection.model'; import { SubmissionJsonPatchOperationsService } from '../../../core/submission/submission-json-patch-operations.service'; +import { BtnDisabledDirective } from '../../../shared/btn-disabled.directive'; import { ThemedCollectionDropdownComponent } from '../../../shared/collection-dropdown/themed-collection-dropdown.component'; import { DSONameServiceMock } from '../../../shared/mocks/dso-name.service.mock'; import { @@ -152,6 +153,7 @@ describe('SubmissionFormCollectionComponent Component', () => { TranslateModule.forRoot(), SubmissionFormCollectionComponent, TestComponent, + BtnDisabledDirective, ], providers: [ { provide: DSONameService, useValue: new DSONameServiceMock() }, @@ -275,7 +277,8 @@ describe('SubmissionFormCollectionComponent Component', () => { it('the dropdown button should be disabled when isReadonly is true', () => { comp.isReadonly = true; 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', () => { diff --git a/src/app/submission/form/collection/submission-form-collection.component.ts b/src/app/submission/form/collection/submission-form-collection.component.ts index 2aec1c9e30b..1736b474d5f 100644 --- a/src/app/submission/form/collection/submission-form-collection.component.ts +++ b/src/app/submission/form/collection/submission-form-collection.component.ts @@ -34,6 +34,7 @@ import { Collection } from '../../../core/shared/collection.model'; import { getFirstSucceededRemoteDataPayload } from '../../../core/shared/operators'; import { SubmissionObject } from '../../../core/submission/models/submission-object.model'; import { SubmissionJsonPatchOperationsService } from '../../../core/submission/submission-json-patch-operations.service'; +import { BtnDisabledDirective } from '../../../shared/btn-disabled.directive'; import { CollectionDropdownComponent } from '../../../shared/collection-dropdown/collection-dropdown.component'; import { ThemedCollectionDropdownComponent } from '../../../shared/collection-dropdown/themed-collection-dropdown.component'; import { @@ -57,6 +58,7 @@ import { SubmissionService } from '../../submission.service'; TranslateModule, NgbDropdownModule, ThemedCollectionDropdownComponent, + BtnDisabledDirective, ], }) export class SubmissionFormCollectionComponent implements OnDestroy, OnChanges, OnInit { diff --git a/src/app/submission/form/footer/submission-form-footer.component.html b/src/app/submission/form/footer/submission-form-footer.component.html index f1162cea4de..c860fb557b2 100644 --- a/src/app/submission/form/footer/submission-form-footer.component.html +++ b/src/app/submission/form/footer/submission-form-footer.component.html @@ -5,7 +5,7 @@ id="discard" [attr.data-test]="'discard' | dsBrowserOnly" class="btn btn-danger" - [disabled]="(processingSaveStatus | async) || (processingDepositStatus | async)" + [dsBtnDisabled]="(processingSaveStatus | async) || (processingDepositStatus | async)" (click)="$event.preventDefault();confirmDiscard(content)"> {{'submission.general.discard.submit' | translate}} @@ -28,7 +28,7 @@ class="btn btn-secondary" id="save" [attr.data-test]="'save' | dsBrowserOnly" - [disabled]="(processingSaveStatus | async) || (hasUnsavedModification | async) !== true" + [dsBtnDisabled]="(processingSaveStatus | async) || (hasUnsavedModification | async) !== true" (click)="save($event)"> {{'submission.general.save' | translate}} @@ -38,7 +38,7 @@ class="btn" id="saveForLater" [attr.data-test]="'save-for-later' | dsBrowserOnly" - [disabled]="(processingSaveStatus | async) || (processingDepositStatus | async)" + [dsBtnDisabled]="(processingSaveStatus | async) || (processingDepositStatus | async)" (click)="saveLater($event)"> {{'submission.general.save-later' | translate}} @@ -47,7 +47,7 @@ id="deposit" [attr.data-test]="'deposit' | dsBrowserOnly" class="btn btn-success" - [disabled]="(processingSaveStatus | async) || (processingDepositStatus | async)" + [dsBtnDisabled]="(processingSaveStatus | async) || (processingDepositStatus | async)" (click)="deposit($event)"> {{'submission.general.deposit' | translate}} diff --git a/src/app/submission/form/footer/submission-form-footer.component.spec.ts b/src/app/submission/form/footer/submission-form-footer.component.spec.ts index 4c6d04c9701..82bc309cc81 100644 --- a/src/app/submission/form/footer/submission-form-footer.component.spec.ts +++ b/src/app/submission/form/footer/submission-form-footer.component.spec.ts @@ -25,6 +25,7 @@ import { of as observableOf } from 'rxjs'; import { TestScheduler } from 'rxjs/testing'; import { SubmissionRestService } from '../../../core/submission/submission-rest.service'; +import { BtnDisabledDirective } from '../../../shared/btn-disabled.directive'; import { mockSubmissionId } from '../../../shared/mocks/submission.mock'; import { SubmissionRestServiceStub } from '../../../shared/testing/submission-rest-service.stub'; import { SubmissionServiceStub } from '../../../shared/testing/submission-service.stub'; @@ -51,6 +52,7 @@ describe('SubmissionFormFooterComponent', () => { TranslateModule.forRoot(), SubmissionFormFooterComponent, TestComponent, + BtnDisabledDirective, ], providers: [ { provide: SubmissionService, useValue: submissionServiceStub }, @@ -227,7 +229,8 @@ describe('SubmissionFormFooterComponent', () => { fixture.detectChanges(); 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', () => { @@ -236,7 +239,8 @@ describe('SubmissionFormFooterComponent', () => { fixture.detectChanges(); 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', () => { @@ -244,7 +248,8 @@ describe('SubmissionFormFooterComponent', () => { fixture.detectChanges(); 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', () => { @@ -252,7 +257,8 @@ describe('SubmissionFormFooterComponent', () => { fixture.detectChanges(); 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(); }); }); diff --git a/src/app/submission/form/footer/submission-form-footer.component.ts b/src/app/submission/form/footer/submission-form-footer.component.ts index 712a944a844..8645003783c 100644 --- a/src/app/submission/form/footer/submission-form-footer.component.ts +++ b/src/app/submission/form/footer/submission-form-footer.component.ts @@ -15,6 +15,7 @@ import { map } from 'rxjs/operators'; import { SubmissionRestService } from '../../../core/submission/submission-rest.service'; import { SubmissionScopeType } from '../../../core/submission/submission-scope-type'; +import { BtnDisabledDirective } from '../../../shared/btn-disabled.directive'; import { isNotEmpty } from '../../../shared/empty.util'; import { BrowserOnlyPipe } from '../../../shared/utils/browser-only.pipe'; import { SubmissionService } from '../../submission.service'; @@ -27,7 +28,7 @@ import { SubmissionService } from '../../submission.service'; styleUrls: ['./submission-form-footer.component.scss'], templateUrl: './submission-form-footer.component.html', standalone: true, - imports: [CommonModule, BrowserOnlyPipe, TranslateModule], + imports: [CommonModule, BrowserOnlyPipe, TranslateModule, BtnDisabledDirective], }) export class SubmissionFormFooterComponent implements OnChanges { diff --git a/src/app/submission/form/section-add/submission-form-section-add.component.html b/src/app/submission/form/section-add/submission-form-section-add.component.html index 0c7035c7f82..563044d270a 100644 --- a/src/app/submission/form/section-add/submission-form-section-add.component.html +++ b/src/app/submission/form/section-add/submission-form-section-add.component.html @@ -6,7 +6,7 @@ + diff --git a/src/app/submission/import-external/import-external-searchbar/submission-import-external-searchbar.component.ts b/src/app/submission/import-external/import-external-searchbar/submission-import-external-searchbar.component.ts index aace4de79a8..eb7a703cc2d 100644 --- a/src/app/submission/import-external/import-external-searchbar/submission-import-external-searchbar.component.ts +++ b/src/app/submission/import-external/import-external-searchbar/submission-import-external-searchbar.component.ts @@ -36,6 +36,7 @@ import { getFirstSucceededRemoteDataPayload, } from '../../../core/shared/operators'; import { PageInfo } from '../../../core/shared/page-info.model'; +import { BtnDisabledDirective } from '../../../shared/btn-disabled.directive'; import { hasValue } from '../../../shared/empty.util'; import { HostWindowService } from '../../../shared/host-window.service'; import { createSuccessfulRemoteDataObject } from '../../../shared/remote-data.utils'; @@ -70,6 +71,7 @@ export interface ExternalSourceData { InfiniteScrollModule, NgbDropdownModule, FormsModule, + BtnDisabledDirective, ], standalone: true, }) diff --git a/src/app/submission/sections/cc-license/submission-section-cc-licenses.component.ts b/src/app/submission/sections/cc-license/submission-section-cc-licenses.component.ts index cc1925a4a66..a633e640abf 100644 --- a/src/app/submission/sections/cc-license/submission-section-cc-licenses.component.ts +++ b/src/app/submission/sections/cc-license/submission-section-cc-licenses.component.ts @@ -46,6 +46,7 @@ import { import { WorkspaceitemSectionCcLicenseObject } from '../../../core/submission/models/workspaceitem-section-cc-license.model'; import { SubmissionCcLicenseDataService } from '../../../core/submission/submission-cc-license-data.service'; import { SubmissionCcLicenseUrlDataService } from '../../../core/submission/submission-cc-license-url-data.service'; +import { BtnDisabledDirective } from '../../../shared/btn-disabled.directive'; import { DsSelectComponent } from '../../../shared/ds-select/ds-select.component'; import { isNotEmpty } from '../../../shared/empty.util'; import { ThemedLoadingComponent } from '../../../shared/loading/themed-loading.component'; @@ -73,6 +74,7 @@ import { SectionsType } from '../sections-type'; NgbDropdownModule, FormsModule, InfiniteScrollModule, + BtnDisabledDirective, ], standalone: true, }) diff --git a/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.html b/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.html index 0cb79e51cb0..b03504c6beb 100644 --- a/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.html +++ b/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.html @@ -1,7 +1,7 @@
diff --git a/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.ts b/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.ts index 24488d1adb3..79b3d3a5652 100644 --- a/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.ts +++ b/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.ts @@ -38,6 +38,7 @@ import { JsonPatchOperationPathCombiner } from '../../../../../core/json-patch/b import { JsonPatchOperationsBuilder } from '../../../../../core/json-patch/builder/json-patch-operations-builder'; import { WorkspaceitemSectionUploadFileObject } from '../../../../../core/submission/models/workspaceitem-section-upload-file.model'; import { SubmissionJsonPatchOperationsService } from '../../../../../core/submission/submission-json-patch-operations.service'; +import { BtnDisabledDirective } from '../../../../../shared/btn-disabled.directive'; import { dateToISOFormat } from '../../../../../shared/date.util'; import { hasNoValue, @@ -80,6 +81,7 @@ import { FormComponent, NgIf, TranslateModule, + BtnDisabledDirective, ], standalone: true, }) diff --git a/src/app/submission/sections/upload/file/section-upload-file.component.html b/src/app/submission/sections/upload/file/section-upload-file.component.html index 4698fdf68a6..cd045426769 100644 --- a/src/app/submission/sections/upload/file/section-upload-file.component.html +++ b/src/app/submission/sections/upload/file/section-upload-file.component.html @@ -35,7 +35,7 @@ -
- \ No newline at end of file + diff --git a/src/app/system-wide-alert/alert-form/system-wide-alert-form.component.ts b/src/app/system-wide-alert/alert-form/system-wide-alert-form.component.ts index b30e864fa12..ea1c0a55f0c 100644 --- a/src/app/system-wide-alert/alert-form/system-wide-alert-form.component.ts +++ b/src/app/system-wide-alert/alert-form/system-wide-alert-form.component.ts @@ -43,6 +43,7 @@ import { RemoteData } from '../../core/data/remote-data'; import { RequestService } from '../../core/data/request.service'; import { SystemWideAlertDataService } from '../../core/data/system-wide-alert-data.service'; import { getFirstCompletedRemoteData } from '../../core/shared/operators'; +import { BtnDisabledDirective } from '../../shared/btn-disabled.directive'; import { hasValue, isNotEmpty, @@ -59,7 +60,7 @@ import { SystemWideAlert } from '../system-wide-alert.model'; styleUrls: ['./system-wide-alert-form.component.scss'], templateUrl: './system-wide-alert-form.component.html', standalone: true, - imports: [FormsModule, ReactiveFormsModule, UiSwitchModule, NgIf, NgbDatepickerModule, NgbTimepickerModule, AsyncPipe, TranslateModule], + imports: [FormsModule, ReactiveFormsModule, UiSwitchModule, NgIf, NgbDatepickerModule, NgbTimepickerModule, AsyncPipe, TranslateModule, BtnDisabledDirective], }) export class SystemWideAlertFormComponent implements OnInit { diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/reviewers-list/reviewers-list.component.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/reviewers-list/reviewers-list.component.ts index 5ae3a13f313..c43fabdb83a 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/reviewers-list/reviewers-list.component.ts +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/reviewers-list/reviewers-list.component.ts @@ -44,6 +44,7 @@ import { EpersonDtoModel } from '../../../../core/eperson/models/eperson-dto.mod import { Group } from '../../../../core/eperson/models/group.model'; import { PaginationService } from '../../../../core/pagination/pagination.service'; import { getFirstSucceededRemoteDataPayload } from '../../../../core/shared/operators'; +import { BtnDisabledDirective } from '../../../../shared/btn-disabled.directive'; import { ContextHelpDirective } from '../../../../shared/context-help.directive'; import { hasValue } from '../../../../shared/empty.util'; import { NotificationsService } from '../../../../shared/notifications/notifications.service'; @@ -76,6 +77,7 @@ enum SubKey { RouterLink, NgClass, NgForOf, + BtnDisabledDirective, ], }) export class ReviewersListComponent extends MembersListComponent implements OnInit, OnChanges, OnDestroy { diff --git a/src/styles/_global-styles.scss b/src/styles/_global-styles.scss index 4934ec3d350..b1a6c62beec 100644 --- a/src/styles/_global-styles.scss +++ b/src/styles/_global-styles.scss @@ -463,6 +463,17 @@ ngb-accordion { 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 .mt-cs { margin-top: var(--ds-content-spacing); } .mb-cs { margin-bottom: var(--ds-content-spacing); } diff --git a/src/themes/custom/app/dso-shared/dso-edit-metadata/dso-edit-metadata.component.ts b/src/themes/custom/app/dso-shared/dso-edit-metadata/dso-edit-metadata.component.ts index a7437c04081..f74a73a4b88 100644 --- a/src/themes/custom/app/dso-shared/dso-edit-metadata/dso-edit-metadata.component.ts +++ b/src/themes/custom/app/dso-shared/dso-edit-metadata/dso-edit-metadata.component.ts @@ -13,6 +13,7 @@ import { DsoEditMetadataValueComponent } from '../../../../../app/dso-shared/dso import { DsoEditMetadataValueHeadersComponent } from '../../../../../app/dso-shared/dso-edit-metadata/dso-edit-metadata-value-headers/dso-edit-metadata-value-headers.component'; import { MetadataFieldSelectorComponent } from '../../../../../app/dso-shared/dso-edit-metadata/metadata-field-selector/metadata-field-selector.component'; import { AlertComponent } from '../../../../../app/shared/alert/alert.component'; +import { BtnDisabledDirective } from '../../../../../app/shared/btn-disabled.directive'; import { ThemedLoadingComponent } from '../../../../../app/shared/loading/themed-loading.component'; @Component({ @@ -22,7 +23,7 @@ import { ThemedLoadingComponent } from '../../../../../app/shared/loading/themed // templateUrl: './dso-edit-metadata.component.html', templateUrl: '../../../../../app/dso-shared/dso-edit-metadata/dso-edit-metadata.component.html', standalone: true, - imports: [NgIf, DsoEditMetadataHeadersComponent, MetadataFieldSelectorComponent, DsoEditMetadataValueHeadersComponent, DsoEditMetadataValueComponent, NgFor, DsoEditMetadataFieldValuesComponent, AlertComponent, ThemedLoadingComponent, AsyncPipe, TranslateModule], + imports: [NgIf, DsoEditMetadataHeadersComponent, MetadataFieldSelectorComponent, DsoEditMetadataValueHeadersComponent, DsoEditMetadataValueComponent, NgFor, DsoEditMetadataFieldValuesComponent, AlertComponent, ThemedLoadingComponent, AsyncPipe, TranslateModule, BtnDisabledDirective], }) export class DsoEditMetadataComponent extends BaseComponent { } diff --git a/src/themes/custom/app/forgot-password/forgot-password-form/forgot-password-form.component.ts b/src/themes/custom/app/forgot-password/forgot-password-form/forgot-password-form.component.ts index 9656e7b85ce..37fb3834294 100644 --- a/src/themes/custom/app/forgot-password/forgot-password-form/forgot-password-form.component.ts +++ b/src/themes/custom/app/forgot-password/forgot-password-form/forgot-password-form.component.ts @@ -7,6 +7,7 @@ import { TranslateModule } from '@ngx-translate/core'; import { ForgotPasswordFormComponent as BaseComponent } from '../../../../../app/forgot-password/forgot-password-form/forgot-password-form.component'; import { ProfilePageSecurityFormComponent } from '../../../../../app/profile-page/profile-page-security-form/profile-page-security-form.component'; +import { BtnDisabledDirective } from '../../../../../app/shared/btn-disabled.directive'; import { BrowserOnlyPipe } from '../../../../../app/shared/utils/browser-only.pipe'; @Component({ @@ -22,6 +23,7 @@ import { BrowserOnlyPipe } from '../../../../../app/shared/utils/browser-only.pi ProfilePageSecurityFormComponent, AsyncPipe, NgIf, + BtnDisabledDirective, ], }) /** diff --git a/src/themes/custom/app/info/end-user-agreement/end-user-agreement.component.ts b/src/themes/custom/app/info/end-user-agreement/end-user-agreement.component.ts index 05f22522163..2b78006f650 100644 --- a/src/themes/custom/app/info/end-user-agreement/end-user-agreement.component.ts +++ b/src/themes/custom/app/info/end-user-agreement/end-user-agreement.component.ts @@ -4,6 +4,7 @@ import { TranslateModule } from '@ngx-translate/core'; import { EndUserAgreementComponent as BaseComponent } from '../../../../../app/info/end-user-agreement/end-user-agreement.component'; import { EndUserAgreementContentComponent } from '../../../../../app/info/end-user-agreement/end-user-agreement-content/end-user-agreement-content.component'; +import { BtnDisabledDirective } from '../../../../../app/shared/btn-disabled.directive'; @Component({ selector: 'ds-themed-end-user-agreement', @@ -12,7 +13,7 @@ import { EndUserAgreementContentComponent } from '../../../../../app/info/end-us // templateUrl: './end-user-agreement.component.html' templateUrl: '../../../../../app/info/end-user-agreement/end-user-agreement.component.html', standalone: true, - imports: [EndUserAgreementContentComponent, FormsModule, TranslateModule], + imports: [EndUserAgreementContentComponent, FormsModule, TranslateModule, BtnDisabledDirective], }) /** diff --git a/src/themes/custom/app/info/feedback/feedback-form/feedback-form.component.ts b/src/themes/custom/app/info/feedback/feedback-form/feedback-form.component.ts index b3cd90e73ed..38356671118 100644 --- a/src/themes/custom/app/info/feedback/feedback-form/feedback-form.component.ts +++ b/src/themes/custom/app/info/feedback/feedback-form/feedback-form.component.ts @@ -7,6 +7,7 @@ import { import { TranslateModule } from '@ngx-translate/core'; import { FeedbackFormComponent as BaseComponent } from '../../../../../../app/info/feedback/feedback-form/feedback-form.component'; +import { BtnDisabledDirective } from '../../../../../../app/shared/btn-disabled.directive'; import { ErrorComponent } from '../../../../../../app/shared/error/error.component'; @Component({ @@ -16,7 +17,7 @@ import { ErrorComponent } from '../../../../../../app/shared/error/error.compone // styleUrls: ['./feedback-form.component.scss'], styleUrls: ['../../../../../../app/info/feedback/feedback-form/feedback-form.component.scss'], standalone: true, - imports: [FormsModule, ReactiveFormsModule, NgIf, ErrorComponent, TranslateModule], + imports: [FormsModule, ReactiveFormsModule, NgIf, ErrorComponent, TranslateModule, BtnDisabledDirective], }) export class FeedbackFormComponent extends BaseComponent { } diff --git a/src/themes/custom/app/item-page/media-viewer/media-viewer-video/media-viewer-video.component.ts b/src/themes/custom/app/item-page/media-viewer/media-viewer-video/media-viewer-video.component.ts index 5c6c7f35072..0efa0130640 100644 --- a/src/themes/custom/app/item-page/media-viewer/media-viewer-video/media-viewer-video.component.ts +++ b/src/themes/custom/app/item-page/media-viewer/media-viewer-video/media-viewer-video.component.ts @@ -7,6 +7,7 @@ import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'; import { TranslateModule } from '@ngx-translate/core'; import { MediaViewerVideoComponent as BaseComponent } from '../../../../../../app/item-page/media-viewer/media-viewer-video/media-viewer-video.component'; +import { BtnDisabledDirective } from '../../../../../../app/shared/btn-disabled.directive'; @Component({ selector: 'ds-themed-media-viewer-video', @@ -20,6 +21,7 @@ import { MediaViewerVideoComponent as BaseComponent } from '../../../../../../ap NgbDropdownModule, TranslateModule, NgIf, + BtnDisabledDirective, ], }) export class MediaViewerVideoComponent extends BaseComponent { diff --git a/src/themes/custom/app/register-email-form/register-email-form.component.ts b/src/themes/custom/app/register-email-form/register-email-form.component.ts index 44426c527fd..d1f4a6d3ffe 100644 --- a/src/themes/custom/app/register-email-form/register-email-form.component.ts +++ b/src/themes/custom/app/register-email-form/register-email-form.component.ts @@ -12,13 +12,14 @@ import { AlertComponent } from 'src/app/shared/alert/alert.component'; import { GoogleRecaptchaComponent } from 'src/app/shared/google-recaptcha/google-recaptcha.component'; import { RegisterEmailFormComponent as BaseComponent } from '../../../../app/register-email-form/register-email-form.component'; +import { BtnDisabledDirective } from '../../../../app/shared/btn-disabled.directive'; @Component({ selector: 'ds-themed-register-email-form', // templateUrl: './register-email-form.component.html', templateUrl: '../../../../app/register-email-form/register-email-form.component.html', standalone: true, - imports: [NgIf, FormsModule, ReactiveFormsModule, AlertComponent, GoogleRecaptchaComponent, AsyncPipe, TranslateModule], + imports: [NgIf, FormsModule, ReactiveFormsModule, AlertComponent, GoogleRecaptchaComponent, AsyncPipe, TranslateModule, BtnDisabledDirective], }) export class RegisterEmailFormComponent extends BaseComponent { } diff --git a/src/themes/custom/app/register-page/create-profile/create-profile.component.ts b/src/themes/custom/app/register-page/create-profile/create-profile.component.ts index ac2491cc4ee..7c1ff908c37 100644 --- a/src/themes/custom/app/register-page/create-profile/create-profile.component.ts +++ b/src/themes/custom/app/register-page/create-profile/create-profile.component.ts @@ -9,6 +9,7 @@ import { TranslateModule } from '@ngx-translate/core'; import { ProfilePageSecurityFormComponent } from '../../../../../app/profile-page/profile-page-security-form/profile-page-security-form.component'; import { CreateProfileComponent as BaseComponent } from '../../../../../app/register-page/create-profile/create-profile.component'; +import { BtnDisabledDirective } from '../../../../../app/shared/btn-disabled.directive'; /** * Component that renders the create profile page to be used by a user registering through a token @@ -27,6 +28,7 @@ import { CreateProfileComponent as BaseComponent } from '../../../../../app/regi AsyncPipe, ReactiveFormsModule, NgForOf, + BtnDisabledDirective, ], }) export class CreateProfileComponent extends BaseComponent { diff --git a/src/themes/custom/app/request-copy/email-request-copy/email-request-copy.component.ts b/src/themes/custom/app/request-copy/email-request-copy/email-request-copy.component.ts index 1876b134a26..cf4981d289b 100644 --- a/src/themes/custom/app/request-copy/email-request-copy/email-request-copy.component.ts +++ b/src/themes/custom/app/request-copy/email-request-copy/email-request-copy.component.ts @@ -7,6 +7,8 @@ import { FormsModule } from '@angular/forms'; import { TranslateModule } from '@ngx-translate/core'; import { EmailRequestCopyComponent as BaseComponent } from 'src/app/request-copy/email-request-copy/email-request-copy.component'; +import { BtnDisabledDirective } from '../../../../../app/shared/btn-disabled.directive'; + @Component({ selector: 'ds-themed-email-request-copy', // styleUrls: ['./email-request-copy.component.scss'], @@ -14,7 +16,7 @@ import { EmailRequestCopyComponent as BaseComponent } from 'src/app/request-copy // templateUrl: './email-request-copy.component.html', templateUrl: './../../../../../app/request-copy/email-request-copy/email-request-copy.component.html', standalone: true, - imports: [FormsModule, NgClass, NgIf, TranslateModule], + imports: [FormsModule, NgClass, NgIf, TranslateModule, BtnDisabledDirective], }) export class EmailRequestCopyComponent extends BaseComponent { diff --git a/src/themes/custom/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/external-source-tab/external-source-entry-import-modal/external-source-entry-import-modal.component.ts b/src/themes/custom/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/external-source-tab/external-source-entry-import-modal/external-source-entry-import-modal.component.ts index 424adb79429..aec7bfd6db2 100644 --- a/src/themes/custom/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/external-source-tab/external-source-entry-import-modal/external-source-entry-import-modal.component.ts +++ b/src/themes/custom/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/external-source-tab/external-source-entry-import-modal/external-source-entry-import-modal.component.ts @@ -5,6 +5,7 @@ import { import { Component } from '@angular/core'; import { TranslateModule } from '@ngx-translate/core'; +import { BtnDisabledDirective } from '../../../../../../../../../../app/shared/btn-disabled.directive'; import { ExternalSourceEntryImportModalComponent as BaseComponent } from '../../../../../../../../../../app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/external-source-tab/external-source-entry-import-modal/external-source-entry-import-modal.component'; import { ThemedSearchResultsComponent } from '../../../../../../../../../../app/shared/search/search-results/themed-search-results.component'; @@ -19,6 +20,7 @@ import { ThemedSearchResultsComponent } from '../../../../../../../../../../app/ ThemedSearchResultsComponent, NgIf, AsyncPipe, + BtnDisabledDirective, ], }) export class ExternalSourceEntryImportModalComponent extends BaseComponent { diff --git a/src/themes/custom/app/submission/sections/upload/file/section-upload-file.component.ts b/src/themes/custom/app/submission/sections/upload/file/section-upload-file.component.ts index 13f5f976503..cea515102ac 100644 --- a/src/themes/custom/app/submission/sections/upload/file/section-upload-file.component.ts +++ b/src/themes/custom/app/submission/sections/upload/file/section-upload-file.component.ts @@ -6,6 +6,7 @@ import { Component } from '@angular/core'; import { TranslateModule } from '@ngx-translate/core'; import { SubmissionSectionUploadFileComponent as BaseComponent } from 'src/app/submission/sections/upload/file/section-upload-file.component'; +import { BtnDisabledDirective } from '../../../../../../../app/shared/btn-disabled.directive'; import { ThemedFileDownloadLinkComponent } from '../../../../../../../app/shared/file-download-link/themed-file-download-link.component'; import { FileSizePipe } from '../../../../../../../app/shared/utils/file-size-pipe'; import { SubmissionSectionUploadFileViewComponent } from '../../../../../../../app/submission/sections/upload/file/view/section-upload-file-view.component'; @@ -27,6 +28,7 @@ import { SubmissionSectionUploadFileViewComponent } from '../../../../../../../a AsyncPipe, ThemedFileDownloadLinkComponent, FileSizePipe, + BtnDisabledDirective, ], }) export class SubmissionSectionUploadFileComponent