@@ -91,31 +91,26 @@ class DropzoneGroup extends Group {
9191
9292 void _handleDragEnter (Element element, Point mousePagePosition,
9393 Point mouseClientPosition, EventTarget target) {
94- _logger.finest ('handleDragEnter {dragOverElements.length: ${ currentDragOverElements . length }} ' );
94+ _logger.finest ('handleDragEnter' );
9595
96- // Only handle dropzone element itself and not any of its children.
97- if (currentDragOverElements.isEmpty) {
98- if (currentDraggableGroup.overClass != null ) {
99- String overClass = currentDraggableGroup.overClass;
100- element.classes.add (overClass);
101-
102- // Make sure overClass is removed when drag ended. Is necessary
103- // because if drag is aborted (e.g. with esc-key), no dragLeave or
104- // drop event is fired on the dropzone.
105- StreamSubscription dragEndSub;
106- dragEndSub = currentDraggableGroup.onDragEnd.listen ((_) {
107- element.classes.remove (overClass);
108- dragEndSub.cancel ();
109- });
110- }
96+ if (currentDraggableGroup.overClass != null ) {
97+ String overClass = currentDraggableGroup.overClass;
98+ element.classes.add (overClass);
11199
112- if (_onDragEnter != null ) {
113- _onDragEnter.add (new DropzoneEvent (currentDraggable,
114- element, mousePagePosition, mouseClientPosition));
115- }
100+ // Make sure overClass is removed when drag ended. Is necessary
101+ // because if drag is aborted (e.g. with esc-key), no dragLeave or
102+ // drop event is fired on the dropzone.
103+ StreamSubscription dragEndSub;
104+ dragEndSub = currentDraggableGroup.onDragEnd.listen ((_) {
105+ element.classes.remove (overClass);
106+ dragEndSub.cancel ();
107+ });
116108 }
117109
118- currentDragOverElements.add (target);
110+ if (_onDragEnter != null ) {
111+ _onDragEnter.add (new DropzoneEvent (currentDraggable,
112+ element, mousePagePosition, mouseClientPosition));
113+ }
119114 }
120115
121116 void _handleDragOver (Element element, Point mousePagePosition, Point mouseClientPosition) {
@@ -128,26 +123,21 @@ class DropzoneGroup extends Group {
128123 void _handleDragLeave (Element element, Point mousePagePosition,
129124 Point mouseClientPosition, EventTarget target,
130125 EventTarget relatedTarget) {
131- // Firefox fires too many onDragLeave events. This condition fixes it.
132- if (target != relatedTarget) {
133- currentDragOverElements.remove (target);
126+ _logger.finest ('handleDragLeave' );
127+
128+ if (currentDraggableGroup.overClass != null ) {
129+ element.classes.remove (currentDraggableGroup.overClass);
134130 }
135- _logger.finest ('handleDragLeave {dragOverElements.length: ${currentDragOverElements .length }}' );
136131
137- // Only handle event if dropzone element is left and not on any of its children.
138- if (currentDragOverElements.isEmpty) {
139- if (currentDraggableGroup.overClass != null ) {
140- element.classes.remove (currentDraggableGroup.overClass);
141- }
142-
143- if (_onDragLeave != null ) {
144- _onDragLeave.add (new DropzoneEvent (currentDraggable, element,
145- mousePagePosition, mouseClientPosition));
146- }
132+ if (_onDragLeave != null ) {
133+ _onDragLeave.add (new DropzoneEvent (currentDraggable, element,
134+ mousePagePosition, mouseClientPosition));
147135 }
148136 }
149137
150138 void _handleDrop (Element element, Point mousePagePosition, Point mouseClientPosition) {
139+ _logger.finest ('handleDrop' );
140+
151141 if (_onDrop != null ) {
152142 _onDrop.add (new DropzoneEvent (currentDraggable, element,
153143 mousePagePosition, mouseClientPosition));
@@ -188,22 +178,37 @@ List<StreamSubscription> _installDropzone(Element element, DropzoneGroup group)
188178 // Do nothing if no element of this dnd is dragged.
189179 if (currentDraggable == null ) return ;
190180
181+ // Firefox fires too many dragEnter events. This condition fixes it.
182+ // Actually, according to W3C spec, a dragEnter event should not have a
183+ // related target at all
184+ if (mouseEvent.target == mouseEvent.relatedTarget) return ;
185+
191186 // Necessary for IE?
192187 mouseEvent.preventDefault ();
193188
194- // Test if this dropzone accepts the current draggable.
195- draggableAccepted = group._draggableAccepted ();
196- if (draggableAccepted) {
197- mouseEvent.dataTransfer.dropEffect = currentDraggableGroup.dropEffect;
198- } else {
199- mouseEvent.dataTransfer.dropEffect = 'none' ;
200- return ; // Return here as drop is not accepted.
201- }
202-
203189 _logger.finest ('dragEnter' );
204-
205- group._handleDragEnter (element, mouseEvent.page, mouseEvent.client,
206- mouseEvent.target);
190+
191+ // Related target (the element dragged from) is the last entered element.
192+ var relatedTarget = _lastDragEnterTarget;
193+
194+ // Save current target.
195+ _lastDragEnterTarget = mouseEvent.target;
196+
197+ // Only continue if the event is a real event generated for the main
198+ // element and not bubbled up by any of its children.
199+ if (_isMainEvent (element, relatedTarget)) {
200+ // Test if this dropzone accepts the current draggable.
201+ draggableAccepted = group._draggableAccepted ();
202+ if (draggableAccepted) {
203+ mouseEvent.dataTransfer.dropEffect = currentDraggableGroup.dropEffect;
204+ } else {
205+ mouseEvent.dataTransfer.dropEffect = 'none' ;
206+ return ; // Return here as drop is not accepted.
207+ }
208+
209+ group._handleDragEnter (element, mouseEvent.page, mouseEvent.client,
210+ mouseEvent.target);
211+ }
207212 }));
208213
209214 // -------------------
@@ -233,10 +238,33 @@ List<StreamSubscription> _installDropzone(Element element, DropzoneGroup group)
233238 // Do nothing if no element of this dnd is dragged.
234239 if (currentDraggable == null || ! draggableAccepted) return ;
235240
241+ // Firefox fires too many dragLeave events. This condition fixes it.
242+ // Actually, according to W3C spec, a dragLeave event should not have a
243+ // related target at all
244+ if (mouseEvent.target == mouseEvent.relatedTarget) return ;
245+
236246 _logger.finest ('dragLeave' );
237247
238- group._handleDragLeave (element, mouseEvent.page, mouseEvent.client,
239- mouseEvent.target, mouseEvent.relatedTarget);
248+ // Related target (the element dragged to) is the last entered element.
249+ var relatedTarget;
250+ if (mouseEvent.target != _lastDragEnterTarget) {
251+ relatedTarget = _lastDragEnterTarget;
252+ } else {
253+ // The mouse left the main element. When this happens, there is no
254+ // dragEnter event before the dragLeave because the entered element would
255+ // be null or an element outside of the main element.
256+ relatedTarget = null ;
257+ }
258+
259+ // Only continue if the event is a real event generated for the main
260+ // element and not bubbled up by any of its children.
261+ if (_isMainEvent (element, relatedTarget)) {
262+ // Main element was left so reset the targets.
263+ _lastDragEnterTarget = null ;
264+
265+ group._handleDragLeave (element, mouseEvent.page, mouseEvent.client,
266+ mouseEvent.target, mouseEvent.relatedTarget);
267+ }
240268 }));
241269
242270 // -------------------
@@ -245,6 +273,7 @@ List<StreamSubscription> _installDropzone(Element element, DropzoneGroup group)
245273 subs.add (element.onDrop.listen ((MouseEvent mouseEvent) {
246274 // Do nothing if no element of this dnd is dragged.
247275 if (currentDraggable == null || ! draggableAccepted) return ;
276+
248277 _logger.finest ('drop' );
249278
250279 // Stops browsers from redirecting.
@@ -256,6 +285,26 @@ List<StreamSubscription> _installDropzone(Element element, DropzoneGroup group)
256285 return subs;
257286}
258287
288+
289+ // As dragEnter and dragLeave events do not have the relatedTarget property set,
290+ // we keep track of the last target the user entered and use this as related target.
291+ EventTarget _lastDragEnterTarget;
292+
293+ /**
294+ * Returns true if the mouse event with [relatedTarget] is actually a real event
295+ * generated for [mainElement] and not bubbled up by any of its children.
296+ *
297+ * This is used to filter dragEnter, dragLeave, mouseOver, mouseOut events:
298+ * * For dragEnter and mouseOver the [relatedTarget] must be the element the
299+ * mouse entered from.
300+ * * For dragLeave and mouseOut the [relatedTarget] must be the element the
301+ * mouse entered to.
302+ */
303+ bool _isMainEvent (Element mainElement, EventTarget relatedTarget) {
304+ return (relatedTarget == null
305+ || (relatedTarget != mainElement && ! mainElement.contains (relatedTarget)));
306+ }
307+
259308/**
260309 * Event for dropzone elements.
261310 */
0 commit comments