Skip to content

Commit 55905d7

Browse files
mstahvmcollovatiArtur-caalador
authored
feat: Adds relevant API to allow drag and drop to a certain location (#23187)
Exposes clientX/Y for all D&D events, to allow "repositioning" absolutely positioned drag and drop component. Also exposing offsets of the target and start element (if operation started on top of Vaadin component), so that one can build UIs where "things" are dragged on a specific location of another component. Fixes #19297 ## Type of change - [ ] Bugfix - [x] Feature --------- Co-authored-by: Marco Collovati <marco@vaadin.com> Co-authored-by: Artur Signell <artur@vaadin.com> Co-authored-by: caalador <mikael.grankvist@vaadin.com>
1 parent 96d8802 commit 55905d7

12 files changed

Lines changed: 809 additions & 56 deletions

File tree

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/*
2+
* Copyright 2000-2026 Vaadin Ltd.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
5+
* use this file except in compliance with the License. You may obtain a copy of
6+
* the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations under
14+
* the License.
15+
*/
16+
package com.vaadin.flow.component.dnd;
17+
18+
import com.vaadin.flow.component.Component;
19+
import com.vaadin.flow.component.ComponentEvent;
20+
21+
/**
22+
* Abstract base class for HTML5 drag and drop events.
23+
* <p>
24+
* In the browser, drag events inherit from {@code MouseEvent}, so this class
25+
* provides access to common mouse event properties like cursor coordinates.
26+
*
27+
* @param <T>
28+
* Type of the component associated with the event.
29+
* @author Vaadin Ltd
30+
*/
31+
public abstract class AbstractDnDEvent<T extends Component>
32+
extends ComponentEvent<T> {
33+
34+
private final int clientX;
35+
private final int clientY;
36+
37+
/**
38+
* Creates a new drag and drop event.
39+
*
40+
* @param source
41+
* the component that is the source of the event
42+
* @param fromClient
43+
* {@code true} if the event originated from the client side,
44+
* {@code false} otherwise
45+
* @param clientX
46+
* the x coordinate of the mouse pointer relative to the
47+
* viewport, excluding any scroll offset
48+
* @param clientY
49+
* the y coordinate of the mouse pointer relative to the
50+
* viewport, excluding any scroll offset
51+
*/
52+
protected AbstractDnDEvent(T source, boolean fromClient, int clientX,
53+
int clientY) {
54+
super(source, fromClient);
55+
this.clientX = clientX;
56+
this.clientY = clientY;
57+
}
58+
59+
/**
60+
* Gets the x coordinate of the mouse pointer relative to the viewport,
61+
* excluding any scroll offset.
62+
* <p>
63+
* This is useful for positioning elements based on where the drag/drop
64+
* operation occurred.
65+
*
66+
* @return the x coordinate relative to the viewport
67+
*/
68+
public int getClientX() {
69+
return clientX;
70+
}
71+
72+
/**
73+
* Gets the y coordinate of the mouse pointer relative to the viewport,
74+
* excluding any scroll offset.
75+
* <p>
76+
* This is useful for positioning elements based on where the drag/drop
77+
* operation occurred.
78+
*
79+
* @return the y coordinate relative to the viewport
80+
*/
81+
public int getClientY() {
82+
return clientY;
83+
}
84+
85+
/**
86+
* Returns the component associated with this event.
87+
*
88+
* @return the component associated with this event
89+
*/
90+
public T getComponent() {
91+
return getSource();
92+
}
93+
}

flow-dnd/src/main/java/com/vaadin/flow/component/dnd/DragEndEvent.java

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
package com.vaadin.flow.component.dnd;
1717

1818
import com.vaadin.flow.component.Component;
19-
import com.vaadin.flow.component.ComponentEvent;
2019
import com.vaadin.flow.component.DomEvent;
2120
import com.vaadin.flow.component.EventData;
2221

@@ -31,7 +30,7 @@
3130
* @since 2.0
3231
*/
3332
@DomEvent("dragend")
34-
public class DragEndEvent<T extends Component> extends ComponentEvent<T> {
33+
public class DragEndEvent<T extends Component> extends AbstractDnDEvent<T> {
3534
private final DropEffect dropEffect;
3635

3736
/**
@@ -44,10 +43,16 @@ public class DragEndEvent<T extends Component> extends ComponentEvent<T> {
4443
* side, <code>false</code> otherwise
4544
* @param dropEffect
4645
* Drop effect from {@code DataTransfer.dropEffect} object.
46+
* @param clientX
47+
* the x coordinate of the mouse pointer relative to the viewport
48+
* @param clientY
49+
* the y coordinate of the mouse pointer relative to the viewport
4750
*/
4851
public DragEndEvent(T source, boolean fromClient,
49-
@EventData("event.dataTransfer.dropEffect") String dropEffect) {
50-
super(source, fromClient);
52+
@EventData("event.dataTransfer.dropEffect") String dropEffect,
53+
@EventData("event.clientX") int clientX,
54+
@EventData("event.clientY") int clientY) {
55+
super(source, fromClient, clientX, clientY);
5156
this.dropEffect = DropEffect.fromString(dropEffect);
5257
}
5358

@@ -87,15 +92,6 @@ public boolean isSuccessful() {
8792
return getDropEffect() != DropEffect.NONE;
8893
}
8994

90-
/**
91-
* Returns the drag source component where the dragend event occurred.
92-
*
93-
* @return Component which was dragged.
94-
*/
95-
public T getComponent() {
96-
return getSource();
97-
}
98-
9995
/**
10096
* Clears the drag data for this drag operation (and the drag source
10197
* component).

flow-dnd/src/main/java/com/vaadin/flow/component/dnd/DragSource.java

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -181,11 +181,20 @@ default void setDraggable(boolean draggable) {
181181

182182
// store & clear the component as active drag source for the UI
183183
Registration startListenerRegistration = addDragStartListener(
184-
event -> getDragSourceComponent().getUI()
185-
.orElseThrow(() -> new IllegalStateException(
186-
"DragSource not attached to an UI but received a drag start event."))
187-
.getInternals().setActiveDragSourceComponent(
188-
getDragSourceComponent()));
184+
event -> {
185+
getDragSourceComponent().getUI()
186+
.orElseThrow(() -> new IllegalStateException(
187+
"DragSource not attached to an UI but received a drag start event."))
188+
.getInternals().setActiveDragSourceComponent(
189+
getDragSourceComponent());
190+
// Store drag start offsets for use in drop event
191+
ComponentUtil.setData(getDragSourceComponent(),
192+
DndUtil.DRAG_START_OFFSET_X_KEY,
193+
event.getOffsetX());
194+
ComponentUtil.setData(getDragSourceComponent(),
195+
DndUtil.DRAG_START_OFFSET_Y_KEY,
196+
event.getOffsetY());
197+
});
189198
Registration endListenerRegistration = addDragEndListener(
190199
event -> getDragSourceComponent().getUI()
191200
.orElse(UI.getCurrent()).getInternals()

flow-dnd/src/main/java/com/vaadin/flow/component/dnd/DragStartEvent.java

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616
package com.vaadin.flow.component.dnd;
1717

1818
import com.vaadin.flow.component.Component;
19-
import com.vaadin.flow.component.ComponentEvent;
2019
import com.vaadin.flow.component.DomEvent;
20+
import com.vaadin.flow.component.EventData;
2121

2222
/**
2323
* HTML5 drag start event, fired when the user starts dragging a drag source.
@@ -26,11 +26,13 @@
2626
* Type of the component that is dragged.
2727
* @author Vaadin Ltd
2828
* @see DragSource#addDragStartListener(com.vaadin.flow.component.ComponentEventListener)
29-
* @author Vaadin Ltd
3029
* @since 2.0
3130
*/
3231
@DomEvent("dragstart")
33-
public class DragStartEvent<T extends Component> extends ComponentEvent<T> {
32+
public class DragStartEvent<T extends Component> extends AbstractDnDEvent<T> {
33+
34+
private final int offsetX;
35+
private final int offsetY;
3436

3537
/**
3638
* Creates a drag start event.
@@ -40,18 +42,51 @@ public class DragStartEvent<T extends Component> extends ComponentEvent<T> {
4042
* @param fromClient
4143
* <code>true</code> if the event originated from the client
4244
* side, <code>false</code> otherwise
45+
* @param clientX
46+
* the x coordinate of the mouse pointer relative to the viewport
47+
* @param clientY
48+
* the y coordinate of the mouse pointer relative to the viewport
49+
* @param offsetX
50+
* the x coordinate of the mouse pointer relative to the drag
51+
* source element
52+
* @param offsetY
53+
* the y coordinate of the mouse pointer relative to the drag
54+
* source element
4355
*/
44-
public DragStartEvent(T source, boolean fromClient) {
45-
super(source, fromClient);
56+
public DragStartEvent(T source, boolean fromClient,
57+
@EventData("event.clientX") int clientX,
58+
@EventData("event.clientY") int clientY,
59+
@EventData("event.offsetX") int offsetX,
60+
@EventData("event.offsetY") int offsetY) {
61+
super(source, fromClient, clientX, clientY);
62+
this.offsetX = offsetX;
63+
this.offsetY = offsetY;
4664
}
4765

4866
/**
49-
* Returns the drag source component where the dragstart event occurred.
67+
* Gets the x coordinate of the mouse pointer relative to the drag source
68+
* element when the drag started.
69+
* <p>
70+
* This is useful for maintaining the relative grab position when
71+
* positioning dropped items.
72+
*
73+
* @return the x coordinate relative to the drag source element
74+
*/
75+
public int getOffsetX() {
76+
return offsetX;
77+
}
78+
79+
/**
80+
* Gets the y coordinate of the mouse pointer relative to the drag source
81+
* element when the drag started.
82+
* <p>
83+
* This is useful for maintaining the relative grab position when
84+
* positioning dropped items.
5085
*
51-
* @return Component which is dragged.
86+
* @return the y coordinate relative to the drag source element
5287
*/
53-
public T getComponent() {
54-
return getSource();
88+
public int getOffsetY() {
89+
return offsetY;
5590
}
5691

5792
/**

flow-dnd/src/main/java/com/vaadin/flow/component/dnd/DropEvent.java

Lines changed: 76 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
import java.util.Optional;
1919

2020
import com.vaadin.flow.component.Component;
21-
import com.vaadin.flow.component.ComponentEvent;
2221
import com.vaadin.flow.component.ComponentUtil;
2322
import com.vaadin.flow.component.DomEvent;
2423
import com.vaadin.flow.component.EventData;
@@ -32,15 +31,16 @@
3231
* Type of the drop target component.
3332
* @author Vaadin Ltd
3433
* @see DropTarget#addDropListener(com.vaadin.flow.component.ComponentEventListener)
35-
* @author Vaadin Ltd
3634
* @since 2.0
3735
*/
3836
@DomEvent("drop")
39-
public class DropEvent<T extends Component> extends ComponentEvent<T> {
37+
public class DropEvent<T extends Component> extends AbstractDnDEvent<T> {
4038

4139
private final EffectAllowed effectAllowed;
4240
private final String dropEffect;
4341
private final Component dragSourceComponent;
42+
private final int offsetX;
43+
private final int offsetY;
4444

4545
/**
4646
* Creates a server side drop event.
@@ -52,12 +52,28 @@ public class DropEvent<T extends Component> extends ComponentEvent<T> {
5252
* side, <code>false</code> otherwise
5353
* @param effectAllowed
5454
* the effect allowed by the drag source
55+
* @param clientX
56+
* the x coordinate of the mouse pointer relative to the viewport
57+
* @param clientY
58+
* the y coordinate of the mouse pointer relative to the viewport
59+
* @param offsetX
60+
* the x coordinate of the mouse pointer relative to the drop
61+
* target element
62+
* @param offsetY
63+
* the y coordinate of the mouse pointer relative to the drop
64+
* target element
5565
*/
5666
public DropEvent(T source, boolean fromClient,
57-
@EventData("event.dataTransfer.effectAllowed") String effectAllowed) {
58-
super(source, fromClient);
67+
@EventData("event.dataTransfer.effectAllowed") String effectAllowed,
68+
@EventData("event.clientX") int clientX,
69+
@EventData("event.clientY") int clientY,
70+
@EventData("event.clientX - event.currentTarget.getBoundingClientRect().left") int offsetX,
71+
@EventData("event.clientY - event.currentTarget.getBoundingClientRect().top") int offsetY) {
72+
super(source, fromClient, clientX, clientY);
5973

6074
this.effectAllowed = EffectAllowed.fromString(effectAllowed);
75+
this.offsetX = offsetX;
76+
this.offsetY = offsetY;
6177
// capture drop effect from server side, since it is meant for drag
6278
// end event
6379
dropEffect = source.getElement()
@@ -114,11 +130,62 @@ public Optional<Component> getDragSourceComponent() {
114130
}
115131

116132
/**
117-
* Returns the drop target component where the drop event occurred.
133+
* Gets the x coordinate of the drop position relative to the drop target
134+
* element.
135+
* <p>
136+
* This is useful for positioning dropped items within the drop target
137+
* container using absolute or relative positioning.
138+
*
139+
* @return the x coordinate relative to the drop target element
140+
*/
141+
public int getOffsetX() {
142+
return offsetX;
143+
}
144+
145+
/**
146+
* Gets the y coordinate of the drop position relative to the drop target
147+
* element.
148+
* <p>
149+
* This is useful for positioning dropped items within the drop target
150+
* container using absolute or relative positioning.
151+
*
152+
* @return the y coordinate relative to the drop target element
153+
*/
154+
public int getOffsetY() {
155+
return offsetY;
156+
}
157+
158+
/**
159+
* Gets the x coordinate of the mouse pointer relative to the drag source
160+
* element when the drag started.
161+
* <p>
162+
* This is useful for maintaining the relative grab position when
163+
* positioning dropped items. For example, if you want items to appear where
164+
* they were grabbed (not where the cursor is), subtract this value from
165+
* {@link #getOffsetX()}.
166+
*
167+
* @return the drag start x offset if the drag source is in the same UI,
168+
* otherwise {@code 0}
169+
*/
170+
public int getDragStartOffsetX() {
171+
return getDragSourceComponent().map(component -> (Integer) ComponentUtil
172+
.getData(component, DndUtil.DRAG_START_OFFSET_X_KEY)).orElse(0);
173+
}
174+
175+
/**
176+
* Gets the y coordinate of the mouse pointer relative to the drag source
177+
* element when the drag started.
178+
* <p>
179+
* This is useful for maintaining the relative grab position when
180+
* positioning dropped items. For example, if you want items to appear where
181+
* they were grabbed (not where the cursor is), subtract this value from
182+
* {@link #getOffsetY()}.
118183
*
119-
* @return Component on which a drag source was dropped.
184+
* @return the drag start y offset if the drag source is in the same UI,
185+
* otherwise {@code 0}
120186
*/
121-
public T getComponent() {
122-
return getSource();
187+
public int getDragStartOffsetY() {
188+
return getDragSourceComponent().map(component -> (Integer) ComponentUtil
189+
.getData(component, DndUtil.DRAG_START_OFFSET_Y_KEY)).orElse(0);
123190
}
124191
}

flow-dnd/src/main/java/com/vaadin/flow/component/dnd/internal/DndUtil.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,16 @@ public class DndUtil {
4949
*/
5050
public static final String DRAG_SOURCE_DATA_KEY = "drag-source-data";
5151

52+
/**
53+
* Key for storing drag start offset X coordinate.
54+
*/
55+
public static final String DRAG_START_OFFSET_X_KEY = "drag-start-offset-x";
56+
57+
/**
58+
* Key for storing drag start offset Y coordinate.
59+
*/
60+
public static final String DRAG_START_OFFSET_Y_KEY = "drag-start-offset-y";
61+
5262
/**
5363
* Key for storing server side drag image for a
5464
* {@link com.vaadin.flow.component.dnd.DragSource}.

0 commit comments

Comments
 (0)