#19490·primeng

PrimeNG Tree D&D Droppoint CSS customisation: regression

Author: ArnaudPanaiotisSeiraCreated Mar 20, 2026Updated Sep 17, 2026
LabelsType: BugType: Breaking ChangeResolution: Stale

Describe the bug

Hello,

We have CSS on PrimeNG tree droppoint setting height.

I have some changes to offer, but you may not want all of them, then I opted to create this ticket instead of a PR.

Upgrading to version 21.1.1, we found a regression customizing the droppoint introduced in this commit. The droppoint used to be full time visible, and is now conditionned on first 25% for previous droppoint and last 25% for after droppoint.

How to reproduce (extraction): .tree { .p-tree-node-drop-point { height: 50px; }}

Issue 1

Opening previous droppoint leads to exiting the first 25% and close droppoint. Also leaving node to opened next droppoint leads to the same issue.

Fix :

  • Identify event for previous/next droppoint
  • Manage exit node on top/bottom/side according the the node
  • Add events on droppoint
-                    (dragleave)="onNodeDragLeave()"
                    (dragleave)="onNodeDragLeave($event)"
-    onNodeDragLeave() {
-        this.isPrevDropPointHovered.set(false);
-        this.isNextDropPointHovered.set(false);
    onNodeDragLeave(event: any) {
        let rect = event.currentTarget.getBoundingClientRect();
        if (
            event.x > rect.left + rect.width || // right leave
            event.x < rect.left // left leave
        ) {
            this.isPrevDropPointHovered.set(false);
            this.isNextDropPointHovered.set(false);
        }
-                    <div [class]="cx('dropPoint')" [attr.aria-hidden]="true" [pBind]="getPTOptions('dropPoint')"></div>
                    <div 
                        [class]="cx('dropPoint')" 
                        [attr.aria-hidden]="true" 
                        (dragleave)="onDropPointDragLeave($event, 'previous')"
                        (drop)="onDropPoint($event, 'previous')"
                        [pBind]="getPTOptions('dropPoint')"
                    ></div>
-                    <div [class]="cx('dropPoint', { next: true })" [attr.aria-hidden]="true" [pBind]="getPTOptions('dropPoint')"></div>
                    <div 
                        [class]="cx('dropPoint', { next: true })" 
                        [attr.aria-hidden]="true" 
                        (dragleave)="onDropPointDragLeave($event, 'next')"
                        (drop)="onDropPoint($event, 'next')"
                        [pBind]="getPTOptions('dropPoint')"
                    ></div>
    onDropPoint(event: any, _position : string) {
        this.onNodeDrop(event);
    }
    onDropPointDragLeave(event: any, position : string) {
        let rect = event.currentTarget.getBoundingClientRect();
        if (position === 'previous' && event.y < Math.floor(rect.top + rect.height)) {
            this.isPrevDropPointHovered.set(false);
        }
        if (position === 'next' && event.y >= rect.top) {
            this.isNextDropPointHovered.set(false);
        }
    }
Issue 2

Index returned by drop event is not the same between node A and node B but is visually the same. This may not be mandatory, but leads to confusion for users. (We used to hide one of the droppoint to avoid this management)

Fix: return same index for visual droppoint

            if (isValidDrop) {
+                const newIndex : number | undefined = (this.index && position === -1) ? this.index - 1 : this.index
                if (this.tree.validateDrop) {
                    this.tree.onNodeDrop.emit({
                        originalEvent: event,
                        dragNode,
                        dropNode: this.node,
-                        index: this.index,
+                        index: newIndex,
Issue 3

Now if I go too fast while D&D, I sometimes have severals droppoints that does not got their onDropPointDragLeave.

Fix: introduce delayed event to hide droppoint if more than one is visible.

Note: May not be the solution wanted by PrimeNG team.

static cleanDroppoint: number;

    private delayedCleanNodeDropPoints(event : any) : void {
        clearTimeout(UITreeNode.cleanDroppoint);
        UITreeNode.cleanDroppoint = window.setTimeout(() => {
            this.cleanNodeDropPoints(event);
        }, 10);
    }

    private cleanNodeDropPoints (event : any) : void {
        let parent : ParentNode | null = event.target.parentNode;
        while (parent !== null && parent.nodeName !== 'APP-CUSTOM-PRIME-TREE')  {
            parent = parent.parentNode;
        } 
        if (parent) {
            const nodes : NodeListOf<Element> = parent.querySelectorAll('.p-tree-node-drop-point');
            if (nodes.length > 1) {
                nodes.forEach((droppoint : Element) => droppoint.remove())
            }
        }
    }

This is open to discussion.

I'll try to create a Stackbitz to help you with this feedback. Right know can't make d&d work.

If you agree with all those changes, I may have time to create PR, please let me know.

Here is what we have on our project with the fixes (and more CSS): https://github.com/user-attachments/assets/8ef3b539-d67d-4290-91bf-018dff7962bb

BTW, thats for the work

Pull Request Link

No response

Reason for not contributing a PR

  • Lack of time
  • Unsure how to implement the fix/feature
  • Difficulty understanding the codebase
  • Other

Other Reason

No response

Reproducer

https://stackblitz.com/edit/github-l3djqppz?file=src%2Fapp%2Fapp.component.css

Environment

"@angular/...": "^21.0.0",
"primeicons": "^7.0.0",
"primeng": "^21.1.1",

Angular version

21.1.1

PrimeNG version

v21

Node version

24

Browser(s)

Opera

Steps to reproduce the behavior

1- add CSS to set droppoint height, eventually backgroud color to have it clear. 2- drag & drop to display droppoint

Expected behavior

Droppoint should be customised. Drop index should be the same when visually the same.