Skip to content

Commit b098759

Browse files
solomona2003github-actions[bot]
authored andcommitted
Fix: Load dropdown items dynamically and mark current selection
Dynamic Loading: - Uses actual vocabulary size from pageInfo.totalElements - Caps at 200 items to prevent loading huge vocabularies - Performance scaled: 50 items (Good UX), 100 items (Acceptable), 200 items (Borderline) Selection Highlighting: - Highlight current value when menu opens (setSelectedIndexToCurrentValue) - Treat 'Clear selection' as index 0, adjust item indices (i+1) - Arrow key navigation respects clear item (totalItems) - Enter selects correct item or clears when index = 0 - Skip disabled items in scrollToSelected - Selected items now highlighted correctly up to position 200# This is the 1st commit message: (cherry picked from commit 44a3fcd)
1 parent 07abccf commit b098759

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
@@ -148,7 +148,7 @@ export class DsDynamicScrollableDropdownComponent extends DsDynamicVocabularyCom
148148
}
149149
}
150150

151-
loadOptions(fromInit: boolean) {
151+
loadOptions(fromInit: boolean, scrollAfterLoad: boolean = false) {
152152
this.loading = true;
153153
this.getDataFromService().pipe(
154154
getFirstSucceededRemoteDataPayload(),
@@ -166,7 +166,11 @@ export class DsDynamicScrollableDropdownComponent extends DsDynamicVocabularyCom
166166
list.pageInfo.totalElements,
167167
list.pageInfo.totalPages,
168168
);
169-
this.selectedIndex = 0;
169+
170+
if (!fromInit) {
171+
this.setSelectedIndexToCurrentValue(scrollAfterLoad);
172+
}
173+
170174
this.cdr.detectChanges();
171175
});
172176
}
@@ -184,26 +188,60 @@ export class DsDynamicScrollableDropdownComponent extends DsDynamicVocabularyCom
184188
if (!this.model.readOnly) {
185189
this.group.markAsUntouched();
186190
this.inputText = null;
187-
this.updatePageInfo(this.model.maxOptions, 1);
188-
this.loadOptions(false);
191+
192+
const pageSize = Math.min(this.pageInfo.totalElements, 200);
193+
this.updatePageInfo(pageSize, 1);
194+
195+
this.loadOptions(false, true);
196+
this.setSelectedIndexToCurrentValue(true);
189197
sdRef.open();
190198
}
191199
}
192200

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

202240
scrollToSelected() {
203-
const dropdownItems = this.dropdownMenu.nativeElement.querySelectorAll('.dropdown-item');
241+
const dropdownItems = this.dropdownMenu.nativeElement.querySelectorAll('.dropdown-item:not(.disabled)');
204242
const selectedItem = dropdownItems[this.selectedIndex];
205243
if (selectedItem) {
206-
selectedItem.scrollIntoView({ block: 'nearest' });
244+
selectedItem.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
207245
}
208246
}
209247

@@ -219,7 +257,11 @@ export class DsDynamicScrollableDropdownComponent extends DsDynamicVocabularyCom
219257
event.preventDefault();
220258
event.stopPropagation();
221259
if (sdRef.isOpen()) {
222-
this.onSelect(this.optionsList[this.selectedIndex]);
260+
if (this.selectedIndex === 0) {
261+
this.onSelect(undefined);
262+
} else {
263+
this.onSelect(this.optionsList[this.selectedIndex - 1]);
264+
}
223265
sdRef.close();
224266
} else {
225267
sdRef.open();

0 commit comments

Comments
 (0)