diff --git a/debug/tests/mousemove_on_polygons.html b/debug/tests/mousemove_on_polygons.html
new file mode 100644
index 00000000..96c08acb
--- /dev/null
+++ b/debug/tests/mousemove_on_polygons.html
@@ -0,0 +1,107 @@
+
+
+
+ Leaflet debug page
+
+
+
+
+
+
+
+
+
+
+ | Enter | Move | Exit | Click |
+ Triangle 1: | | | | |
+ Triangle 2: | | | | |
+ Map: | | | | |
+
+
+
+
+
+
+
+
+
diff --git a/debug/vector/vector-canvas.html b/debug/vector/vector-canvas.html
index 7216a646..430ed130 100644
--- a/debug/vector/vector-canvas.html
+++ b/debug/vector/vector-canvas.html
@@ -29,7 +29,8 @@
for (var i = 0, latlngs = [], len = route.length; i < len; i++) {
latlngs.push(new L.LatLng(route[i][0], route[i][1]));
}
- var path = new L.Polyline(latlngs);
+ var canvas = L.canvas();
+ var path = new L.Polyline(latlngs, {renderer: canvas});
var map = new L.Map('map', {layers: [osm]});
@@ -41,7 +42,8 @@
circleOptions = {
color: 'red',
fillColor: 'yellow',
- fillOpacity: 0.7
+ fillOpacity: 0.7,
+ renderer: canvas
};
var circle = new L.Circle(circleLocation, 500000, circleOptions),
@@ -79,7 +81,8 @@
var polygon = new L.Polygon([polygonPoints, holePoints], {
fillColor: "#333",
- color: 'green'
+ color: 'green',
+ renderer: canvas
});
group.addLayer(polygon);
diff --git a/spec/suites/map/MapSpec.js b/spec/suites/map/MapSpec.js
index eeb033fe..43f692c7 100644
--- a/spec/suites/map/MapSpec.js
+++ b/spec/suites/map/MapSpec.js
@@ -619,4 +619,132 @@ describe("Map", function () {
});
+ describe('#DOM events', function () {
+
+ var c, map;
+
+ beforeEach(function () {
+ c = document.createElement('div');
+ c.style.width = '400px';
+ c.style.height = '400px';
+ map = new L.Map(c);
+ map.setView(new L.LatLng(0, 0), 0);
+ document.body.appendChild(c);
+ });
+
+ afterEach(function () {
+ document.body.removeChild(c);
+ });
+
+ it("DOM events propagate from polygon to map", function () {
+ var spy = sinon.spy();
+ map.on("mousemove", spy);
+ var layer = new L.Polygon([[1, 2], [3, 4], [5, 6]]).addTo(map);
+ happen.mousemove(layer._path);
+ expect(spy.calledOnce).to.be.ok();
+ });
+
+ it("DOM events propagate from canvas polygon to map", function () {
+ var spy = sinon.spy();
+ map.on("mousemove", spy);
+ var layer = new L.Polygon([[1, 2], [3, 4], [5, 6]], {rendered: L.canvas()}).addTo(map);
+ happen.mousemove(layer._path);
+ expect(spy.calledOnce).to.be.ok();
+ });
+
+ it("DOM events propagate from marker to map", function () {
+ var spy = sinon.spy();
+ map.on("mousemove", spy);
+ var layer = new L.Marker([1, 2]).addTo(map);
+ happen.mousemove(layer._icon);
+ expect(spy.calledOnce).to.be.ok();
+ });
+
+ it("DOM events fired on marker can be cancelled before being caught by the map", function () {
+ var mapSpy = sinon.spy();
+ var layerSpy = sinon.spy();
+ map.on("mousemove", mapSpy);
+ var layer = new L.Marker([1, 2]).addTo(map);
+ layer.on("mousemove", L.DomEvent.stopPropagation).on("mousemove", layerSpy);
+ happen.mousemove(layer._icon);
+ expect(layerSpy.calledOnce).to.be.ok();
+ expect(mapSpy.called).not.to.be.ok();
+ });
+
+ it("DOM events fired on polygon can be cancelled before being caught by the map", function () {
+ var mapSpy = sinon.spy();
+ var layerSpy = sinon.spy();
+ map.on("mousemove", mapSpy);
+ var layer = new L.Polygon([[1, 2], [3, 4], [5, 6]]).addTo(map);
+ layer.on("mousemove", L.DomEvent.stopPropagation).on("mousemove", layerSpy);
+ happen.mousemove(layer._path);
+ expect(layerSpy.calledOnce).to.be.ok();
+ expect(mapSpy.called).not.to.be.ok();
+ });
+
+ it("DOM events fired on canvas polygon can be cancelled before being caught by the map", function () {
+ var mapSpy = sinon.spy();
+ var layerSpy = sinon.spy();
+ map.on("mousemove", mapSpy);
+ var layer = new L.Polygon([[1, 2], [3, 4], [5, 6]], {rendered: L.canvas()}).addTo(map);
+ layer.on("mousemove", L.DomEvent.stopPropagation).on("mousemove", layerSpy);
+ happen.mousemove(layer._path);
+ expect(layerSpy.calledOnce).to.be.ok();
+ expect(mapSpy.called).not.to.be.ok();
+ });
+
+ it("mouseout is only forwared if fired on the original target", function () {
+ var mapSpy = sinon.spy(),
+ layerSpy = sinon.spy(),
+ otherSpy = sinon.spy();
+ var layer = new L.Polygon([[1, 2], [3, 4], [5, 6]]).addTo(map);
+ var other = new L.Polygon([[10, 20], [30, 40], [50, 60]]).addTo(map);
+ map.on("mouseout", mapSpy);
+ layer.on("mouseout", layerSpy);
+ other.on("mouseout", otherSpy);
+ happen.mouseout(layer._path);
+ expect(mapSpy.called).not.to.be.ok();
+ expect(otherSpy.called).not.to.be.ok();
+ expect(layerSpy.calledOnce).to.be.ok();
+ });
+
+ it("mouseout is not forwared to layers if fired on the map", function () {
+ var mapSpy = sinon.spy(),
+ layerSpy = sinon.spy(),
+ otherSpy = sinon.spy();
+ var layer = new L.Polygon([[1, 2], [3, 4], [5, 6]]).addTo(map);
+ var other = new L.Polygon([[10, 20], [30, 40], [50, 60]]).addTo(map);
+ map.on("mouseout", mapSpy);
+ layer.on("mouseout", layerSpy);
+ other.on("mouseout", otherSpy);
+ happen.mouseout(map._container);
+ expect(otherSpy.called).not.to.be.ok();
+ expect(layerSpy.called).not.to.be.ok();
+ expect(mapSpy.calledOnce).to.be.ok();
+ });
+
+ it("preclick is fired before click on marker and map", function () {
+ var called = 0;
+ var layer = new L.Marker([1, 2]).addTo(map);
+ layer.on("preclick", function (e) {
+ expect(called++).to.eql(0);
+ expect(e.latlng).to.ok();
+ });
+ layer.on("click", function (e) {
+ expect(called++).to.eql(2);
+ expect(e.latlng).to.ok();
+ });
+ map.on("preclick", function (e) {
+ expect(called++).to.eql(1);
+ expect(e.latlng).to.ok();
+ });
+ map.on("click", function (e) {
+ expect(called++).to.eql(3);
+ expect(e.latlng).to.ok();
+ });
+ happen.click(layer._icon);
+ });
+
+ });
+
});
diff --git a/src/dom/DomEvent.js b/src/dom/DomEvent.js
index 9d67761f..2a87a5e6 100644
--- a/src/dom/DomEvent.js
+++ b/src/dom/DomEvent.js
@@ -130,6 +130,8 @@ L.DomEvent = {
if (e.stopPropagation) {
e.stopPropagation();
+ } else if (e.originalEvent) { // In case of Leaflet event.
+ e.originalEvent._stopped = true;
} else {
e.cancelBubble = true;
}
diff --git a/src/layer/vector/Canvas.js b/src/layer/vector/Canvas.js
index 67ea31b4..ec2c0042 100644
--- a/src/layer/vector/Canvas.js
+++ b/src/layer/vector/Canvas.js
@@ -233,7 +233,7 @@ L.Canvas = L.Renderer.extend({
},
_fireEvent: function (layer, e, type) {
- this._map._fireDOMEvent(layer, e, type || e.type);
+ this._map._fireDOMEvent(e, type || e.type, [layer]);
},
// TODO _bringToFront & _bringToBack, pretty tricky
diff --git a/src/map/Map.js b/src/map/Map.js
index 21274dc8..663e963b 100644
--- a/src/map/Map.js
+++ b/src/map/Map.js
@@ -601,6 +601,7 @@ L.Map = L.Evented.extend({
if (!L.DomEvent) { return; }
this._targets = {};
+ this._targets[L.stamp(this._container)] = this;
var onOff = remove ? 'off' : 'on';
@@ -623,62 +624,81 @@ L.Map = L.Evented.extend({
this._container.scrollLeft = 0;
},
- _findEventTarget: function (src) {
+ _findEventTargets: function (src, bubble) {
+ var targets = [], target;
while (src) {
- var target = this._targets[L.stamp(src)];
+ target = this._targets[L.stamp(src)];
if (target) {
- return target;
+ targets.push(target);
+ if (!bubble) { break; }
}
if (src === this._container) {
break;
}
src = src.parentNode;
}
- return null;
+ return targets;
},
_handleDOMEvent: function (e) {
if (!this._loaded || L.DomEvent._skipped(e)) { return; }
- // find the layer the event is propagating from
- var target = this._findEventTarget(e.target || e.srcElement),
- type = e.type === 'keypress' && e.keyCode === 13 ? 'click' : e.type;
+ // find the layer the event is propagating from and its parents
+ var type = e.type === 'keypress' && e.keyCode === 13 ? 'click' : e.type;
- // special case for map mouseover/mouseout events so that they're actually mouseenter/mouseleave
- if (!target && (type === 'mouseover' || type === 'mouseout') &&
- !L.DomEvent._checkMouse(this._container, e)) { return; }
+ if (e.type === 'click') {
+ // Fire a synthetic 'preclick' event which propagates up (mainly for closing popups).
+ var synth = L.Util.extend({}, e);
+ synth.type = 'preclick';
+ this._handleDOMEvent(synth);
+ }
- // prevents outline when clicking on keyboard-focusable element
if (type === 'mousedown') {
+ // prevents outline when clicking on keyboard-focusable element
L.DomUtil.preventOutline(e.target || e.srcElement);
}
- this._fireDOMEvent(target || this, e, type);
+ this._fireDOMEvent(e, type);
},
- _fireDOMEvent: function (target, e, type) {
- if (!target.listens(type, true) && (type !== 'click' || !target.listens('preclick', true))) { return; }
+ _fireDOMEvent: function (e, type, targets) {
if (type === 'contextmenu') {
L.DomEvent.preventDefault(e);
}
+ var isHover = type === 'mouseover' || type === 'mouseout';
+ targets = (targets || []).concat(this._findEventTargets(e.target || e.srcElement, !isHover));
+
+ if (!targets.length) {
+ targets = [this];
+
+ // special case for map mouseover/mouseout events so that they're actually mouseenter/mouseleave
+ if (isHover && !L.DomEvent._checkMouse(this._container, e)) { return; }
+ }
+
+ var target = targets[0];
+
// prevents firing click after you just dragged an object
if (e.type === 'click' && !e._simulated && this._draggableMoved(target)) { return; }
var data = {
originalEvent: e
};
+
if (e.type !== 'keypress') {
data.containerPoint = target instanceof L.Marker ?
this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e);
data.layerPoint = this.containerPointToLayerPoint(data.containerPoint);
data.latlng = this.layerPointToLatLng(data.layerPoint);
}
- if (type === 'click') {
- target.fire('preclick', data, true);
+
+ for (var i = 0; i < targets.length; i++) {
+ if (targets[i].listens(type, true)) {
+ targets[i].fire(type, data, true);
+ if (data.originalEvent._stopped) { return; }
+ }
}
- target.fire(type, data, true);
},
_draggableMoved: function (obj) {