Skip to content

Commit 27ca162

Browse files
r-farkhutdinovRuslan Farkhutdinov
andauthored
Overlay: Should focus next focusable element if target is deleted (T1248343) (DevExpress#29342)
Co-authored-by: Ruslan Farkhutdinov <[email protected]>
1 parent 362692b commit 27ca162

File tree

2 files changed

+72
-21
lines changed

2 files changed

+72
-21
lines changed

packages/devextreme/js/__internal/ui/overlay/m_overlay.ts

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -783,47 +783,47 @@ class Overlay<
783783
_findTabbableBounds(): { first: dxElementWrapper | null; last: dxElementWrapper | null } {
784784
const $elements = this._$wrapper.find('*');
785785
const elementsCount = $elements.length - 1;
786-
const result = { first: null, last: null };
787786

788-
for (let i = 0; i <= elementsCount; i++) {
787+
let first = null;
788+
let last = null;
789+
790+
for (let i = 0; i <= elementsCount; i += 1) {
789791
// @ts-expect-error ts-error
790-
if (!result.first && $elements.eq(i).is(tabbable)) {
792+
if (!first && $elements.eq(i).is(tabbable)) {
791793
// @ts-expect-error ts-error
792-
result.first = $elements.eq(i);
794+
first = $elements.eq(i);
793795
}
794796
// @ts-expect-error ts-error
795-
if (!result.last && $elements.eq(elementsCount - i).is(tabbable)) {
797+
if (!last && $elements.eq(elementsCount - i).is(tabbable)) {
796798
// @ts-expect-error ts-error
797-
result.last = $elements.eq(elementsCount - i);
799+
last = $elements.eq(elementsCount - i);
798800
}
799801

800-
if (result.first && result.last) {
802+
if (first && last) {
801803
break;
802804
}
803805
}
804806

805-
return result;
807+
return { first, last };
806808
}
807809

808-
_tabKeyHandler(e): void {
810+
_tabKeyHandler(e: KeyboardEvent): void {
809811
if (normalizeKeyName(e) !== TAB_KEY || !this._isTopOverlay()) {
810812
return;
811813
}
812814

813-
const tabbableElements = this._findTabbableBounds();
815+
const wrapper = this._$wrapper.get(0) as HTMLElement;
816+
const activeElement = domAdapter.getActiveElement(wrapper);
814817

815-
const $firstTabbable = tabbableElements.first;
816-
const $lastTabbable = tabbableElements.last;
817-
// @ts-expect-error ts-error
818-
const isTabOnLast = !e.shiftKey && e.target === $lastTabbable.get(0);
819-
// @ts-expect-error ts-error
820-
const isShiftTabOnFirst = e.shiftKey && e.target === $firstTabbable.get(0);
821-
// @ts-expect-error ts-error
822-
const isEmptyTabList = tabbableElements.length === 0;
823-
const isOutsideTarget = !domUtils.contains(this._$wrapper.get(0), e.target);
818+
const { first: $firstTabbable, last: $lastTabbable } = this._findTabbableBounds();
819+
820+
const isTabOnLast = !e.shiftKey && activeElement === $lastTabbable?.get(0);
821+
const isShiftTabOnFirst = e.shiftKey && activeElement === $firstTabbable?.get(0);
822+
const isOutsideTarget = !domUtils.contains(wrapper, activeElement);
823+
824+
const shouldPreventDefault = isTabOnLast || isShiftTabOnFirst || isOutsideTarget;
824825

825-
if (isTabOnLast || isShiftTabOnFirst
826-
|| isEmptyTabList || isOutsideTarget) {
826+
if (shouldPreventDefault) {
827827
e.preventDefault();
828828

829829
const $focusElement = e.shiftKey ? $lastTabbable : $firstTabbable;

packages/devextreme/testing/tests/DevExpress.ui.widgets/overlay.tests.js

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3446,6 +3446,57 @@ testModule('focus policy', {
34463446

34473447
assert.strictEqual(contentFocusHandler.callCount, 1, 'focus has been triggered once from keyboardMock');
34483448
});
3449+
3450+
test('tab behavior should remain default when event.target is removed from DOM (T1248343)', function(assert) {
3451+
const overlay = new Overlay($('<div>').appendTo('#qunit-fixture'), {
3452+
visible: true,
3453+
shading: true,
3454+
contentTemplate: $('<div>')
3455+
.append('<input class="templateField">')
3456+
.append('<input class="anotherField">')
3457+
.append('<input class="thirdField">'),
3458+
});
3459+
3460+
const $content = $(overlay.$content());
3461+
const $templateField = $content.find('.templateField');
3462+
const $anotherField = $content.find('.anotherField');
3463+
3464+
$templateField.trigger('focus');
3465+
3466+
assert.strictEqual(getActiveElement(), $templateField.get(0), 'templatedField is focused');
3467+
3468+
$templateField.remove();
3469+
3470+
$anotherField.trigger('focus');
3471+
3472+
const tabEvent = $.Event('keydown', { key: 'Tab', shiftKey: false, target: $templateField.get(0) });
3473+
3474+
$(document).trigger(tabEvent);
3475+
assert.strictEqual(tabEvent.isDefaultPrevented(), false, 'event is not prevented');
3476+
});
3477+
3478+
test('tab event is prevented when removed target was outside overlay', function(assert) {
3479+
const overlay = new Overlay($('<div>').appendTo('#qunit-fixture'), {
3480+
visible: true,
3481+
shading: true,
3482+
contentTemplate: $('<div>')
3483+
.append('<input class="templateField">')
3484+
});
3485+
3486+
const $content = $(overlay.$content());
3487+
const $templateField = $content.find('.templateField');
3488+
const $outerField = $('<input class="outerField">').appendTo('#qunit-fixture');
3489+
$outerField.trigger('focus');
3490+
assert.strictEqual(getActiveElement(), $outerField.get(0), 'outerField is focused');
3491+
3492+
$outerField.remove();
3493+
3494+
const tabEventOutside = $.Event('keydown', { key: 'Tab', shiftKey: false, target: $outerField.get(0) });
3495+
$(document).trigger(tabEventOutside);
3496+
3497+
assert.strictEqual(tabEventOutside.isDefaultPrevented(), true, 'event is prevented when target removed outside overlay');
3498+
assert.strictEqual(getActiveElement(), $templateField.get(0), 'focus is returned to Popup');
3499+
});
34493500
});
34503501

34513502
testModule('preventScrollEvents', () => {

0 commit comments

Comments
 (0)