Skip to content

Commit 57f8d6a

Browse files
authored
fix: infinite scroll with arrow navigation should work with short list (#256)
- infinite scroll should also work with small list even it doesn't have a scroll
1 parent 85bb553 commit 57f8d6a

File tree

2 files changed

+29
-14
lines changed

2 files changed

+29
-14
lines changed

packages/demo/src/examples/example16.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ export default class Example {
2121
{ value: '4', text: `<img alt="avatar4" src="${avatar4}" class="avatar"> Julia` },
2222
{ value: '5', text: `<img alt="avatar5" src="${avatar5}" class="avatar"> Catherine` },
2323
],
24-
infiniteScroll: true,
2524
renderOptionLabelAsHtml: true, // without this flag, html code will be showing as plain text
2625
}) as MultipleSelectInstance;
2726

packages/multiple-select-vanilla/src/MultipleSelectInstance.ts

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ export class MultipleSelectInstance {
5353
protected selectAllName = '';
5454
protected selectGroupName = '';
5555
protected selectItemName = '';
56+
protected scrolledByMouse = false;
5657
protected openDelayTimer: NodeJS.Timeout | undefined;
5758

5859
protected updateDataStart?: number;
@@ -538,10 +539,11 @@ export class MultipleSelectInstance {
538539
if (this.options.infiniteScroll) {
539540
rows.push({
540541
tagName: 'li',
541-
props: { className: 'ms-infinite-option', role: 'option', dataset: { key: 'infinite' } },
542+
props: { className: 'ms-infinite-option', role: 'option' },
542543
});
543544
}
544545

546+
// add a "No Results" option that is hidden by default
545547
rows.push({ tagName: 'li', props: { className: 'ms-no-results', textContent: this.formatNoMatchesFound() } });
546548

547549
return rows;
@@ -1000,7 +1002,7 @@ export class MultipleSelectInstance {
10001002
'mouseover',
10011003
((e: MouseEvent & { target: HTMLDivElement | HTMLLIElement }) => {
10021004
const liElm = (e.target.closest('.ms-select-all') || e.target.closest('li')) as HTMLLIElement;
1003-
if (this.dropElm.contains(liElm)) {
1005+
if (this.dropElm.contains(liElm) && this.scrolledByMouse) {
10041006
const optionElms = this.dropElm?.querySelectorAll<HTMLLIElement>(OPTIONS_LIST_SELECTOR) || [];
10051007
const newIdx = Array.from(optionElms).findIndex(el => el.dataset.key === liElm.dataset.key);
10061008
if (this._currentHighlightIndex !== newIdx && !liElm.classList.contains('disabled')) {
@@ -1083,19 +1085,26 @@ export class MultipleSelectInstance {
10831085
* Checks if user reached the end of the list through mouse scrolling and/or arrow down,
10841086
* then scroll back to the top whenever that happens.
10851087
*/
1086-
protected infiniteScrollHandler(e: MouseEvent & { target: HTMLElement }) {
1087-
if (e.target && this.ulElm) {
1088-
const scrollPos = e.target.scrollTop + e.target.clientHeight;
1088+
protected infiniteScrollHandler(e: (MouseEvent & { target: HTMLElement }) | null, idx?: number, fullCount?: number) {
1089+
let needHighlightRecalc = false;
10891090

1091+
if (e?.target && this.ulElm && this.scrolledByMouse) {
1092+
const scrollPos = e.target.scrollTop + e.target.clientHeight;
10901093
if (scrollPos === this.ulElm.scrollHeight) {
1091-
if (this.virtualScroll) {
1092-
this.initListItems();
1093-
} else {
1094-
this.ulElm.scrollTop = 0;
1095-
}
1096-
this._currentHighlightIndex = 0;
1097-
this.highlightCurrentOption();
1094+
needHighlightRecalc = true;
10981095
}
1096+
} else if (idx !== undefined && idx + 1 === fullCount) {
1097+
needHighlightRecalc = true;
1098+
}
1099+
1100+
if (needHighlightRecalc && this.ulElm) {
1101+
if (this.virtualScroll) {
1102+
this.initListItems();
1103+
} else {
1104+
this.ulElm.scrollTop = 0;
1105+
}
1106+
this._currentHighlightIndex = 0;
1107+
this.highlightCurrentOption();
10991108
}
11001109
}
11011110

@@ -1236,8 +1245,11 @@ export class MultipleSelectInstance {
12361245
this._currentSelectedElm = currentOption;
12371246

12381247
// Scroll the current option into view
1248+
// use a global flag to differentiate scroll by mouse or by scrollIntoView
1249+
this.scrolledByMouse = false;
12391250
currentOption.scrollIntoView({ block: 'nearest' });
12401251
this.changeCurrentOptionHighlight(currentOption);
1252+
setTimeout(() => (this.scrolledByMouse = true), 10);
12411253
}
12421254
}
12431255
}
@@ -1255,11 +1267,15 @@ export class MultipleSelectInstance {
12551267

12561268
protected moveHighlightDown() {
12571269
const optionElms = this.dropElm?.querySelectorAll<HTMLLIElement>(OPTIONS_LIST_SELECTOR) || [];
1258-
if (this._currentHighlightIndex < optionElms.length - 1) {
1270+
const domOptionsCount = optionElms.length;
1271+
1272+
if (this._currentHighlightIndex < domOptionsCount - 1) {
12591273
this._currentHighlightIndex++;
12601274
if (optionElms[this._currentHighlightIndex]?.classList.contains('disabled')) {
12611275
this.moveHighlightDown();
12621276
}
1277+
} else if (this.options.infiniteScroll) {
1278+
this.infiniteScrollHandler(null, this._currentHighlightIndex, domOptionsCount);
12631279
}
12641280
this.highlightCurrentOption();
12651281
}

0 commit comments

Comments
 (0)