Skip to content

Commit ef8b124

Browse files
authored
Merge pull request DSpace#5020 from solomona2003/fix/scrollable-dropdown-selection
Fix: Scrollable dropdown does not highlight current selection (DSpace#4958)
2 parents 3e3df1a + 44a3fcd commit ef8b124

2 files changed

Lines changed: 52 additions & 9 deletions

File tree

src/app/shared/form/builder/ds-dynamic-form-ui/models/scrollable-dropdown/dynamic-scrollable-dropdown.component.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,15 @@
4949
</button>
5050
}
5151
<button class="dropdown-item collection-item text-truncate"
52+
[class.active]="selectedIndex === 0"
5253
(click)="onSelect(undefined); sdRef.close()" (mousedown)="onSelect(undefined); sdRef.close()"
5354
title="{{ 'dropdown.clear.tooltip' | translate }}" role="option"
5455
type="button">
5556
<i>{{ 'dropdown.clear' | translate }}</i>
5657
</button>
5758
@for (listEntry of optionsList; track listEntry; let i = $index) {
5859
<button class="dropdown-item collection-item text-truncate"
59-
[class.active]="i === selectedIndex"
60+
[class.active]="selectedIndex === (i + 1)"
6061
(keydown.enter)="onSelect(listEntry); sdRef.close()" (mousedown)="onSelect(listEntry); sdRef.close()"
6162
title="{{ inputFormatter(listEntry) }}" role="option" type="button"
6263
[attr.id]="inputFormatter(listEntry) === (currentValue|async) ? ('combobox_' + id + '_selected') : null">

src/app/shared/form/builder/ds-dynamic-form-ui/models/scrollable-dropdown/dynamic-scrollable-dropdown.component.ts

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ export class DsDynamicScrollableDropdownComponent extends DsDynamicVocabularyCom
149149
}
150150
}
151151

152-
loadOptions(fromInit: boolean) {
152+
loadOptions(fromInit: boolean, scrollAfterLoad: boolean = false) {
153153
this.loading = true;
154154
this.getDataFromService().pipe(
155155
getFirstSucceededRemoteDataPayload(),
@@ -167,7 +167,11 @@ export class DsDynamicScrollableDropdownComponent extends DsDynamicVocabularyCom
167167
list.pageInfo.totalElements,
168168
list.pageInfo.totalPages,
169169
);
170-
this.selectedIndex = 0;
170+
171+
if (!fromInit) {
172+
this.setSelectedIndexToCurrentValue(scrollAfterLoad);
173+
}
174+
171175
this.cdr.detectChanges();
172176
});
173177
}
@@ -185,26 +189,60 @@ export class DsDynamicScrollableDropdownComponent extends DsDynamicVocabularyCom
185189
if (!this.model.readOnly) {
186190
this.group.markAsUntouched();
187191
this.inputText = null;
188-
this.updatePageInfo(this.model.maxOptions, 1);
189-
this.loadOptions(false);
192+
193+
const pageSize = Math.min(this.pageInfo.totalElements, 200);
194+
this.updatePageInfo(pageSize, 1);
195+
196+
this.loadOptions(false, true);
197+
this.setSelectedIndexToCurrentValue(true);
190198
sdRef.open();
191199
}
192200
}
193201

202+
/**
203+
* Set the selectedIndex to match the current value when dropdown opens
204+
* @param shouldScroll Whether to scroll to the selected item after setting the index
205+
*/
206+
private setSelectedIndexToCurrentValue(shouldScroll: boolean = false): void {
207+
if (this.currentValue) {
208+
this.currentValue.pipe(take(1)).subscribe(currentVal => {
209+
if (currentVal && this.optionsList.length > 0) {
210+
const foundIndex = this.optionsList.findIndex(entry =>
211+
this.inputFormatter(entry) === currentVal,
212+
);
213+
this.selectedIndex = foundIndex >= 0 ? foundIndex + 1 : 0;
214+
} else {
215+
this.selectedIndex = 0;
216+
}
217+
218+
if (shouldScroll && this.selectedIndex > 0) {
219+
// Ensure DOM is updated before scrolling
220+
this.cdr.detectChanges();
221+
// Use setTimeout to ensure the active class is applied and rendered
222+
setTimeout(() => this.scrollToSelected(), 0);
223+
}
224+
});
225+
} else {
226+
this.selectedIndex = 0;
227+
}
228+
}
229+
194230
navigateDropdown(event: KeyboardEvent) {
231+
const totalItems = this.optionsList.length + 1;
232+
195233
if (event.key === 'ArrowDown') {
196-
this.selectedIndex = Math.min(this.selectedIndex + 1, this.optionsList.length - 1);
234+
this.selectedIndex = Math.min(this.selectedIndex + 1, totalItems - 1);
197235
} else if (event.key === 'ArrowUp') {
198236
this.selectedIndex = Math.max(this.selectedIndex - 1, 0);
199237
}
200238
this.scrollToSelected();
201239
}
202240

203241
scrollToSelected() {
204-
const dropdownItems = this.dropdownMenu.nativeElement.querySelectorAll('.dropdown-item');
242+
const dropdownItems = this.dropdownMenu.nativeElement.querySelectorAll('.dropdown-item:not(.disabled)');
205243
const selectedItem = dropdownItems[this.selectedIndex];
206244
if (selectedItem) {
207-
selectedItem.scrollIntoView({ block: 'nearest' });
245+
selectedItem.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
208246
}
209247
}
210248

@@ -220,7 +258,11 @@ export class DsDynamicScrollableDropdownComponent extends DsDynamicVocabularyCom
220258
event.preventDefault();
221259
event.stopPropagation();
222260
if (sdRef.isOpen()) {
223-
this.onSelect(this.optionsList[this.selectedIndex]);
261+
if (this.selectedIndex === 0) {
262+
this.onSelect(undefined);
263+
} else {
264+
this.onSelect(this.optionsList[this.selectedIndex - 1]);
265+
}
224266
sdRef.close();
225267
} else {
226268
sdRef.open();

0 commit comments

Comments
 (0)