Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
f0fca41
Toolbar: support keyboard navigation according to APG W3C
EugeniyKiyashko May 14, 2026
e926260
Toolbar: support keyboard navigation according to APG W3C
EugeniyKiyashko May 14, 2026
e3ff633
update imports for classnames
dmlvr May 15, 2026
b98de3d
add testfile
dmlvr May 15, 2026
84c694f
add tests
pharret31 May 15, 2026
46ae4b1
remove extra tests and unskipped some
dmlvr May 15, 2026
87d48e7
merge test part
dmlvr May 15, 2026
0a7da03
fix qunit scenarios
EugeniyKiyashko May 18, 2026
cf7e016
Merge branch '26_1' into 26_1_toolbar_kbn
EugeniyKiyashko May 18, 2026
3208739
fix old tests
EugeniyKiyashko May 18, 2026
dfdc9ee
exclude disabled items
EugeniyKiyashko May 18, 2026
bb24433
fix jquery related tests
dmlvr May 18, 2026
720a174
Merge branch '26_1_toolbar_kbn' of github.com:EugeniyKiyashko/DevExtr…
dmlvr May 18, 2026
84c6bf3
consider more scenarios
EugeniyKiyashko May 18, 2026
9584e39
prevent item focusing inside overflow menu
EugeniyKiyashko May 18, 2026
86e5c76
add fallback strategy using focusStateEnabled option
EugeniyKiyashko May 18, 2026
a953640
add focusStateEnabled tests
pharret31 May 18, 2026
301115e
remove extra comments
dmlvr May 18, 2026
a840a5d
non-focusable service items
pharret31 May 18, 2026
0bcb3db
add templates tests
pharret31 May 18, 2026
1e0a66a
add enter/exit tests
pharret31 May 18, 2026
d1f4e57
repair tests
EugeniyKiyashko May 19, 2026
ba22bb2
Merge branch '26_1' into 26_1_toolbar_kbn
EugeniyKiyashko May 19, 2026
5b360ed
Merge remote-tracking branch 'EugeniyKiyashko/26_1_toolbar_kbn' into …
pharret31 May 19, 2026
6ee9c86
add tab/shift-tab testcafe tests
pharret31 May 19, 2026
23765ef
remove only
pharret31 May 19, 2026
a3092f3
add some extra test cases
dmlvr May 19, 2026
d615427
Merge branch '26_1_toolbar_kbn' of github.com:EugeniyKiyashko/DevExtr…
dmlvr May 19, 2026
f59577b
fix one tab stop on editors according to APG
EugeniyKiyashko May 19, 2026
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
141 changes: 141 additions & 0 deletions e2e/testcafe-devextreme/tests/navigation/toolbar/keyboard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import { Selector, ClientFunction } from 'testcafe';
import url from '../../../helpers/getPageUrl';
import { createWidget } from '../../../helpers/createWidget';
import { appendElementTo } from '../../../helpers/domUtils';

fixture.disablePageReloads`Toolbar_keyboard_navigation`
.page(url(__dirname, '../../container.html'));

const isFocusInsideItem = ClientFunction((itemIndex: number) => {
const items = document.querySelectorAll(
'#toolbar .dx-toolbar-item',
);
const item = items[itemIndex];
return item ? item.contains(document.activeElement) : false;
});

const toolbarWidgets = [
{
widget: 'dxButton',
options: { text: 'Button' },
},
{
widget: 'dxTextBox',
options: { value: 'text', showClearButton: false },
},
{
widget: 'dxAutocomplete',
options: { value: 'auto', showClearButton: false },
},
{
widget: 'dxCheckBox',
options: { value: true },
},
{
widget: 'dxDateBox',
options: {
value: new Date(2021, 9, 17),
openOnFieldClick: false,
showClearButton: false,
showDropDownButton: false,
},
},
{
widget: 'dxSelectBox',
options: {
items: ['Item 1', 'Item 2'],
value: 'Item 1',
showClearButton: false,
showDropDownButton: false,
},
},
{
widget: 'dxMenu',
options: {
items: [{ text: 'Menu Item 1' }, { text: 'Menu Item 2' }],
},
},
{
widget: 'dxTabs',
options: {
items: [{ text: 'Tab 1' }, { text: 'Tab 2' }],
},
},
{
widget: 'dxButtonGroup',
options: {
items: [{ text: 'Left' }, { text: 'Right' }],
},
},
{
widget: 'dxDropDownButton',
options: {
text: 'Drop',
items: [{ text: 'Action 1' }, { text: 'Action 2' }],
},
},
] as const;

