Skip to content

Commit 8cafcb5

Browse files
List: fix kbn scrolling in simulated strategy (T1298074) (DevExpress#30384) (DevExpress#30389)
1 parent f278a9c commit 8cafcb5

File tree

2 files changed

+100
-53
lines changed

2 files changed

+100
-53
lines changed

packages/devextreme/js/__internal/ui/list/m_list.base.ts

Lines changed: 65 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ export interface ListBaseProperties extends Properties<Item> {
8080
focusedElement?: dxElementWrapper;
8181
}
8282

83+
type Direction = 'prev' | 'next';
84+
8385
export class ListBase extends CollectionWidget<ListBaseProperties> {
8486
static ItemClass = ListItem;
8587

@@ -120,78 +122,88 @@ export class ListBase extends CollectionWidget<ListBaseProperties> {
120122
_selectionChangeEventInstance?: any;
121123

122124
_supportedKeys(): Record<string, (e: KeyboardEvent, options?: Record<string, unknown>) => void> {
123-
const that = this;
125+
return {
126+
...super._supportedKeys(),
127+
leftArrow: noop,
128+
rightArrow: noop,
129+
pageUp(e) {
130+
this._moveFocusPerPage(e, 'prev');
131+
},
132+
pageDown(e) {
133+
this._moveFocusPerPage(e, 'next');
134+
},
135+
};
136+
}
124137

125-
const moveFocusPerPage = function (direction) {
126-
let $item = getEdgeVisibleItem(direction);
138+
_moveFocusPerPage(e: KeyboardEvent, direction: Direction): void {
139+
if (this._isLastItemFocused(direction)) {
140+
return;
141+
}
127142

128-
const { focusedElement } = that.option();
129-
// @ts-expect-error ts-error
130-
const isFocusedItem = $item.is(focusedElement);
143+
e.preventDefault();
144+
e.stopPropagation();
131145

132-
if (isFocusedItem) {
133-
scrollListTo($item, direction);
134-
$item = getEdgeVisibleItem(direction);
135-
}
146+
let $item = this._getEdgeVisibleItem(direction);
147+
const { focusedElement } = this.option();
136148

137-
that.option('focusedElement', getPublicElement($item));
138-
that.scrollToItem($item);
139-
};
149+
const isFocusedItem = $item.is($(focusedElement));
140150

141-
function getEdgeVisibleItem(direction) {
142-
const scrollTop = that.scrollTop();
143-
const containerHeight = getHeight(that.$element());
151+
if (isFocusedItem) {
152+
this.scrollTo(this._getItemLocation($item, direction));
153+
$item = this._getEdgeVisibleItem(direction);
154+
}
144155

145-
const { focusedElement } = that.option();
156+
this.option('focusedElement', getPublicElement($item));
157+
this.scrollToItem($item);
158+
}
146159

147-
let $item = $(focusedElement);
148-
let isItemVisible = true;
160+
_isLastItemFocused(direction: Direction): boolean {
161+
const lastItemInDirection = direction === 'prev' ? this._itemElements().first() : this._itemElements().last();
162+
const { focusedElement } = this.option();
149163

150-
if (!$item.length) {
151-
return $();
152-
}
164+
return lastItemInDirection.is($(focusedElement));
165+
}
153166

154-
while (isItemVisible) {
155-
const $nextItem = $item[direction]();
167+
_getEdgeVisibleItem(direction: Direction): dxElementWrapper {
168+
const scrollTop = this.scrollTop();
169+
const containerHeight = getHeight(this.$element());
156170

157-
if (!$nextItem.length) {
158-
break;
159-
}
171+
const { focusedElement } = this.option();
160172

161-
const nextItemLocation = $nextItem.position().top + getOuterHeight($nextItem) / 2;
162-
isItemVisible = nextItemLocation < containerHeight + scrollTop && nextItemLocation > scrollTop;
173+
let $item = $(focusedElement);
174+
let isItemVisible = true;
163175

164-
if (isItemVisible) {
165-
$item = $nextItem;
166-
}
167-
}
168-
169-
return $item;
176+
if (!$item.length) {
177+
return $();
170178
}
171179

172-
function scrollListTo($item, direction) {
173-
let resultPosition = $item.position().top;
180+
while (isItemVisible) {
181+
const $nextItem = $item[direction]();
174182

175-
if (direction === 'prev') {
176-
resultPosition = $item.position().top - getHeight(that.$element()) + getOuterHeight($item);
183+
if (!$nextItem.length) {
184+
break;
177185
}
178186

179-
that.scrollTo(resultPosition);
187+
const nextItemLocation = ($nextItem.position()?.top ?? 0) + getOuterHeight($nextItem) / 2;
188+
isItemVisible = nextItemLocation < containerHeight + scrollTop && nextItemLocation > scrollTop;
189+
190+
if (isItemVisible) {
191+
$item = $nextItem;
192+
}
180193
}
181194

182-
return {
183-
...super._supportedKeys(),
184-
leftArrow: noop,
185-
rightArrow: noop,
186-
pageUp() {
187-
moveFocusPerPage('prev');
188-
return false;
189-
},
190-
pageDown() {
191-
moveFocusPerPage('next');
192-
return false;
193-
},
194-
};
195+
return $item;
196+
}
197+
198+
_getItemLocation($item: dxElementWrapper, direction: Direction): number {
199+
if (direction === 'prev') {
200+
// @ts-expect-error ts-error
201+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
202+
return $item.position().top - getHeight(this.$element()) + getOuterHeight($item);
203+
}
204+
205+
// @ts-expect-error ts-error
206+
return $item.position().top;
195207
}
196208

197209
_getDefaultOptions(): ListBaseProperties {

packages/devextreme/testing/tests/DevExpress.ui.widgets/listParts/commonTests.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4335,6 +4335,41 @@ QUnit.module('keyboard navigation', {
43354335
assert.ok(secondItemIsFocused, 'focused item change to last visible item on new page');
43364336
});
43374337

4338+
['pageUp', 'pageDown'].forEach((key) => {
4339+
const moveDown = key === 'pageDown';
4340+
4341+
[true, false].forEach((useNativeScrolling) => {
4342+
QUnit.test(`on list scroll with ${key} pressed original event prevented and propagation stopped if not ${moveDown ? 'last' : 'first'} item was focused, useNativeScrolling=${useNativeScrolling} (T1298074)`, function(assert) {
4343+
assert.expect(4);
4344+
4345+
const $list = $('#list').dxList({
4346+
useNativeScrolling,
4347+
focusStateEnabled: true,
4348+
items: [0, 1, 2, 3, 4],
4349+
});
4350+
4351+
const instance = $list.dxList('instance');
4352+
const $items = $list.find(`.${LIST_ITEM_CLASS}`);
4353+
const keyboard = getListKeyboard($list);
4354+
const itemHeight = $items.first().outerHeight();
4355+
4356+
instance.option('height', itemHeight * 3);
4357+
instance.option('focusedElement', $items.eq(3));
4358+
instance.scrollToItem(moveDown ? $items.last() : $items.first());
4359+
4360+
keyboard.keyDown(key);
4361+
4362+
assert.strictEqual(keyboard.event.isDefaultPrevented(), true, 'event is prevented');
4363+
assert.strictEqual(keyboard.event.isPropagationStopped(), true, 'propogation is stopped');
4364+
4365+
keyboard.keyDown(key);
4366+
4367+
assert.strictEqual(keyboard.event.isDefaultPrevented(), false, `event is not prevented when ${moveDown ? 'last' : 'first'} item is focused`);
4368+
assert.strictEqual(keyboard.event.isPropagationStopped(), false, `propogation is not stopped when ${moveDown ? 'last' : 'first'} item is focused`);
4369+
});
4370+
});
4371+
});
4372+
43384373
QUnit.test('focus should be moved to selectedItem after focusing of grouped list (T1278005)', function(assert) {
43394374
const list = $('#list').dxList({
43404375
selectedItemKeys: [5],

0 commit comments

Comments
 (0)