Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 21 additions & 3 deletions packages/devextreme/js/__internal/ui/overlay/overlay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -565,21 +565,39 @@ class Overlay<
super._initTemplates();
}

_isTopOverlay(): boolean {
_isTopFocusableOverlay(): boolean {
const overlayStack = this._overlayStack();

for (let i = overlayStack.length - 1; i >= 0; i -= 1) {
const tabbableElements = overlayStack[i]._findTabbableBounds();

if (tabbableElements.$first || tabbableElements.$last) {
// @ts-ignore expected: types Overlay<OverlayProperties> and this have no overlap
return overlayStack[i] === this;
}
}

return false;
}

_isLastInOverlayStack(): boolean {
const overlayStack = this._overlayStack();

return overlayStack[overlayStack.length - 1] === this;
}

_handlesDocumentEscapeKey(): boolean {
return false;
}

_isEscapeHandledByOverlayAbove(): boolean {
const overlayStack = this._overlayStack();
const index = overlayStack.indexOf(this);

return overlayStack
.slice(index + 1)
.some((overlay) => overlay._handlesDocumentEscapeKey());
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
_overlayStack(): Overlay<any>[] {
return OVERLAY_STACK;
Expand Down Expand Up @@ -1045,7 +1063,7 @@ class Overlay<
}

_tabKeyHandler(e: KeyboardEvent): void {
if (normalizeKeyName(e) !== TAB_KEY || !this._isTopOverlay()) {
if (normalizeKeyName(e) !== TAB_KEY || !this._isTopFocusableOverlay()) {
return;
}

Expand Down
13 changes: 7 additions & 6 deletions packages/devextreme/js/__internal/ui/popover/popover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,18 +215,19 @@ class Popover<

_initEscapeKeyHandler(): void {
this._documentEscapeKeyHandler = (e: KeyboardEvent): void => {
const { visible } = this.option();

const overlayStack = this._overlayStack();
const isTopOverlay = overlayStack[overlayStack.length - 1] === this;

if (normalizeKeyName(e) === ESC_KEY_NAME && visible && isTopOverlay) {
if (normalizeKeyName(e) === ESC_KEY_NAME && this._handlesDocumentEscapeKey()) {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.hide();
}
};
}

_handlesDocumentEscapeKey(): boolean {
const { visible } = this.option();

return Boolean(visible) && this._isLastInOverlayStack();
}

_attachEscapeKeyHandler(): void {
const eventName = addNamespace('keydown', this.NAME as string);

Expand Down
12 changes: 8 additions & 4 deletions packages/devextreme/js/__internal/ui/popup/popup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,10 +246,14 @@ class Popup<
const e = options.originalEvent;
const $target = $(e.target);

if (this._$content && !$target.is(this._$content)
&& options.keyName === ESC_KEY_NAME
&& !e.isDefaultPrevented()
&& !_ignoreCloseOnChildEscape) {
const shouldCloseOnChildEscape = !!this._$content
&& !$target.is(this._$content)
&& options.keyName === ESC_KEY_NAME
&& !e.isDefaultPrevented()
&& !_ignoreCloseOnChildEscape
&& !this._isEscapeHandledByOverlayAbove();

if (shouldCloseOnChildEscape) {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.hide();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ import { BUTTON_CLASS } from '__internal/ui/button/button';

import 'fluent_blue_light.css!';
import 'ui/popup';
import 'ui/tooltip';
import 'ui/load_panel';
import 'ui/tab_panel';
import 'ui/scroll_view';
import 'ui/date_box';
Expand Down Expand Up @@ -2725,6 +2727,41 @@ QUnit.module('keyboard navigation', {

assert.strictEqual(this.popup.option('visible'), true, 'popup remains visible when _ignoreCloseOnChildEscape is true');
});

QUnit.test('should remain visible when child element presses escape and a tooltip is shown above it (T1334708)', function(assert) {
this.init({ dragEnabled: false });

const $input = $('<input>').appendTo(this.popup.$content());
const tooltip = $('<div>')
.appendTo('#qunit-fixture')
.dxTooltip({ target: $input, visible: true, animation: null })
.dxTooltip('instance');

assert.strictEqual(tooltip.option('visible'), true, 'tooltip is shown');

keyboardMock($input).keyDown('esc');

assert.strictEqual(tooltip.option('visible'), false, 'tooltip is hidden');
assert.strictEqual(this.popup.option('visible'), true, 'popup remains visible');

tooltip.dispose();
});

QUnit.test('should be closed on child element escape key press when an overlay that ignores escape is shown above it', function(assert) {
this.init({ dragEnabled: false });

const $input = $('<input>').appendTo(this.popup.$content());
const loadPanel = $('<div>')
.appendTo('#qunit-fixture')
.dxLoadPanel({ visible: true, animation: null })
.dxLoadPanel('instance');

keyboardMock($input).keyDown('esc');

assert.strictEqual(this.popup.option('visible'), false, 'popup is closed');

loadPanel.dispose();
});
});

QUnit.module('rendering', {
Expand Down
Loading