diff --git a/frontend/src/app/features/work-packages/components/wp-edit-form/table-edit-form.ts b/frontend/src/app/features/work-packages/components/wp-edit-form/table-edit-form.ts index 4429551757b..1a88b34a2ad 100644 --- a/frontend/src/app/features/work-packages/components/wp-edit-form/table-edit-form.ts +++ b/frontend/src/app/features/work-packages/components/wp-edit-form/table-edit-form.ts @@ -122,7 +122,7 @@ export class TableEditForm extends EditForm { td.removeClass(editModeClassName); if (focus) { - this.FocusHelper.focusElement(cell); + this.FocusHelper.focus(cell[0]); } } } diff --git a/frontend/src/app/features/work-packages/components/wp-fast-table/handlers/state/selection-transformer.ts b/frontend/src/app/features/work-packages/components/wp-fast-table/handlers/state/selection-transformer.ts index 1fac244ade7..0bc61c2daeb 100644 --- a/frontend/src/app/features/work-packages/components/wp-fast-table/handlers/state/selection-transformer.ts +++ b/frontend/src/app/features/work-packages/components/wp-fast-table/handlers/state/selection-transformer.ts @@ -34,7 +34,7 @@ export class SelectionTransformer { const element = locateTableRow(wpId); if (element.length) { scrollTableRowIntoView(wpId); - this.FocusHelper.focusElement(element, true); + this.FocusHelper.focus(element[0]); } }); }); diff --git a/frontend/src/app/shared/components/datepicker/multi-date-modal/multi-date.modal.ts b/frontend/src/app/shared/components/datepicker/multi-date-modal/multi-date.modal.ts index 6011c1789f5..3da29a5d8ca 100644 --- a/frontend/src/app/shared/components/datepicker/multi-date-modal/multi-date.modal.ts +++ b/frontend/src/app/shared/components/datepicker/multi-date-modal/multi-date.modal.ts @@ -79,6 +79,7 @@ import { } from 'core-app/shared/components/datepicker/helpers/date-modal.helpers'; import { castArray } from 'lodash'; import { WeekdayService } from 'core-app/core/days/weekday.service'; +import { FocusHelperService } from 'core-app/shared/directives/focus/focus-helper'; export type DateKeys = 'start'|'end'; export type DateFields = DateKeys|'duration'; diff --git a/frontend/src/app/shared/directives/a11y/keyboard-shortcut.service.ts b/frontend/src/app/shared/directives/a11y/keyboard-shortcut.service.ts index 6a71de21a88..df0baee895c 100644 --- a/frontend/src/app/shared/directives/a11y/keyboard-shortcut.service.ts +++ b/frontend/src/app/shared/directives/a11y/keyboard-shortcut.service.ts @@ -95,7 +95,7 @@ export class KeyboardShortcutService { if (elem.is('input') || elem.attr('id') === 'global-search-input') { // timeout with delay so that the key is not // triggered on the input - setTimeout(() => this.FocusHelper.focus(elem), 200); + setTimeout(() => this.FocusHelper.focus(elem[0]), 200); } else if (elem.is('[href]')) { this.clickLink(elem[0] as HTMLLinkElement); } else { diff --git a/frontend/src/app/shared/directives/focus/autofocus.directive.ts b/frontend/src/app/shared/directives/focus/autofocus.directive.ts index b056a45b616..827ca28452e 100644 --- a/frontend/src/app/shared/directives/focus/autofocus.directive.ts +++ b/frontend/src/app/shared/directives/focus/autofocus.directive.ts @@ -12,8 +12,6 @@ import { FocusHelperService } from './focus-helper'; export class AutofocusDirective implements AfterViewInit { @Input('opAutofocus') public condition = true; - @Input('opAutofocusPriority') priority?:number = 0; - constructor( readonly FocusHelper:FocusHelperService, readonly elementRef:ElementRef, @@ -25,8 +23,7 @@ export class AutofocusDirective implements AfterViewInit { private updateFocus() { if (this.condition) { - const element = jQuery(this.elementRef.nativeElement); - this.FocusHelper.focusElement(element, this.priority); + this.FocusHelper.focus(this.elementRef.nativeElement); } } } diff --git a/frontend/src/app/shared/directives/focus/focus-helper.ts b/frontend/src/app/shared/directives/focus/focus-helper.ts index a5c63ebeb5d..042284f9888 100644 --- a/frontend/src/app/shared/directives/focus/focus-helper.ts +++ b/frontend/src/app/shared/directives/focus/focus-helper.ts @@ -30,80 +30,28 @@ import { Injectable } from '@angular/core'; @Injectable({ providedIn: 'root' }) export class FocusHelperService { - private minimumOffsetForNewSwitchInMs = 100; + private static FOCUSABLE_SELECTORS = 'a, button, input, [tabindex], select, textarea'; - private lastFocusSwitch = -this.minimumOffsetForNewSwitchInMs; + public getFocusableElement(element:HTMLElement):HTMLElement { + const focusser = element.querySelector('input.ui-select-focusser'); - private lastPriority = -1; - - private static FOCUSABLE_SELECTORS = 'a, button, :input, [tabindex], select'; - - public throttleAndCheckIfAllowedFocusChangeBasedOnTimeout() { - const allowFocusSwitch = (Date.now() - this.lastFocusSwitch) >= this.minimumOffsetForNewSwitchInMs; - - // Always update so that a chain of focus-change-requests gets considered as one - this.lastFocusSwitch = Date.now(); - - return allowFocusSwitch; - } - - public checkIfAllowedFocusChange(priority?:any) { - const checkTimeout = this.throttleAndCheckIfAllowedFocusChangeBasedOnTimeout(); - - if (checkTimeout) { - // new timeout window -> reset priority - this.lastPriority = -1; - return checkTimeout; - } - - if (priority > this.lastPriority) { - // within timeout window - this.lastPriority = priority; - return true; - } - - return checkTimeout; - } - - public getFocusableElement(element:JQuery) { - const focusser = element.find('input.ui-select-focusser'); - - if (focusser.length > 0) { - return focusser[0]; + if (focusser) { + return focusser; } let focusable = element; - if (!element.is(FocusHelperService.FOCUSABLE_SELECTORS)) { - focusable = element.find(FocusHelperService.FOCUSABLE_SELECTORS); + if (!element.matches(FocusHelperService.FOCUSABLE_SELECTORS)) { + focusable = element.querySelector(FocusHelperService.FOCUSABLE_SELECTORS) || element; } - return focusable[0]; + return focusable; } - public focus(element:JQuery) { - const focusable = jQuery(this.getFocusableElement(element)); - const $focusable = jQuery(focusable); - const isDisabled = $focusable.is('[disabled]'); - - if (isDisabled && !$focusable.attr('ng-disabled')) { - $focusable.prop('disabled', false); - } - - focusable.focus(); - - if (isDisabled && !$focusable.attr('ng-disabled')) { - $focusable.prop('disabled', true); - } - } - - public focusElement(element:JQuery, priority?:any) { - if (!this.checkIfAllowedFocusChange(priority)) { - return; - } - + public focus(element:HTMLElement):void { setTimeout(() => { - this.focus(element); + const focusable = this.getFocusableElement(element); + focusable?.focus(); }, 10); } } diff --git a/spec/features/work_packages/table/duration_field_spec.rb b/spec/features/work_packages/table/duration_field_spec.rb index 5bcb1f26ade..a3eaab536ba 100644 --- a/spec/features/work_packages/table/duration_field_spec.rb +++ b/spec/features/work_packages/table/duration_field_spec.rb @@ -39,6 +39,6 @@ describe 'Duration field in the work package table', date_field.expect_duration_highlighted expect(page).to have_focus_on('[data-qa-selector="op-datepicker-modal--duration-field"] input[name="duration"]') - expect(container).to have_field('duration', with: '4', wait: 10) + expect(page).to have_field('duration', with: '4', wait: 10) end end