toolbarWidgets.forEach(({ widget, options }) => {
test(`${widget}: Tab leaves and Shift+Tab returns focus`, async (t) => {
const externalBefore = Selector('#externalBefore');
const externalAfter = Selector('#externalAfter');

await t.click(externalBefore);
await t
.expect(externalBefore.focused)
.ok('external before button should be focused');

await t.pressKey('tab');
await t
.expect(isFocusInsideItem(0))
.ok('first toolbar item should be focused after Tab');

await t.pressKey('right');
await t
.expect(isFocusInsideItem(1))
.ok(`${widget} should be focused after arrow right`);

await t.pressKey('tab');
await t
.expect(externalAfter.focused)
.ok('external after button should be focused after Tab');

await t.pressKey('shift+tab');
await t
.expect(isFocusInsideItem(1))
.ok(`${widget} should be focused after Shift+Tab`);
}).before(async () => {
await appendElementTo('#container', 'div', 'externalBefore');
await appendElementTo('#container', 'div', 'toolbar');
await appendElementTo('#container', 'div', 'externalAfter');

await createWidget('dxButton', {
text: 'External Before',
}, '#externalBefore');

await createWidget('dxToolbar', {
items: [
{
location: 'before',
widget: 'dxButton',
options: { text: 'Prev', focusStateEnabled: true },
},
{
location: 'before',
widget,
options: { ...options, focusStateEnabled: true },
},
{
location: 'before',
widget: 'dxButton',
options: { text: 'Next', focusStateEnabled: true },
},
],
}, '#toolbar');

await createWidget('dxButton', {
text: 'External After',
}, '#externalAfter');
});
});
2 changes: 1 addition & 1 deletion packages/devextreme/js/__internal/core/widget/widget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import type { OptionChanged } from '@ts/core/widget/types';
import type { KeyboardKeyDownEvent } from '@ts/events/core/m_keyboard_processor';

export const WIDGET_CLASS = 'dx-widget';
const DISABLED_STATE_CLASS = 'dx-state-disabled';
export const DISABLED_STATE_CLASS = 'dx-state-disabled';
export const ACTIVE_STATE_CLASS = 'dx-state-active';
export const FOCUSED_STATE_CLASS = 'dx-state-focused';
export const HOVER_STATE_CLASS = 'dx-state-hover';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,10 +158,12 @@ class DropDownEditor<
: this._getFirstPopupElement();

if ($focusableElement) {
const $input = $focusableElement.hasClass('dx-texteditor') ? $focusableElement.find('.dx-texteditor-input').first() : $();
const $focusTarget = $input.length ? $input : $focusableElement;
// @ts-expect-error ts-error
eventsEngine.trigger($focusableElement, 'focus');
eventsEngine.trigger($focusTarget, 'focus');
// @ts-expect-error ts-error
$focusableElement.select();
$focusTarget.select();
}
e.preventDefault();
},
Expand Down
2 changes: 1 addition & 1 deletion packages/devextreme/js/__internal/ui/list/list.base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ import { getElementMargin } from '@ts/ui/scroll_view/utils/get_element_style';

const LIST_CLASS = 'dx-list';
const LIST_ITEMS_CLASS = 'dx-list-items';
const LIST_ITEM_CLASS = 'dx-list-item';
export const LIST_ITEM_CLASS = 'dx-list-item';
const LIST_ITEM_SELECTOR = `.${LIST_ITEM_CLASS}`;
const LIST_ITEM_ICON_CONTAINER_CLASS = 'dx-list-item-icon-container';
const LIST_ITEM_ICON_CLASS = 'dx-list-item-icon';
Expand Down
1 change: 1 addition & 0 deletions packages/devextreme/js/__internal/ui/popup/m_popup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,7 @@ class Popup<
disabled,
rtlEnabled,
items,
focusStateEnabled: false,
useDefaultButtons: useDefaultToolbarButtons,
useFlatButtons: useFlatToolbarButtons,
integrationOptions,
Expand Down
Loading
Loading