diff --git a/debug/map/popup-keepinview.html b/debug/map/popup-keepinview.html new file mode 100644 index 000000000..7f7520ce4 --- /dev/null +++ b/debug/map/popup-keepinview.html @@ -0,0 +1,93 @@ + + + + Leaflet debug page + + + + + + + + + + + +
+ +
+ + + + + +
+ + + + diff --git a/spec/suites/layer/PopupSpec.js b/spec/suites/layer/PopupSpec.js index 9679c2558..df51157dd 100644 --- a/spec/suites/layer/PopupSpec.js +++ b/spec/suites/layer/PopupSpec.js @@ -423,6 +423,28 @@ describe('Popup', function () { .down().moveBy(10, 10, 20).up(); }); + it('moves the map over a short distance to the popup if it is not in the view (keepInView)', function (done) { + container.style.position = 'absolute'; + container.style.left = 0; + container.style.top = 0; + container.style.zIndex = 10000; + + // to prevent waiting until the animation is finished + map.options.inertia = false; + + var spy = sinon.spy(); + map.on('autopanstart', spy); + + // Short hop to the edge of the map (at time of writing, will trigger an animated pan) + var p = L.popup({keepInView: true}).setContent('Popup').setLatLng(map.getBounds()._northEast); + map.once('moveend', function () { + expect(spy.callCount).to.be(1); + expect(map.getBounds().contains(p.getLatLng())).to.be(true); + done(); + }); + map.openPopup(p); + }); + it('moves the map over a long distance to the popup if it is not in the view (keepInView)', function (done) { container.style.position = 'absolute'; container.style.left = 0; @@ -434,14 +456,30 @@ describe('Popup', function () { var spy = sinon.spy(); map.on('autopanstart', spy); - var p = L.popup({keepInView: true}).setContent('Popup').setLatLng([center[0], center[1] + 50]); - map.openPopup(p); - setTimeout(function () { - expect(spy.called).to.be(true); + // Long hop (at time of writing, will trigger a view reset) + var p = L.popup({keepInView: true}).setContent('Popup').setLatLng([center[0], center[1] + 50]); + map.once('moveend', function () { + expect(spy.callCount).to.be(1); expect(map.getBounds().contains(p.getLatLng())).to.be(true); done(); - }, 800); + }); + map.openPopup(p); + }); + + it('moves on setLatLng after initial autopan', function (done) { + var p = L.popup().setContent('Popup').setLatLng(map.getBounds().getNorthEast()); + + map.once('moveend', function () { + map.once('moveend', function () { + expect(map.getBounds().contains(p.getLatLng())).to.be(true); + done(); + }); + + p.setLatLng(map.getBounds().getNorthEast()); + }); + + map.openPopup(p); }); it("shows the popup at the correct location when multiple markers are registered", function () { diff --git a/src/layer/Popup.js b/src/layer/Popup.js index 46de30692..5da70dc71 100644 --- a/src/layer/Popup.js +++ b/src/layer/Popup.js @@ -254,10 +254,17 @@ export var Popup = DivOverlay.extend({ DomUtil.setPosition(this._container, pos.add(anchor)); }, - _adjustPan: function (e) { + _adjustPan: function () { if (!this.options.autoPan) { return; } if (this._map._panAnim) { this._map._panAnim.stop(); } + // We can endlessly recurse if keepInView is set and the view resets. + // Let's guard against that by exiting early if we're responding to our own autopan. + if (this._autopanning) { + this._autopanning = false; + return; + } + var map = this._map, marginBottom = parseInt(DomUtil.getStyle(this._container, 'marginBottom'), 10) || 0, containerHeight = this._container.offsetHeight + marginBottom, @@ -292,9 +299,14 @@ export var Popup = DivOverlay.extend({ // @event autopanstart: Event // Fired when the map starts autopanning when opening a popup. if (dx || dy) { + // Track that we're autopanning, as this function will be re-ran on moveend + if (this.options.keepInView) { + this._autopanning = true; + } + map .fire('autopanstart') - .panBy([dx, dy], {animate: e && e.type === 'moveend'}); + .panBy([dx, dy]); } },