Skip to content

Commit

Permalink
MultiView: improve swipe UX when some items are hidden (#28128)
Browse files Browse the repository at this point in the history
  • Loading branch information
nikkithelegendarypokemonster authored Oct 3, 2024
1 parent 83c13b7 commit a028a35
Show file tree
Hide file tree
Showing 2 changed files with 152 additions and 8 deletions.
30 changes: 22 additions & 8 deletions packages/devextreme/js/__internal/ui/m_multi_view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,12 @@ const MultiView = CollectionWidget.inherit({
index -= count;
}

const step = this._swipeDirection > 0 ? -1 : 1;

while (!this._isItemVisible(index)) {
index = (index + step) % count;
}

return index;
},

Expand Down Expand Up @@ -260,6 +266,10 @@ const MultiView = CollectionWidget.inherit({
}
},

_isItemVisible(index) {
return this.option('items')[index]?.visible ?? true;
},

_updateItemsVisibility(selectedIndex, newIndex) {
const $itemElements = this._itemElements();

Expand Down Expand Up @@ -353,8 +363,9 @@ const MultiView = CollectionWidget.inherit({

items.forEach((item, index) => {
const isDisabled = Boolean(item?.disabled);
const isVisible = this._isItemVisible(index);

if (!isDisabled) {
if (!isDisabled && isVisible) {
firstIndex ??= index;
lastIndex = index;
}
Expand Down Expand Up @@ -388,16 +399,19 @@ const MultiView = CollectionWidget.inherit({
const { offset } = e;
const swipeDirection = sign(offset) * this._getRTLSignCorrection();

_translator.move(this._$itemContainer, offset * this._itemWidth());

if (swipeDirection !== this._swipeDirection) {
this._swipeDirection = swipeDirection;
}

const selectedIndex = this.option('selectedIndex');
const newIndex = this._normalizeIndex(selectedIndex - swipeDirection);
const selectedIndex = this.option('selectedIndex');
const newIndex = this._normalizeIndex(selectedIndex - swipeDirection);

this._updateItems(selectedIndex, newIndex);
if (selectedIndex === newIndex) {
return;
}

_translator.move(this._$itemContainer, offset * this._itemWidth());
this._updateItems(selectedIndex, newIndex);
},

_findNextAvailableIndex(index, offset) {
Expand All @@ -419,8 +433,8 @@ const MultiView = CollectionWidget.inherit({

for (let i = index + offset; i >= firstAvailableIndex && i <= lastAvailableIndex; i += offset) {
const isDisabled = Boolean(items[i].disabled);

if (!isDisabled) {
const isVisible = this._isItemVisible(i);
if (!isDisabled && isVisible) {
return i;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,30 @@ QUnit.module('rendering', () => {
}
});

QUnit.test('item should be visible if no item.visible property is specified', function(assert) {
const $multiView = $('#multiView').dxMultiView({
items: [
{ text: '1' }
]
});
const $items = $multiView.find(toSelector(MULTIVIEW_ITEM_CLASS));

assert.notOk($items.eq(0).hasClass(MULTIVIEW_ITEM_HIDDEN_CLASS), 'first item is visible');
});

QUnit.test('items with visible=false should be hidden', function(assert) {
const $multiView = $('#multiView').dxMultiView({
items: [
{ text: '1', visible: true },
{ text: '2', visible: false }
]
});
const $items = $multiView.find(toSelector(MULTIVIEW_ITEM_CLASS));

assert.notOk($items.eq(0).hasClass(MULTIVIEW_ITEM_HIDDEN_CLASS), 'first item is visible');
assert.ok($items.eq(1).hasClass(MULTIVIEW_ITEM_HIDDEN_CLASS), 'second item is hidden');
});

QUnit.test('multiView should trigger resize event for item content after item visibility changed', function(assert) {
const resizeHandler = sinon.spy();

Expand Down Expand Up @@ -480,6 +504,60 @@ QUnit.module('interaction via swipe', {
});
});

QUnit.test('when only one item is visible, swipe action does not move the current visible item', function(assert) {
const $multiView = $('#multiView').dxMultiView({
items: [
{ text: '1', visible: true },
{ text: '2', visible: false }
]
});
const instance = $multiView.dxMultiView('instance');
const $itemContainer = $multiView.find(toSelector(MULTIVIEW_ITEM_CONTAINER_CLASS));
const pointer = pointerMock($multiView);

pointer.start().swipeStart().swipe(0.1).swipeEnd(1);
assert.strictEqual(position($itemContainer), 0, 'container did not move');
assert.strictEqual(instance.option('selectedIndex'), 0, 'selectedIndex is not changed');
});

QUnit.test('swiping left should select first visible item to right', function(assert) {
const $multiView = $('#multiView').dxMultiView({
items: [
{ text: '1', visible: true },
{ text: '2', visible: false },
{ text: '3', visible: true }
]
});
const instance = $multiView.dxMultiView('instance');
const $itemContainer = $multiView.find(toSelector(MULTIVIEW_ITEM_CONTAINER_CLASS));
const $thirdItem = $multiView.find(`.${MULTIVIEW_ITEM_CLASS}`).eq(2);
const pointer = pointerMock($multiView);

pointer.start().swipeStart().swipe(-0.1).swipeEnd(-1);
assert.roughEqual(position($itemContainer), position($thirdItem), 1, 'container did move');
assert.strictEqual(instance.option('selectedIndex'), 2, 'first item visible to the right is selected');
});

QUnit.test('swiping right should select first visible item to left', function(assert) {
const $multiView = $('#multiView').dxMultiView({
items: [
{ text: '1', visible: true },
{ text: '2', visible: false },
{ text: '3', visible: true }
],
selectedIndex: 2
});

const instance = $multiView.dxMultiView('instance');
const $itemContainer = $multiView.find(`.${MULTIVIEW_ITEM_CONTAINER_CLASS}`);
const $firstItem = $multiView.find(`.${MULTIVIEW_ITEM_CLASS}`).eq(0);
const pointer = pointerMock($multiView);

pointer.start().swipeStart().swipe(0.9).swipeEnd(1);
assert.roughEqual(position($itemContainer), -position($firstItem), 1, 'container did move');
assert.strictEqual(instance.option('selectedIndex'), 0, 'first item visible to the left is selected');
});

QUnit.test('item container should not be moved by swipe if items count less then 2', function(assert) {
const $multiView = $('#multiView').dxMultiView({
items: [1]
Expand Down Expand Up @@ -741,6 +819,58 @@ QUnit.module('loop', {
delete this.animationStartAction;
}
}, () => {
QUnit.test('when only one item is visible, swipe action does not move the current visible item', function(assert) {
const $multiView = $('#multiView').dxMultiView({
items: [
{ text: '1', visible: true },
{ text: '2', visible: false }
],
loop: true
});
const instance = $multiView.dxMultiView('instance');
const $itemContainer = $multiView.find(toSelector(MULTIVIEW_ITEM_CONTAINER_CLASS));
const pointer = pointerMock($multiView);

pointer.start().swipeStart().swipe(-0.1).swipeEnd(-1);
assert.strictEqual(position($itemContainer), 0, 'container did not move');
assert.strictEqual(instance.option('selectedIndex'), 0, 'selectedIndex does not change');
});

QUnit.test('when swiping left on the first item, show last visible item', function(assert) {
const $multiView = $('#multiView').dxMultiView({
items: [
{ text: '1', visible: true },
{ text: '2', visible: true },
{ text: '3', visible: false }
],
loop: true
});
const instance = $multiView.dxMultiView('instance');
const pointer = pointerMock($multiView);

pointer.start().swipeStart().swipe(-0.5).swipeEnd(-1);

assert.strictEqual(instance.option('selectedIndex'), 1, 'Correct item is shown after swiping left');
});

QUnit.test('when swiping right on the last item, show first visible item', function(assert) {
const $multiView = $('#multiView').dxMultiView({
items: [
{ text: '1', visible: false },
{ text: '2', visible: true },
{ text: '3', visible: true }
],
loop: true,
selectedIndex: 2
});
const instance = $multiView.dxMultiView('instance');
const pointer = pointerMock($multiView);

pointer.start().swipeStart().swipe(0.5).swipeEnd(1);

assert.strictEqual(instance.option('selectedIndex'), 1, 'Correct item is shown after swiping right');
});

QUnit.test('item container should be moved right if selected index is 0', function(assert) {
const $multiView = $('#multiView').dxMultiView({
items: [1, 2, 3],
Expand Down

0 comments on commit a028a35

Please sign in to comment.