Fix addTag and virtual scroll dropdown panel height
Fixes #1700: The height of the dropdown panel does not consider the add tag entries when virtual scroll is active.
Explanation and and reproducible example is included in the referenced issue. The issue is still reproducible with the current version.
The issue is that the addTag is not included in the regular list of items but rendered separately. As the height calculation is based solely on the number of regular items, the addTag is not considered. To compensate for that I passed the information whether the button is displayed or not as an additional parameter to the NgDropdownPanelComponent and increment the itemsLength in case the addTag is displayed.
This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions :fireworks:
This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions :fireworks:
This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions :fireworks:
@troehling please fix conflicts
@pavankjadda I rebased but I did not get any conflicts. Is it sufficient this way or am I missing something?
This small fix closes a 5-year-old (!) issue, would be great to merge.
@troehling I think this does need a rebase as there are conflicts (even though GH is not showing them above)
diff --cc src/ng-select/lib/ng-dropdown-panel.component.ts
index ba54c1b,dbcae2e..0000000
--- a/src/ng-select/lib/ng-dropdown-panel.component.ts
+++ b/src/ng-select/lib/ng-dropdown-panel.component.ts
@@@ -51,44 -54,36 +51,58 @@@ const SCROLL_SCHEDULER = typeof request
</div>
}
`,
- imports: [NgTemplateOutlet],
+ imports: [NgTemplateOutlet]
})
export class NgDropdownPanelComponent implements OnInit, OnChanges, OnDestroy {
++<<<<<<< HEAD
+ private _renderer = inject(Renderer2);
+ private _zone = inject(NgZone);
+ private _panelService = inject(NgDropdownPanelService);
+ private _document = inject(DOCUMENT, { optional: true })!;
+ private _dropdown = inject(ElementRef<HTMLElement>).nativeElement;
++=======
+ @Input() items: NgOption[] = [];
+ @Input() showAddTag: boolean = false;
+ @Input() markedItem: NgOption;
+ @Input() position: DropdownPosition = 'auto';
+ @Input() appendTo: string;
+ @Input() bufferAmount: number;
+ @Input({ transform: booleanAttribute }) virtualScroll = false;
+ @Input() headerTemplate: TemplateRef<any>;
+ @Input() footerTemplate: TemplateRef<any>;
+ @Input() filterValue: string = null;
+ ariaLabelDropdown = input<string | null>(null);
-
- @Output() update = new EventEmitter<any[]>();
- @Output() scroll = new EventEmitter<{ start: number; end: number }>();
- @Output() scrollToEnd = new EventEmitter<void>();
- @Output() outsideClick = new EventEmitter<void>();
-
- @ViewChild('content', { read: ElementRef, static: true }) contentElementRef: ElementRef;
- @ViewChild('scroll', { read: ElementRef, static: true }) scrollElementRef: ElementRef;
- @ViewChild('padding', { read: ElementRef, static: true }) paddingElementRef: ElementRef;
++>>>>>>> bbbc168 (Fix addTag and virtual scroll dropdown panel height)
+
+ readonly items = input<NgOption[]>([]);
+ readonly markedItem = input<NgOption>(undefined);
+ readonly position = input<DropdownPosition>('auto');
+ readonly appendTo = input<string>(undefined);
+ readonly bufferAmount = input<number>(undefined);
+ readonly virtualScroll = input(false, { transform: booleanAttribute });
+ readonly headerTemplate = input<TemplateRef<any>>(undefined);
+ readonly footerTemplate = input<TemplateRef<any>>(undefined);
+ readonly filterValue = input<string>(null);
+ readonly ariaLabelDropdown = input<string | null>(null);
+
+ readonly update = output<any[]>();
+ readonly scroll = output<{
+ start: number;
+ end: number;
+ }>();
+ readonly scrollToEnd = output<void>();
+ readonly outsideClick = output<void>();
+
+ private readonly contentElementRef = viewChild('content', { read: ElementRef });
+ private readonly scrollElementRef = viewChild('scroll', { read: ElementRef });
+ private readonly paddingElementRef = viewChild('padding', { read: ElementRef });
private readonly _destroy$ = new Subject<void>();
- private readonly _dropdown: HTMLElement;
- private _virtualPadding: HTMLElement;
- private _scrollablePanel: HTMLElement;
- private _contentPanel: HTMLElement;
- private _select: HTMLElement;
+ private readonly _virtualPadding = computed(() => this.paddingElementRef()?.nativeElement);
+ private readonly _scrollablePanel = computed(() => this.scrollElementRef()?.nativeElement);
+ private readonly _contentPanel = computed(() => this.contentElementRef()?.nativeElement);
+
+ private _select: HTMLElement | undefined;
private _parent: HTMLElement;
private _scrollToEndFired = false;
private _updateScrollHeight = false;
@@@ -250,11 -259,14 +268,22 @@@
this._zone.run(() => this.outsideClick.emit());
}
++<<<<<<< HEAD
+ private _onItemsChange(items: NgOption[] = [], firstChange: boolean) {
+ this._scrollToEndFired = false;
+ this.itemsLength = items.length;
+
+ if (this.virtualScroll()) {
++=======
+ private _onItemsOrShowAddTagChange(items: NgOption[], showAddTag: boolean, firstChange: boolean) {
+ this.items = items || [];
+ this._scrollToEndFired = false;
+ this.itemsLength = items.length;
+ if (showAddTag && !(items.length === 0)) {
+ this.itemsLength++;
+ }
+ if (this.virtualScroll) {
++>>>>>>> bbbc168 (Fix addTag and virtual scroll dropdown panel height)
this._updateItemsRange(firstChange);
} else {
this._setVirtualHeight();
Let me know if I can help in any way (e.g. if you give me write access to your fork I can help get this over the line, but no pressure of course)
@shamoon I rebased it now. Hopefully that worked out. I also granted you write access in case there is something pending to do.
Thanks! Will certainly help if I can.
@pavankjadda or @saibaburaavi would really appreciate a look here, again the bug is years old
Hopefully should be good here if you dont mind re-triggering the workflow @pavankjadda 🙏
Initial chunk files | Names | Raw size
chunk-PX7XMSCE.js | - | 2.39 MB |
chunk-RD665U2J.js | - | 1.10 MB |
polyfills.js | polyfills | 1.01 MB |
spec-lib-ng-select.component.spec.js | spec-lib-ng-select.component.spec | 223.33 kB |
chunk-B5AKN72Z.js | - | 67.20 kB |
chunk-J6FNGYNQ.js | - | 38.75 kB |
spec-lib-items-list.spec.js | spec-lib-items-list.spec | 22.97 kB |
test_main.js | test_main | 21.67 kB |
spec-lib-ng-dropdown-panel.service.spec.js | spec-lib-ng-dropdown-panel.service.spec | 2.64 kB |
chunk-TTULUY32.js | - | 1.99 kB |
jasmine-cleanup-0.js | jasmine-cleanup-0 | 519 bytes |
jasmine-cleanup-1.js | jasmine-cleanup-1 | 106 bytes |
| Initial total | 4.88 MB
Application bundle generation complete. [2.308 seconds]
23 09 2025 13:55:37.196:INFO [karma-server]: Karma v6.4.4 server started at http://localhost:9876/
23 09 2025 13:55:37.197:INFO [launcher]: Launching browsers ChromeHeadless with concurrency unlimited
23 09 2025 13:55:37.199:INFO [launcher]: Starting browser ChromeHeadless
23 09 2025 13:55:37.777:INFO [Chrome Headless 140.0.0.0 (Mac OS 10.15.7)]: Connected on socket vY2DeriSH_0prqSNAAAB with id 33834035
Chrome Headless 140.0.0.0 (Mac OS 10.15.7): Executed 315 of 315 SUCCESS (0.926 secs / 0.847 secs)
TOTAL: 315 SUCCESS
TOTAL: 315 SUCCESS
=============================== Coverage summary ===============================
Statements : 94.08% ( 1033/1098 )
Branches : 86.41% ( 477/552 )
Functions : 96.44% ( 244/253 )
Lines : 93.98% ( 985/1048 )
================================================================================
Initial chunk files | Names | Raw size
chunk-67HTMDRQ.js | - | 2.23 MB |
polyfills.js | polyfills | 1.01 MB |
jasmine-cleanup-1.js | jasmine-cleanup-1 | 67.20 kB |
spec-lib-ng-option-highlight.directive.spec.js | spec-lib-ng-option-highlight.directive.spec | 44.95 kB |
test_main.js | test_main | 21.25 kB |
chunk-EVSPGG2W.js | - | 1.64 kB |
jasmine-cleanup-0.js | jasmine-cleanup-0 | 519 bytes |
| Initial total | 3.37 MB
Application bundle generation complete. [1.420 seconds]
23 09 2025 13:55:40.648:INFO [karma-server]: Karma v6.4.4 server started at http://localhost:9876/
23 09 2025 13:55:40.648:INFO [launcher]: Launching browsers ChromeHeadless with concurrency unlimited
23 09 2025 13:55:40.650:INFO [launcher]: Starting browser ChromeHeadless
23 09 2025 13:55:41.209:INFO [Chrome Headless 140.0.0.0 (Mac OS 10.15.7)]: Connected on socket w01_OevJup_Fuit5AAAD with id 86746019
Chrome Headless 140.0.0.0 (Mac OS 10.15.7): Executed 9 of 9 SUCCESS (0.033 secs / 0.03 secs)
TOTAL: 9 SUCCESS
TOTAL: 9 SUCCESS
=============================== Coverage summary ===============================
Statements : 100% ( 24/24 )
Branches : 100% ( 7/7 )
Functions : 100% ( 8/8 )
Lines : 100% ( 24/24 )
================================================================================
✨ Done in 7.77s.
@pavankjadda I think the copilot suggestion was in fact wrong
Verifying the current code works as expected:
with 0 items:
vs Copilot suggestion:
vs before:
Also the last CI failure doesnt seem related to this PR:
📄 Using coverage file: ./coverage/ng-select/lcov.info
🚀 Posting coverage data to https://coveralls.io/api/v1/jobs
⚠️ Internal server error. Please contact Coveralls team.
Error: Process completed with exit code 1.
Awesome work @troehling , let's get this in please!
@saibaburaavi any plans an merging this?
Because it's been left open conflicts keep cropping up from other changes in the main branch, unfortunately. I will fix it now but yes it would be great to finally get this merged, or at least some discussion of why not
Again, merge conflicts resolved. If there is something preventing merge please tell us, rather than silently waiting until conflicts crop up again...
@shamoon @troehling Sorry for delay. I will review and merge this asap
coverage: 91.945% (+0.05%) from 91.894% when pulling c3d0ab28ff72932246143f90d82f2afa2b790356 on troehling:master into 6e0b09b973bcd08c78d1934de9135e3b7cac07c9 on ng-select:master.
:tada: This PR is included in version 20.6.2 :tada:
The release is available on:
Your semantic-release bot :package::rocket: