diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 00000000..c6678be8 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,29 @@ +{ + "rules": { + "camelcase": 2, + "quotes": [2, "single"], + "no-mixed-spaces-and-tabs": [2, "smart-tabs"], + "space-after-function-name": 2, + "space-in-parens": 2, + "space-in-brackets": 2, + "space-before-blocks": 2, + "space-after-keywords": 2, + "no-lonely-if": 2, + "comma-style": 2, + "indent": [2, "tab"], + "no-underscore-dangle": 0, + "no-constant-condition": 0, + "no-multi-spaces": 0, + "strict": 0, + "key-spacing": 0, + "no-shadow": 0 + }, + "globals": { + "L": true, + "module": false, + "define": false + }, + "env": { + "browser": true + } +} diff --git a/.travis.yml b/.travis.yml index dbbdb7ac..a8a9d28f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,8 +18,8 @@ before_script: > test ${TRAVIS_BRANCH} = master || test ${TRAVIS_BRANCH} = stable && test ${TRAVIS_PULL_REQUEST} = false && - gem install --no-rdoc --no-ri --version 0.8.9 faraday && - gem install --no-rdoc --no-ri travis-artifacts || true + gem install --no-document --version 0.8.9 faraday && + gem install --no-document travis-artifacts || true after_success: > test ${TRAVIS_BRANCH} = master || test ${TRAVIS_BRANCH} = stable && diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 49c80cf6..b17605e6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -31,7 +31,7 @@ here are some tips for creating a helpful report that will make fixing it much e * Write a **descriptive, specific title**. Bad: *Problem with polylines*. Good: *Doing X in IE9 causes Z*. * Include **browser, OS and Leaflet version** info in the description. - * Create a **simple test case** that demonstrates the bug (e.g. using [JSFiddle](http://jsfiddle.net/)). + * Create a **simple test case** that demonstrates the bug (e.g. using [JSFiddle](http://jsfiddle.net/) or [JS Bin](http://jsbin.com/)). * Check whether the bug can be reproduced in **other browsers**. * Check if the bug occurs in the stable version, master, or both. * *Bonus tip:* if the bug only appears in the master version but the stable version is fine, @@ -125,7 +125,7 @@ From there you can click through folders/files to get details on their individua ## Improving Documentation The code of the live Leaflet website that contains all documentation and examples is located in the `gh-pages` branch -and is automatically generated from a set of HTML and Markdown files by [Jekyll](https://github.com/mojombo/jekyll). +and is automatically generated from a set of HTML and Markdown files by [Jekyll](http://jekyllrb.com/). The easiest way to make little improvements such as fixing typos without even leaving the browser is by editing one of the files with the online GitHub editor: diff --git a/Jakefile.js b/Jakefile.js index 1e3599c9..62e9ed2d 100644 --- a/Jakefile.js +++ b/Jakefile.js @@ -15,10 +15,10 @@ For a custom build, open build/build.html in the browser and follow the instruct var build = require('./build/build.js'), version = require('./src/Leaflet.js').version; -function hint(msg, paths) { +function hint(msg, args) { return function () { console.log(msg); - jake.exec('node node_modules/jshint/bin/jshint -c ' + paths, + jake.exec('node node_modules/eslint/bin/eslint.js ' + args, {printStdout: true}, function () { console.log('\tCheck passed.\n'); complete(); @@ -26,11 +26,11 @@ function hint(msg, paths) { }; } -desc('Check Leaflet source for errors with JSHint'); -task('lint', {async: true}, hint('Checking for JS errors...', 'build/hintrc.js src')); +desc('Check Leaflet source for errors with ESLint'); +task('lint', {async: true}, hint('Checking for JS errors...', 'src --config .eslintrc')); -desc('Check Leaflet specs source for errors with JSHint'); -task('lintspec', {async: true}, hint('Checking for specs JS errors...', 'spec/spec.hintrc.js spec/suites')); +desc('Check Leaflet specs source for errors with ESLint'); +task('lintspec', {async: true}, hint('Checking for specs JS errors...', 'spec/suites --config spec/.eslintrc')); desc('Combine and compress Leaflet source files'); task('build', {async: true}, function (compsBase32, buildName) { diff --git a/LICENSE b/LICENSE index d6258ad4..13f6bcf2 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2010-2014, Vladimir Agafonkin +Copyright (c) 2010-2015, Vladimir Agafonkin Copyright (c) 2010-2011, CloudMade All rights reserved. diff --git a/PLUGIN-GUIDE.md b/PLUGIN-GUIDE.md index dfd0cede..742583d0 100644 --- a/PLUGIN-GUIDE.md +++ b/PLUGIN-GUIDE.md @@ -113,7 +113,7 @@ Function, method and property names should be in `camelCase`.
Class names should be in `CapitalizedCamelCase`. If you have a lot of arguments in your function, consider accepting an options object instead -(putting default values where possible so that users don't need specify all of them): +(putting default values where possible so that users don't need to specify all of them): ```js // bad @@ -168,12 +168,11 @@ You can add support for AMD/CommonJS loaders to your Leaflet plugin by following // define a Common JS module that relies on 'leaflet' } else if (typeof exports === 'object') { module.exports = factory(require('leaflet')); - } // attach your plugin to the global 'L' variable - if(typeof window !== 'undefined' && window.L){ - window.L.YourPlugin = factory(L); + if (typeof window !== 'undefined' && window.L) { + window.L.YourPlugin = factory(L); } }(function (L) { var MyLeafletPlugin = {}; diff --git a/README.md b/README.md index 220b74ec..15e55b74 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ If you want to **get involved** with Leaflet development, check out the [contrib Let's make the best mapping library that will ever exist, and push the limits of what's possible with online maps! -[![Build Status](https://travis-ci.org/Leaflet/Leaflet.png?branch=master)](https://travis-ci.org/Leaflet/Leaflet) +[![Build Status](https://travis-ci.org/Leaflet/Leaflet.svg?branch=master)](https://travis-ci.org/Leaflet/Leaflet) [Vladimir Agafonkin]: http://agafonkin.com/en [contributors]: https://github.com/Leaflet/Leaflet/graphs/contributors diff --git a/build/hintrc.js b/build/hintrc.js deleted file mode 100644 index 596aeafe..00000000 --- a/build/hintrc.js +++ /dev/null @@ -1,40 +0,0 @@ -{ - // environment - "browser": true, - "node": true, - "globals": { - "L": true, - "define": true - }, - "strict": false, - "es3": true, - - // code style - "bitwise": true, - "camelcase": true, - "curly": true, - "eqeqeq": true, - "forin": false, - "immed": true, - "latedef": true, - "newcap": true, - "noarg": true, - "noempty": true, - "nonew": true, - "undef": true, - "unused": true, - "quotmark": "single", - - // whitespace - "indent": 4, - "trailing": true, - "white": true, - "smarttabs": true, - "maxlen": 120 - - // code simplicity - not enforced but nice to check from time to time - // "maxstatements": 20, - // "maxcomplexity": 5 - // "maxparams": 4, - // "maxdepth": 4 -} diff --git a/debug/map/image-overlay.html b/debug/map/image-overlay.html index f1068092..cb136015 100644 --- a/debug/map/image-overlay.html +++ b/debug/map/image-overlay.html @@ -40,8 +40,8 @@ map.addLayer(overlay); overlay.on('dblclick',function (e) { - console.log('Double click on image.') - }) + console.log('Double click on image.'); + }); diff --git a/dist/leaflet.css b/dist/leaflet.css index 5f3d8abc..e50e886c 100644 --- a/dist/leaflet.css +++ b/dist/leaflet.css @@ -139,7 +139,9 @@ /* zoom and fade animations */ -.leaflet-fade-anim .leaflet-tile, +.leaflet-fade-anim .leaflet-tile { + will-change: opacity; + } .leaflet-fade-anim .leaflet-popup { opacity: 0; -webkit-transition: opacity 0.2s linear; @@ -147,11 +149,12 @@ -o-transition: opacity 0.2s linear; transition: opacity 0.2s linear; } -.leaflet-fade-anim .leaflet-tile-loaded, .leaflet-fade-anim .leaflet-map-pane .leaflet-popup { opacity: 1; } - +.leaflet-zoom-anim .leaflet-zoom-animated { + will-change: transform; + } .leaflet-zoom-anim .leaflet-zoom-animated { -webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1); -moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1); @@ -440,7 +443,7 @@ .leaflet-popup-content-wrapper, .leaflet-popup-tip { background: white; - + color: #333; box-shadow: 0 3px 14px rgba(0,0,0,0.4); } .leaflet-container a.leaflet-popup-close-button { diff --git a/package.json b/package.json index c4ac13cf..b6e4b128 100644 --- a/package.json +++ b/package.json @@ -3,20 +3,20 @@ "version": "0.8.0-dev", "description": "JavaScript library for mobile-friendly interactive maps", "devDependencies": { - "jake": "~8.0.10", - "jshint": "~2.5.7", - "uglify-js": "~2.4.15", - "mocha": "~2.0.1", + "copyfiles": "0.1.0", + "eslint": "^0.15.1", "happen": "~0.1.3", - "karma": "~0.12.24", - "karma-mocha": "~0.1.9", - "karma-coverage": "~0.2.6", + "jake": "~8.0.10", + "karma": "~0.12.31", + "karma-chrome-launcher": "^0.1.7", + "karma-coverage": "~0.2.7", + "karma-firefox-launcher": "~0.1.4", + "karma-mocha": "~0.1.10", "karma-phantomjs-launcher": "^0.1.4", - "karma-chrome-launcher": "^0.1.5", - "karma-firefox-launcher": "~0.1.3", "karma-safari-launcher": "~0.1.1", + "mocha": "~2.1.0", "tin": "^0.5.0", - "copyfiles": "0.1.0" + "uglify-js": "~2.4.16" }, "main": "dist/leaflet-src.js", "style": "dist/leaflet.css", diff --git a/spec/.eslintrc b/spec/.eslintrc new file mode 100644 index 00000000..e3a29ac7 --- /dev/null +++ b/spec/.eslintrc @@ -0,0 +1,38 @@ +{ + "rules": { + "camelcase": 2, + "no-mixed-spaces-and-tabs": [2, "smart-tabs"], + "space-after-function-name": 2, + "space-in-parens": 2, + "space-in-brackets": 2, + "space-before-blocks": 2, + "space-after-keywords": 2, + "no-lonely-if": 2, + "comma-style": 2, + "no-unused-vars": 0, + "quotes": 0, + "no-underscore-dangle": 0, + "no-constant-condition": 0, + "no-multi-spaces": 0, + "strict": 0, + "key-spacing": 0, + "no-shadow": 0, + "no-irregular-whitespace": 0, + "no-console": 0 + }, + "globals": { + "L": true, + "module": false, + "define": false, + "expect": false, + "it": false, + "describe": false, + "sinon": false, + "happen": false, + "beforeEach": false, + "afterEach": false + }, + "env": { + "browser": true + } +} diff --git a/spec/karma.conf.js b/spec/karma.conf.js index d287ddac..b48792e4 100644 --- a/spec/karma.conf.js +++ b/spec/karma.conf.js @@ -63,6 +63,10 @@ module.exports = function (config) { // If browser does not capture in given timeout [ms], kill it captureTimeout: 5000, + // Workaround for PhantomJS random DISCONNECTED error + browserDisconnectTimeout: 10000, // default 2000 + browserDisconnectTolerance: 1, // default 0 + // Continuous Integration mode // if true, it capture browsers, run tests and exit singleRun: true diff --git a/spec/suites/SpecHelper.js b/spec/suites/SpecHelper.js index cabff4b2..1e5256b0 100644 --- a/spec/suites/SpecHelper.js +++ b/spec/suites/SpecHelper.js @@ -1,4 +1,5 @@ if (!Array.prototype.map) { + /*eslint no-extend-native:0*/ Array.prototype.map = function (fun /*, thisp */) { "use strict"; @@ -7,7 +8,6 @@ if (!Array.prototype.map) { } var t = Object(this); - // jshint bitwise: false var len = t.length >>> 0; if (typeof fun !== "function") { throw new TypeError(); diff --git a/spec/suites/core/EventsSpec.js b/spec/suites/core/EventsSpec.js index 8af595a1..2860ae53 100644 --- a/spec/suites/core/EventsSpec.js +++ b/spec/suites/core/EventsSpec.js @@ -14,7 +14,7 @@ describe('Events', function () { obj.addEventListener('test', spy1); obj.addEventListener('test', spy2); obj.addEventListener('other', spy3); - obj.addEventListener({ test: spy4, other: spy5 }); + obj.addEventListener({test: spy4, other: spy5}); // obj.addEventListener({'test other': spy6 }); expect(spy1.called).to.be(false); @@ -72,8 +72,8 @@ describe('Events', function () { obj.addEventListener('test', listener1); obj2.addEventListener('test', listener2, foo); - obj3.addEventListener({ test: listener3 }); - obj4.addEventListener({ test: listener4 }, foo); + obj3.addEventListener({test: listener3}); + obj4.addEventListener({test: listener4}, foo); obj.fireEvent('test', {baz: 1}); obj2.fireEvent('test', {baz: 2}); @@ -358,15 +358,15 @@ describe('Events', function () { }); it("doesn't call listeners to events that have been removed", function () { - var obj = new L.Evented(), - spy = sinon.spy(); + var obj = new L.Evented(), + spy = sinon.spy(); - obj.once('test', spy, obj); - obj.off('test', spy, obj); + obj.once('test', spy, obj); + obj.off('test', spy, obj); - obj.fire('test'); + obj.fire('test'); - expect(spy.called).to.be(false); + expect(spy.called).to.be(false); }); it('works if called from a context that doesnt implement #Events', function () { diff --git a/spec/suites/core/UtilSpec.js b/spec/suites/core/UtilSpec.js index a8e01991..ddd2e82d 100644 --- a/spec/suites/core/UtilSpec.js +++ b/spec/suites/core/UtilSpec.js @@ -40,9 +40,9 @@ describe('Util', function () { return this; }; - var fn2 = L.Util.bind(fn, { foo: 'bar' }); + var fn2 = L.Util.bind(fn, {foo: 'bar'}); - expect(fn2()).to.eql({ foo: 'bar' }); + expect(fn2()).to.eql({foo: 'bar'}); }); it('passes additional arguments to the bound function', function () { @@ -230,6 +230,7 @@ describe('Util', function () { describe('#isArray', function () { expect(L.Util.isArray([1, 2, 3])).to.be(true); + /*eslint no-array-constructor:0*/ expect(L.Util.isArray(new Array(1, 2, 3))).to.be(true); expect(L.Util.isArray('blabla')).to.be(false); expect(L.Util.isArray({0: 1, 1: 2})).to.be(false); diff --git a/spec/suites/geo/LatLngSpec.js b/spec/suites/geo/LatLngSpec.js index 9da63f8e..3b8bce0e 100644 --- a/spec/suites/geo/LatLngSpec.js +++ b/spec/suites/geo/LatLngSpec.js @@ -112,9 +112,8 @@ describe('LatLng', function () { }); it('accepts an object with alt', function () { - expect(L.latLng({lat: 50, lng: 30, alt:100})).to.eql(new L.LatLng(50, 30, 100)); - expect(L.latLng({lat: 50, lon: 30, alt:100})).to.eql(new L.LatLng(50, 30, 100)); + expect(L.latLng({lat: 50, lng: 30, alt: 100})).to.eql(new L.LatLng(50, 30, 100)); + expect(L.latLng({lat: 50, lon: 30, alt: 100})).to.eql(new L.LatLng(50, 30, 100)); }); }); }); - diff --git a/spec/suites/geometry/LineUtilSpec.js b/spec/suites/geometry/LineUtilSpec.js index b7251a0f..c09ac761 100644 --- a/spec/suites/geometry/LineUtilSpec.js +++ b/spec/suites/geometry/LineUtilSpec.js @@ -33,6 +33,21 @@ describe('LineUtil', function () { expect(segment).to.be(false); }); + + it('can round numbers in clipped bounds', function () { + var a = new L.Point(4, 5); + var b = new L.Point(8, 6); + + var segment1 = L.LineUtil.clipSegment(a, b, bounds); + + expect(segment1[0]).to.eql(new L.Point(5, 5.25)); + expect(segment1[1]).to.eql(b); + + var segment2 = L.LineUtil.clipSegment(a, b, bounds, false, true); + + expect(segment2[0]).to.eql(new L.Point(5, 5)); + expect(segment2[1]).to.eql(b); + }); }); describe('#pointToSegmentDistance & #closestPointOnSegment', function () { diff --git a/spec/suites/geometry/PolyUtilSpec.js b/spec/suites/geometry/PolyUtilSpec.js index 0cd382ee..6dcf7839 100644 --- a/spec/suites/geometry/PolyUtilSpec.js +++ b/spec/suites/geometry/PolyUtilSpec.js @@ -10,6 +10,7 @@ describe('PolyUtil', function () { new L.Point(10, 15) ]; + //check clip without rounding var clipped = L.PolyUtil.clipPolygon(points, bounds); for (var i = 0, len = clipped.length; i < len; i++) { @@ -17,6 +18,20 @@ describe('PolyUtil', function () { } expect(clipped).to.eql([ + new L.Point(7.5, 10), + new L.Point(5, 5), + new L.Point(10, 7.5), + new L.Point(10, 10) + ]); + + //check clip with rounding + var clippedRounded = L.PolyUtil.clipPolygon(points, bounds, true); + + for (i = 0, len = clippedRounded.length; i < len; i++) { + delete clippedRounded[i]._code; + } + + expect(clippedRounded).to.eql([ new L.Point(8, 10), new L.Point(5, 5), new L.Point(10, 8), diff --git a/spec/suites/layer/LayerGroupSpec.js b/spec/suites/layer/LayerGroupSpec.js index 21220f8e..d157fafc 100644 --- a/spec/suites/layer/LayerGroupSpec.js +++ b/spec/suites/layer/LayerGroupSpec.js @@ -58,7 +58,7 @@ it('iterates over all layers', function () { var lg = L.layerGroup(), marker = L.marker([0, 0]), - ctx = { foo: 'bar' }; + ctx = {foo: 'bar'}; lg.addLayer(marker); diff --git a/spec/suites/layer/PopupSpec.js b/spec/suites/layer/PopupSpec.js index a5bf3a27..9c970939 100644 --- a/spec/suites/layer/PopupSpec.js +++ b/spec/suites/layer/PopupSpec.js @@ -112,6 +112,41 @@ describe('Popup', function () { marker1.closePopup(); expect(spy.callCount).to.be(2); }); + + it("should take into account icon popupAnchor option", function () { + var autoPanBefore = L.Popup.prototype.options.autoPan; + L.Popup.prototype.options.autoPan = false; + var popupAnchorBefore = L.Icon.Default.prototype.options.popupAnchor; + L.Icon.Default.prototype.options.popupAnchor = [0, 0]; + + var latlng = new L.LatLng(55.8, 37.6), + offset = new L.Point(20, 30), + icon = new L.DivIcon({popupAnchor: offset}), + marker1 = new L.Marker(latlng), + marker2 = new L.Marker(latlng, {icon: icon}); + marker1.bindPopup('Popup').addTo(map); + marker1.openPopup(); + var defaultLeft = parseInt(marker1._popup._container.style.left, 10); + var defaultBottom = parseInt(marker1._popup._container.style.bottom, 10); + marker2.bindPopup('Popup').addTo(map); + marker2.openPopup(); + var offsetLeft = parseInt(marker2._popup._container.style.left, 10); + var offsetBottom = parseInt(marker2._popup._container.style.bottom, 10); + expect(offsetLeft - offset.x).to.eql(defaultLeft); + expect(offsetBottom + offset.y).to.eql(defaultBottom); + + // Now retry passing a popup instance to bindPopup + marker2.bindPopup(new L.Popup()); + marker2.openPopup(); + offsetLeft = parseInt(marker2._popup._container.style.left, 10); + offsetBottom = parseInt(marker2._popup._container.style.bottom, 10); + expect(offsetLeft - offset.x).to.eql(defaultLeft); + expect(offsetBottom + offset.y).to.eql(defaultBottom); + + L.Popup.prototype.options.autoPan = autoPanBefore; + L.Icon.Default.prototype.options.popupAnchor = popupAnchorBefore; + }); + }); describe("L.Map#openPopup", function () { diff --git a/spec/suites/layer/marker/MarkerSpec.js b/spec/suites/layer/marker/MarkerSpec.js index fc425850..2d28fac3 100644 --- a/spec/suites/layer/marker/MarkerSpec.js +++ b/spec/suites/layer/marker/MarkerSpec.js @@ -1,11 +1,16 @@ describe("Marker", function () { var map, spy, + div, icon1, icon2; beforeEach(function () { - map = L.map(document.createElement('div')).setView([0, 0], 0); + div = document.createElement('div'); + div.style.height = '100px'; + document.body.appendChild(div); + + map = L.map(div).setView([0, 0], 0); icon1 = new L.Icon.Default(); icon2 = new L.Icon.Default({ iconUrl: icon1._getIconUrl('icon') + '?2', @@ -13,6 +18,10 @@ describe("Marker", function () { }); }); + afterEach(function () { + document.body.removeChild(div); + }); + describe("#setIcon", function () { it("changes the icon to another image", function () { var marker = new L.Marker([0, 0], {icon: icon1}); @@ -40,14 +49,25 @@ describe("Marker", function () { marker.setIcon(icon1); expect(marker.dragging.enabled()).to.be(true); + + map.removeLayer(marker); + map.addLayer(marker); + + expect(marker.dragging.enabled()).to.be(true); + + map.removeLayer(marker); + // Dragging is still enabled, we should be able to disable it, + // even if marker is off the map. + marker.dragging.disable(); + map.addLayer(marker); }); it("changes the icon to another DivIcon", function () { - var marker = new L.Marker([0, 0], {icon: new L.DivIcon({html: 'Inner1Text' }) }); + var marker = new L.Marker([0, 0], {icon: new L.DivIcon({html: 'Inner1Text'})}); map.addLayer(marker); var beforeIcon = marker._icon; - marker.setIcon(new L.DivIcon({html: 'Inner2Text' })); + marker.setIcon(new L.DivIcon({html: 'Inner2Text'})); var afterIcon = marker._icon; expect(beforeIcon).to.be(afterIcon); @@ -55,7 +75,7 @@ describe("Marker", function () { }); it("removes text when changing to a blank DivIcon", function () { - var marker = new L.Marker([0, 0], {icon: new L.DivIcon({html: 'Inner1Text' }) }); + var marker = new L.Marker([0, 0], {icon: new L.DivIcon({html: 'Inner1Text'})}); map.addLayer(marker); marker.setIcon(new L.DivIcon()); @@ -65,7 +85,7 @@ describe("Marker", function () { }); it("changes a DivIcon to an image", function () { - var marker = new L.Marker([0, 0], {icon: new L.DivIcon({html: 'Inner1Text' }) }); + var marker = new L.Marker([0, 0], {icon: new L.DivIcon({html: 'Inner1Text'})}); map.addLayer(marker); var oldIcon = marker._icon; @@ -87,7 +107,7 @@ describe("Marker", function () { map.addLayer(marker); var oldIcon = marker._icon; - marker.setIcon(new L.DivIcon({html: 'Inner1Text' })); + marker.setIcon(new L.DivIcon({html: 'Inner1Text'})); expect(oldIcon).to.not.be(marker._icon); expect(oldIcon.parentNode).to.be(null); @@ -97,7 +117,7 @@ describe("Marker", function () { }); it("reuses the icon/shadow when changing icon", function () { - var marker = new L.Marker([0, 0], { icon: icon1}); + var marker = new L.Marker([0, 0], {icon: icon1}); map.addLayer(marker); var oldIcon = marker._icon; var oldShadow = marker._shadow; @@ -115,7 +135,7 @@ describe("Marker", function () { describe("#setLatLng", function () { it("fires a move event", function () { - var marker = new L.Marker([0, 0], { icon: icon1 }); + var marker = new L.Marker([0, 0], {icon: icon1}); map.addLayer(marker); var beforeLatLng = marker._latlng; @@ -134,4 +154,17 @@ describe("Marker", function () { expect(marker.getLatLng()).to.be(afterLatLng); }); }); + + describe('events', function () { + it('fires click event when clicked', function () { + var spy = sinon.spy(); + + var marker = L.marker([0, 0]).addTo(map); + + marker.on('click', spy); + happen.click(marker._icon); + + expect(spy.called).to.be.ok(); + }); + }); }); diff --git a/spec/suites/layer/tile/GridLayerSpec.js b/spec/suites/layer/tile/GridLayerSpec.js index d5cfdfe8..1ca42279 100644 --- a/spec/suites/layer/tile/GridLayerSpec.js +++ b/spec/suites/layer/tile/GridLayerSpec.js @@ -104,32 +104,6 @@ describe('GridLayer', function () { map.setZoom(0, {animate: false}); clock.tick(250); }); - - it('prunes and retains the correct tiles for back-to-back zooms', function () { - map.setView([0, 0], 1); - - var grid = L.gridLayer(); - var tiles = {}; - - grid.createTile = function (coords) { - tiles[grid._tileCoordsToKey(coords)] = true; - return document.createElement('div'); - }; - - map.addLayer(grid); - clock.tick(500); - - map.setZoom(0, {animate: false}); - clock.tick(250); - - map.setZoom(1, {animate: false}); - clock.tick(500); - - var tileContainers = div.querySelectorAll('.leaflet-tile-container'); - expect(tileContainers.length).to.eql(2); - expect(tileContainers[0].childNodes.length).to.equal(8); - expect(tileContainers[1].childNodes.length).to.equal(0); - }); }); describe("#onAdd", function () { @@ -202,11 +176,12 @@ describe('GridLayer', function () { }); describe("when a tilelayer is removed from a map", function () { it("has its zoomlevels updated to only fit the layers it currently has", function () { - var tiles = [ L.gridLayer({minZoom: 10, maxZoom: 15}).addTo(map), - L.gridLayer({minZoom: 5, maxZoom: 10}).addTo(map), - L.gridLayer({minZoom: 10, maxZoom: 20}).addTo(map), - L.gridLayer({minZoom: 0, maxZoom: 25}).addTo(map) - ]; + var tiles = [ + L.gridLayer({minZoom: 10, maxZoom: 15}).addTo(map), + L.gridLayer({minZoom: 5, maxZoom: 10}).addTo(map), + L.gridLayer({minZoom: 10, maxZoom: 20}).addTo(map), + L.gridLayer({minZoom: 0, maxZoom: 25}).addTo(map) + ]; map.whenReady(function () { expect(map.getMinZoom()).to.be(0); expect(map.getMaxZoom()).to.be(25); diff --git a/spec/suites/layer/vector/CircleMarkerSpec.js b/spec/suites/layer/vector/CircleMarkerSpec.js index 81a712ed..11094392 100644 --- a/spec/suites/layer/vector/CircleMarkerSpec.js +++ b/spec/suites/layer/vector/CircleMarkerSpec.js @@ -8,7 +8,7 @@ describe("when a CircleMarker is added to the map ", function () { describe("with a radius set as an option", function () { it("takes that radius", function () { - var marker = L.circleMarker([0, 0], { radius: 20 }).addTo(map); + var marker = L.circleMarker([0, 0], {radius: 20}).addTo(map); expect(marker._radius).to.be(20); }); @@ -16,7 +16,7 @@ describe("and radius is set before adding it", function () { it("takes that radius", function () { - var marker = L.circleMarker([0, 0], { radius: 20 }); + var marker = L.circleMarker([0, 0], {radius: 20}); marker.setRadius(15); marker.addTo(map); expect(marker._radius).to.be(15); @@ -25,7 +25,7 @@ describe("and radius is set after adding it", function () { it("takes that radius", function () { - var marker = L.circleMarker([0, 0], { radius: 20 }); + var marker = L.circleMarker([0, 0], {radius: 20}); marker.addTo(map); marker.setRadius(15); expect(marker._radius).to.be(15); @@ -34,16 +34,16 @@ describe("and setStyle is used to change the radius after adding", function () { it("takes the given radius", function () { - var marker = L.circleMarker([0, 0], { radius: 20 }); + var marker = L.circleMarker([0, 0], {radius: 20}); marker.addTo(map); - marker.setStyle({ radius: 15 }); + marker.setStyle({radius: 15}); expect(marker._radius).to.be(15); }); }); describe("and setStyle is used to change the radius before adding", function () { it("takes the given radius", function () { - var marker = L.circleMarker([0, 0], { radius: 20 }); - marker.setStyle({ radius: 15 }); + var marker = L.circleMarker([0, 0], {radius: 20}); + marker.setStyle({radius: 15}); marker.addTo(map); expect(marker._radius).to.be(15); }); diff --git a/spec/suites/map/MapSpec.js b/spec/suites/map/MapSpec.js index e7261ce0..f466f701 100644 --- a/spec/suites/map/MapSpec.js +++ b/spec/suites/map/MapSpec.js @@ -570,4 +570,24 @@ describe("Map", function () { expect(spy.called).to.be.ok(); }); }); + + describe('#flyTo', function () { + + it('move to requested center and zoom, and call zoomend once', function (done) { + var spy = sinon.spy(), + newCenter = new L.LatLng(10, 11), + newZoom = 12, + callback = function () { + expect(map.getCenter()).to.eql(newCenter); + expect(map.getZoom()).to.eql(newZoom); + spy(); + expect(spy.calledOnce).to.be.ok(); + done(); + }; + map.setView([0, 0], 0); + map.once('zoomend', callback).flyTo(newCenter, newZoom); + }); + + }); + }); diff --git a/src/control/Control.Attribution.js b/src/control/Control.Attribution.js index 4803e04a..dea9fec0 100644 --- a/src/control/Control.Attribution.js +++ b/src/control/Control.Attribution.js @@ -39,7 +39,7 @@ L.Control.Attribution = L.Control.extend({ }, addAttribution: function (text) { - if (!text) { return; } + if (!text) { return this; } if (!this._attributions[text]) { this._attributions[text] = 0; @@ -52,7 +52,7 @@ L.Control.Attribution = L.Control.extend({ }, removeAttribution: function (text) { - if (!text) { return; } + if (!text) { return this; } if (this._attributions[text]) { this._attributions[text]--; diff --git a/src/control/Control.Layers.js b/src/control/Control.Layers.js index 5366dc87..2ed054b1 100644 --- a/src/control/Control.Layers.js +++ b/src/control/Control.Layers.js @@ -123,7 +123,7 @@ L.Control.Layers = L.Control.extend({ }, _update: function () { - if (!this._container) { return; } + if (!this._container) { return this; } L.DomUtil.empty(this._baseLayersList); L.DomUtil.empty(this._overlaysList); diff --git a/src/control/Control.Zoom.js b/src/control/Control.Zoom.js index 980cf49b..978d2579 100644 --- a/src/control/Control.Zoom.js +++ b/src/control/Control.Zoom.js @@ -31,12 +31,28 @@ L.Control.Zoom = L.Control.extend({ map.off('zoomend zoomlevelschange', this._updateDisabled, this); }, + disable: function () { + this._disabled = true; + this._updateDisabled(); + return this; + }, + + enable: function () { + this._disabled = false; + this._updateDisabled(); + return this; + }, + _zoomIn: function (e) { - this._map.zoomIn(e.shiftKey ? 3 : 1); + if (!this._disabled) { + this._map.zoomIn(e.shiftKey ? 3 : 1); + } }, _zoomOut: function (e) { - this._map.zoomOut(e.shiftKey ? 3 : 1); + if (!this._disabled) { + this._map.zoomOut(e.shiftKey ? 3 : 1); + } }, _createButton: function (html, title, className, container, fn) { @@ -61,10 +77,10 @@ L.Control.Zoom = L.Control.extend({ L.DomUtil.removeClass(this._zoomInButton, className); L.DomUtil.removeClass(this._zoomOutButton, className); - if (map._zoom === map.getMinZoom()) { + if (this._disabled || map._zoom === map.getMinZoom()) { L.DomUtil.addClass(this._zoomOutButton, className); } - if (map._zoom === map.getMaxZoom()) { + if (this._disabled || map._zoom === map.getMaxZoom()) { L.DomUtil.addClass(this._zoomInButton, className); } } @@ -84,4 +100,3 @@ L.Map.addInitHook(function () { L.control.zoom = function (options) { return new L.Control.Zoom(options); }; - diff --git a/src/control/Control.js b/src/control/Control.js index 786ba559..a21b1023 100644 --- a/src/control/Control.js +++ b/src/control/Control.js @@ -71,8 +71,9 @@ L.Control = L.Class.extend({ return this; }, - _refocusOnMap: function () { - if (this._map) { + _refocusOnMap: function (e) { + // if map exists and event is not a keyboard event + if (this._map && e && e.screenX > 0 && e.screenY > 0) { this._map.getContainer().focus(); } } diff --git a/src/copyright.js b/src/copyright.js index cc6889d2..63397223 100644 --- a/src/copyright.js +++ b/src/copyright.js @@ -1,4 +1,4 @@ /* Leaflet {VERSION}, a JS library for interactive maps. http://leafletjs.com - (c) 2010-2014 Vladimir Agafonkin, (c) 2010-2011 CloudMade + (c) 2010-2015 Vladimir Agafonkin, (c) 2010-2011 CloudMade */ diff --git a/src/core/Class.js b/src/core/Class.js index c2d74cb2..e21a2420 100644 --- a/src/core/Class.js +++ b/src/core/Class.js @@ -19,7 +19,6 @@ L.Class.extend = function (props) { this.callInitHooks(); }; - // jshint camelcase: false var parentProto = NewClass.__super__ = this.prototype; var proto = L.Util.create(parentProto); @@ -27,7 +26,7 @@ L.Class.extend = function (props) { NewClass.prototype = proto; - //inherit parent's statics + // inherit parent's statics for (var i in this) { if (this.hasOwnProperty(i) && i !== 'prototype') { NewClass[i] = this[i]; diff --git a/src/core/Events.js b/src/core/Events.js index b0125488..6702d2ae 100644 --- a/src/core/Events.js +++ b/src/core/Events.js @@ -134,8 +134,8 @@ L.Evented = L.Class.extend({ events = this._events; if (events) { - var typeIndex = events[type + '_idx'], - i, len, listeners, id; + var typeIndex = events[type + '_idx'], + i, len, listeners, id; if (events[type]) { // make sure adding/removing listeners inside other listeners won't cause infinite loop diff --git a/src/core/Util.js b/src/core/Util.js index fc66a76e..5927ef01 100644 --- a/src/core/Util.js +++ b/src/core/Util.js @@ -42,9 +42,10 @@ L.Util = { // return unique ID of an object stamp: function (obj) { - // jshint camelcase: false + /*eslint-disable */ obj._leaflet_id = obj._leaflet_id || ++L.Util.lastId; return obj._leaflet_id; + /*eslint-enable */ }, lastId: 0, @@ -116,7 +117,7 @@ L.Util = { return obj.options; }, - // make an URL with GET parameters out of a set of properties/values + // make a URL with GET parameters out of a set of properties/values getParamString: function (obj, existingUrl, uppercase) { var params = []; for (var i in obj) { diff --git a/src/dom/DomEvent.js b/src/dom/DomEvent.js index 9a59441e..9d67761f 100644 --- a/src/dom/DomEvent.js +++ b/src/dom/DomEvent.js @@ -54,7 +54,7 @@ L.DomEvent = { if (L.Browser.pointer && type.indexOf('touch') === 0) { this.addPointerListener(obj, type, handler, id); - + } else if (L.Browser.touch && (type === 'dblclick') && this.addDoubleTapListener) { this.addDoubleTapListener(obj, handler, id); @@ -67,8 +67,9 @@ L.DomEvent = { } else if ((type === 'mouseenter') || (type === 'mouseleave')) { handler = function (e) { e = e || window.event; - if (!L.DomEvent._checkMouse(obj, e)) { return; } - return originalHandler(e); + if (L.DomEvent._checkMouse(obj, e)) { + originalHandler(e); + } }; obj.addEventListener(type === 'mouseenter' ? 'mouseover' : 'mouseout', handler, false); @@ -240,7 +241,7 @@ L.DomEvent = { } L.DomEvent._lastClick = timeStamp; - return handler(e); + handler(e); } }; diff --git a/src/dom/DomUtil.js b/src/dom/DomUtil.js index c13c41e7..67de1558 100644 --- a/src/dom/DomUtil.js +++ b/src/dom/DomUtil.js @@ -100,27 +100,30 @@ L.DomUtil = { el.style.opacity = value; } else if ('filter' in el.style) { + L.DomUtil._setOpacityIE(el, value); + } + }, - var filter = false, - filterName = 'DXImageTransform.Microsoft.Alpha'; + _setOpacityIE: function (el, value) { + var filter = false, + filterName = 'DXImageTransform.Microsoft.Alpha'; - // filters collection throws an error if we try to retrieve a filter that doesn't exist - try { - filter = el.filters.item(filterName); - } catch (e) { - // don't set opacity to 1 if we haven't already set an opacity, - // it isn't needed and breaks transparent pngs. - if (value === 1) { return; } - } + // filters collection throws an error if we try to retrieve a filter that doesn't exist + try { + filter = el.filters.item(filterName); + } catch (e) { + // don't set opacity to 1 if we haven't already set an opacity, + // it isn't needed and breaks transparent pngs. + if (value === 1) { return; } + } - value = Math.round(value * 100); + value = Math.round(value * 100); - if (filter) { - filter.Enabled = (value !== 100); - filter.Opacity = value; - } else { - el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')'; - } + if (filter) { + filter.Enabled = (value !== 100); + filter.Opacity = value; + } else { + el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')'; } }, @@ -145,8 +148,9 @@ L.DomUtil = { setPosition: function (el, point, no3d) { // (HTMLElement, Point[, Boolean]) - // jshint camelcase: false + /*eslint-disable */ el._leaflet_pos = point; + /*eslint-enable */ if (L.Browser.any3d && !no3d) { L.DomUtil.setTransform(el, point); @@ -160,7 +164,6 @@ L.DomUtil = { // this method is only used for elements previously positioned using setPosition, // so it's safe to cache the position for performance - // jshint camelcase: false return el._leaflet_pos; } }; @@ -216,4 +219,19 @@ L.DomUtil = { L.DomUtil.enableImageDrag = function () { L.DomEvent.off(window, 'dragstart', L.DomEvent.preventDefault); }; + + L.DomUtil.preventOutline = function (element) { + L.DomUtil.restoreOutline(); + this._outlineElement = element; + this._outlineStyle = element.style.outline; + element.style.outline = 'none'; + L.DomEvent.on(window, 'keydown', L.DomUtil.restoreOutline, this); + }; + L.DomUtil.restoreOutline = function () { + if (!this._outlineElement) { return; } + this._outlineElement.style.outline = this._outlineStyle; + delete this._outlineElement; + delete this._outlineStyle; + L.DomEvent.off(window, 'keydown', L.DomUtil.restoreOutline, this); + }; })(); diff --git a/src/dom/Draggable.js b/src/dom/Draggable.js index 2a6e9ff7..412004ab 100644 --- a/src/dom/Draggable.js +++ b/src/dom/Draggable.js @@ -20,9 +20,10 @@ L.Draggable = L.Evented.extend({ } }, - initialize: function (element, dragStartTarget) { + initialize: function (element, dragStartTarget, preventOutline) { this._element = element; this._dragStartTarget = dragStartTarget || element; + this._preventOutline = preventOutline; }, enable: function () { @@ -45,10 +46,14 @@ L.Draggable = L.Evented.extend({ _onDown: function (e) { this._moved = false; - if (e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; } + if (e.shiftKey || ((e.which !== 1) && (e.button !== 0) && !e.touches)) { return; } L.DomEvent.stopPropagation(e); + if (this._preventOutline) { + L.DomUtil.preventOutline(this._element); + } + if (L.DomUtil.hasClass(this._element, 'leaflet-zoom-anim')) { return; } L.DomUtil.disableImageDrag(); @@ -99,13 +104,15 @@ L.Draggable = L.Evented.extend({ this._moving = true; L.Util.cancelAnimFrame(this._animRequest); + this._lastEvent = e; this._animRequest = L.Util.requestAnimFrame(this._updatePosition, this, true, this._dragStartTarget); }, _updatePosition: function () { - this.fire('predrag'); + var e = {originalEvent: this._lastEvent}; + this.fire('predrag', e); L.DomUtil.setPosition(this._element, this._newPos); - this.fire('drag'); + this.fire('drag', e); }, _onUp: function () { diff --git a/src/geo/LatLng.js b/src/geo/LatLng.js index 27ec3195..f040cb62 100644 --- a/src/geo/LatLng.js +++ b/src/geo/LatLng.js @@ -80,4 +80,3 @@ L.latLng = function (a, b, c) { } return new L.LatLng(a, b, c); }; - diff --git a/src/geo/crs/CRS.Earth.js b/src/geo/crs/CRS.Earth.js index b1fb4458..d6947501 100644 --- a/src/geo/crs/CRS.Earth.js +++ b/src/geo/crs/CRS.Earth.js @@ -7,7 +7,7 @@ L.CRS.Earth = L.extend({}, L.CRS, { R: 6378137, - // distane between two geographical points using spherical law of cosines approximation + // distance between two geographical points using spherical law of cosines approximation distance: function (latlng1, latlng2) { var rad = Math.PI / 180, lat1 = latlng1.lat * rad, diff --git a/src/geometry/LineUtil.js b/src/geometry/LineUtil.js index ab6a7b6c..aecfc5b3 100644 --- a/src/geometry/LineUtil.js +++ b/src/geometry/LineUtil.js @@ -3,14 +3,12 @@ * and polylines (clipping, simplification, distances, etc.) */ -/*jshint bitwise:false */ // allow bitwise operations for this file - L.LineUtil = { // Simplify polyline with vertex reduction and Douglas-Peucker simplification. // Improves rendering performance dramatically by lessening the number of points to draw. - simplify: function (/*Point[]*/ points, /*Number*/ tolerance) { + simplify: function (points, tolerance) { if (!tolerance || !points.length) { return points.slice(); } @@ -27,11 +25,11 @@ L.LineUtil = { }, // distance from a point to a segment between two points - pointToSegmentDistance: function (/*Point*/ p, /*Point*/ p1, /*Point*/ p2) { + pointToSegmentDistance: function (p, p1, p2) { return Math.sqrt(this._sqClosestPointOnSegment(p, p1, p2, true)); }, - closestPointOnSegment: function (/*Point*/ p, /*Point*/ p1, /*Point*/ p2) { + closestPointOnSegment: function (p, p1, p2) { return this._sqClosestPointOnSegment(p, p1, p2); }, @@ -99,7 +97,7 @@ L.LineUtil = { // Cohen-Sutherland line clipping algorithm. // Used to avoid rendering parts of a polyline that are not currently visible. - clipSegment: function (a, b, bounds, useLastCode) { + clipSegment: function (a, b, bounds, useLastCode, round) { var codeA = useLastCode ? this._lastCode : this._getBitCode(a, bounds), codeB = this._getBitCode(b, bounds), @@ -118,7 +116,7 @@ L.LineUtil = { // other cases } else { codeOut = codeA || codeB; - p = this._getEdgeIntersection(a, b, codeOut, bounds); + p = this._getEdgeIntersection(a, b, codeOut, bounds, round); newCode = this._getBitCode(p, bounds); if (codeOut === codeA) { @@ -132,7 +130,7 @@ L.LineUtil = { } }, - _getEdgeIntersection: function (a, b, code, bounds) { + _getEdgeIntersection: function (a, b, code, bounds, round) { var dx = b.x - a.x, dy = b.y - a.y, min = bounds.min, @@ -156,7 +154,7 @@ L.LineUtil = { y = a.y + dy * (min.x - a.x) / dx; } - return new L.Point(x, y, true); + return new L.Point(x, y, round); }, _getBitCode: function (/*Point*/ p, bounds) { diff --git a/src/geometry/Point.js b/src/geometry/Point.js index 20c5aa59..be4437c8 100644 --- a/src/geometry/Point.js +++ b/src/geometry/Point.js @@ -2,7 +2,7 @@ * L.Point represents a point with x and y coordinates. */ -L.Point = function (/*Number*/ x, /*Number*/ y, /*Boolean*/ round) { +L.Point = function (x, y, round) { this.x = (round ? Math.round(x) : x); this.y = (round ? Math.round(y) : y); }; diff --git a/src/geometry/PolyUtil.js b/src/geometry/PolyUtil.js index 68aa36d8..97723f3a 100644 --- a/src/geometry/PolyUtil.js +++ b/src/geometry/PolyUtil.js @@ -2,15 +2,13 @@ * L.PolyUtil contains utility functions for polygons (clipping, etc.). */ -/*jshint bitwise:false */ // allow bitwise operations here - L.PolyUtil = {}; /* * Sutherland-Hodgeman polygon clipping algorithm. * Used to avoid rendering parts of a polygon that are not currently visible. */ -L.PolyUtil.clipPolygon = function (points, bounds) { +L.PolyUtil.clipPolygon = function (points, bounds, round) { var clippedPoints, edges = [1, 4, 2, 8], i, j, k, @@ -35,7 +33,7 @@ L.PolyUtil.clipPolygon = function (points, bounds) { if (!(a._code & edge)) { // if b is outside the clip window (a->b goes out of screen) if (b._code & edge) { - p = lu._getEdgeIntersection(b, a, edge, bounds); + p = lu._getEdgeIntersection(b, a, edge, bounds, round); p._code = lu._getBitCode(p, bounds); clippedPoints.push(p); } @@ -43,7 +41,7 @@ L.PolyUtil.clipPolygon = function (points, bounds) { // else if b is inside the clip window (a->b enters the screen) } else if (!(b._code & edge)) { - p = lu._getEdgeIntersection(b, a, edge, bounds); + p = lu._getEdgeIntersection(b, a, edge, bounds, round); p._code = lu._getBitCode(p, bounds); clippedPoints.push(p); } diff --git a/src/layer/GeoJSON.js b/src/layer/GeoJSON.js index d8700a8a..cb29d8de 100644 --- a/src/layer/GeoJSON.js +++ b/src/layer/GeoJSON.js @@ -31,7 +31,7 @@ L.GeoJSON = L.FeatureGroup.extend({ var options = this.options; - if (options.filter && !options.filter(geojson)) { return; } + if (options.filter && !options.filter(geojson)) { return this; } var layer = L.GeoJSON.geometryToLayer(geojson, options); layer.feature = L.GeoJSON.asFeature(geojson); @@ -146,7 +146,7 @@ L.extend(L.GeoJSON, { for (var i = 0, len = latlngs.length; i < len; i++) { coords.push(levelsDeep ? - L.GeoJSON.latLngsToCoords(latlngs[i], levelsDeep - 1, closed): + L.GeoJSON.latLngsToCoords(latlngs[i], levelsDeep - 1, closed) : L.GeoJSON.latLngToCoords(latlngs[i])); } diff --git a/src/layer/ImageOverlay.js b/src/layer/ImageOverlay.js index 5862e965..fdd80496 100644 --- a/src/layer/ImageOverlay.js +++ b/src/layer/ImageOverlay.js @@ -26,13 +26,20 @@ L.ImageOverlay = L.Layer.extend({ } } + if (this.options.interactive) { + L.DomUtil.addClass(this._image, 'leaflet-interactive'); + this.addInteractiveTarget(this._image); + } + this.getPane().appendChild(this._image); - this._initInteraction(); this._reset(); }, onRemove: function () { L.DomUtil.remove(this._image); + if (this.options.interactive) { + this.removeInteractiveTarget(this._image); + } }, setOpacity: function (opacity) { @@ -65,19 +72,6 @@ L.ImageOverlay = L.Layer.extend({ return this; }, - _initInteraction: function () { - if (!this.options.interactive) { return; } - L.DomUtil.addClass(this._image, 'leaflet-interactive'); - L.DomEvent.on(this._image, 'click dblclick mousedown mouseup mouseover mousemove mouseout contextmenu', - this._fireMouseEvent, this); - }, - - _fireMouseEvent: function (e, type) { - if (this._map) { - this._map._fireMouseEvent(this, e, type, true); - } - }, - setUrl: function (url) { this._url = url; diff --git a/src/layer/Layer.Popup.js b/src/layer/Layer.Popup.js index 5b9a2956..189fdf3f 100644 --- a/src/layer/Layer.Popup.js +++ b/src/layer/Layer.Popup.js @@ -7,6 +7,7 @@ L.Layer.include({ bindPopup: function (content, options) { if (content instanceof L.Popup) { + L.setOptions(content, options); this._popup = content; content._source = this; } else { @@ -124,4 +125,4 @@ L.Layer.include({ _popupLatLng: function(){ return this._latlng || this.getCenter(); } -}); \ No newline at end of file +}); diff --git a/src/layer/Layer.js b/src/layer/Layer.js index 32b2c44b..579d30b5 100644 --- a/src/layer/Layer.js +++ b/src/layer/Layer.js @@ -25,6 +25,16 @@ L.Layer = L.Evented.extend({ return this._map.getPane(name ? (this.options[name] || name) : this.options.pane); }, + addInteractiveTarget: function (targetEl) { + this._map._targets[L.stamp(targetEl)] = this; + return this; + }, + + removeInteractiveTarget: function (targetEl) { + delete this._map._targets[L.stamp(targetEl)]; + return this; + }, + _layerAdd: function (e) { var map = e.target; diff --git a/src/layer/marker/Marker.Drag.js b/src/layer/marker/Marker.Drag.js index 911fa852..108bcff3 100644 --- a/src/layer/marker/Marker.Drag.js +++ b/src/layer/marker/Marker.Drag.js @@ -11,7 +11,7 @@ L.Handler.MarkerDrag = L.Handler.extend({ var icon = this._marker._icon; if (!this._draggable) { - this._draggable = new L.Draggable(icon, icon); + this._draggable = new L.Draggable(icon, icon, true); } this._draggable.on({ @@ -30,7 +30,9 @@ L.Handler.MarkerDrag = L.Handler.extend({ dragend: this._onDragEnd }, this).disable(); - L.DomUtil.removeClass(this._marker._icon, 'leaflet-marker-draggable'); + if (this._marker._icon) { + L.DomUtil.removeClass(this._marker._icon, 'leaflet-marker-draggable'); + } }, moved: function () { @@ -44,7 +46,7 @@ L.Handler.MarkerDrag = L.Handler.extend({ .fire('dragstart'); }, - _onDrag: function () { + _onDrag: function (e) { var marker = this._marker, shadow = marker._shadow, iconPos = L.DomUtil.getPosition(marker._icon), @@ -56,10 +58,11 @@ L.Handler.MarkerDrag = L.Handler.extend({ } marker._latlng = latlng; + e.latlng = latlng; marker - .fire('move', {latlng: latlng}) - .fire('drag'); + .fire('move', e) + .fire('drag', e); }, _onDragEnd: function (e) { diff --git a/src/layer/marker/Marker.js b/src/layer/marker/Marker.js index d8b001da..575a318c 100644 --- a/src/layer/marker/Marker.js +++ b/src/layer/marker/Marker.js @@ -32,8 +32,8 @@ L.Marker = L.Layer.extend({ }, onRemove: function () { - if (this.dragging) { - this.dragging.disable(); + if (this.dragging && this.dragging.enabled()) { + this.dragging.removeHooks(); } this._removeIcon(); @@ -58,7 +58,7 @@ L.Marker = L.Layer.extend({ var oldLatLng = this._latlng; this._latlng = L.latLng(latlng); this.update(); - return this.fire('move', { oldLatLng: oldLatLng, latlng: this._latlng }); + return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng}); }, setZIndexOffset: function (offset) { @@ -123,11 +123,11 @@ L.Marker = L.Layer.extend({ this._icon = icon; this._initInteraction(); - if (L.DomEvent && options.riseOnHover) { - L.DomEvent.on(icon, { + if (options.riseOnHover) { + this.on({ mouseover: this._bringToFront, mouseout: this._resetZIndex - }, this); + }); } var newShadow = options.icon.createShadow(this._shadow), @@ -158,14 +158,15 @@ L.Marker = L.Layer.extend({ }, _removeIcon: function () { - if (L.DomEvent && this.options.riseOnHover) { - L.DomEvent.off(this._icon, { + if (this.options.riseOnHover) { + this.off({ mouseover: this._bringToFront, mouseout: this._resetZIndex - }, this); + }); } L.DomUtil.remove(this._icon); + this.removeInteractiveTarget(this._icon); this._icon = null; }, @@ -205,11 +206,7 @@ L.Marker = L.Layer.extend({ L.DomUtil.addClass(this._icon, 'leaflet-interactive'); - if (L.DomEvent) { - L.DomEvent.on(this._icon, - 'click dblclick mousedown mouseup mouseover mousemove mouseout contextmenu keypress', - this._fireMouseEvent, this); - } + this.addInteractiveTarget(this._icon); if (L.Handler.MarkerDrag) { var draggable = this.options.draggable; @@ -226,21 +223,6 @@ L.Marker = L.Layer.extend({ } }, - _fireMouseEvent: function (e, type) { - // to prevent outline when clicking on keyboard-focusable marker - if (e.type === 'mousedown') { - L.DomEvent.preventDefault(e); - } - - if (e.type === 'keypress' && e.keyCode === 13) { - type = 'click'; - } - - if (this._map) { - this._map._fireMouseEvent(this, e, type, true, this._latlng); - } - }, - setOpacity: function (opacity) { this.options.opacity = opacity; if (this._map) { diff --git a/src/layer/tile/GridLayer.js b/src/layer/tile/GridLayer.js index 6203929e..7b4a04b7 100644 --- a/src/layer/tile/GridLayer.js +++ b/src/layer/tile/GridLayer.js @@ -10,7 +10,6 @@ L.GridLayer = L.Layer.extend({ tileSize: 256, opacity: 1, - unloadInvisibleTiles: L.Browser.mobile, updateWhenIdle: L.Browser.mobile, updateInterval: 200, @@ -29,16 +28,10 @@ L.GridLayer = L.Layer.extend({ onAdd: function () { this._initContainer(); - this._pruneTiles = L.Util.throttle(this._pruneTiles, 200, this); - this._levels = {}; - this._tiles = {}; - this._loaded = {}; - this._retain = {}; - this._tilesToLoad = 0; - this._reset(); + this._viewReset(); this._update(); }, @@ -103,13 +96,13 @@ L.GridLayer = L.Layer.extend({ getEvents: function () { var events = { - viewreset: this._reset, - moveend: this._update + viewreset: this._viewReset, + moveend: this._move }; if (!this.options.updateWhenIdle) { // update tiles on move, but not more often than once per given interval - events.move = L.Util.throttle(this._update, this.options.updateInterval, this); + events.move = L.Util.throttle(this._move, this.options.updateInterval, this); } if (this._zoomAnimated) { @@ -153,13 +146,33 @@ L.GridLayer = L.Layer.extend({ _updateOpacity: function () { var opacity = this.options.opacity; - if (L.Browser.ielt9) { - // IE doesn't inherit filter opacity properly, so we're forced to set it on tiles - for (var i in this._tiles) { - L.DomUtil.setOpacity(this._tiles[i], opacity); - } - } else { + // IE doesn't inherit filter opacity properly, so we're forced to set it on tiles + if (!L.Browser.ielt9 && !this._map._fadeAnimated) { L.DomUtil.setOpacity(this._container, opacity); + return; + } + + var now = +new Date(), + nextFrame = false; + + for (var key in this._tiles) { + var tile = this._tiles[key]; + if (!tile.current || !tile.loaded || tile.active) { continue; } + + var fade = Math.min(1, (now - tile.loaded) / 200); + if (fade < 1) { + L.DomUtil.setOpacity(tile.el, opacity * fade); + nextFrame = true; + } else { + L.DomUtil.setOpacity(tile.el, opacity); + tile.active = true; + this._pruneTiles(); + } + } + + if (nextFrame) { + L.Util.cancelAnimFrame(this._fadeFrame); + this._fadeFrame = L.Util.requestAnimFrame(this._updateOpacity, this); } }, @@ -177,10 +190,16 @@ L.GridLayer = L.Layer.extend({ }, _updateLevels: function () { - var zoom = this._tileZoom; + var zoom = this._tileZoom, + maxZoom = this.options.maxZoom; for (var z in this._levels) { - this._levels[z].el.style.zIndex = -Math.abs(zoom - z); + if (this._levels[z].el.children.length || z === zoom) { + this._levels[z].el.style.zIndex = maxZoom - Math.abs(zoom - z); + } else { + L.DomUtil.remove(this._levels[z].el); + delete this._levels[z]; + } } var level = this._levels[zoom], @@ -190,10 +209,15 @@ L.GridLayer = L.Layer.extend({ level = this._levels[zoom] = {}; level.el = L.DomUtil.create('div', 'leaflet-tile-container leaflet-zoom-animated', this._container); - level.el.style.zIndex = 0; + level.el.style.zIndex = maxZoom; level.origin = map.project(map.unproject(map.getPixelOrigin()), zoom).round(); level.zoom = zoom; + + this._setZoomTransform(level, map.getCenter(), map.getZoom()); + + // force the browser to consider the newly added element for transition + L.Util.falseFn(level.el.offsetWidth); } this._level = level; @@ -202,39 +226,26 @@ L.GridLayer = L.Layer.extend({ }, _pruneTiles: function () { + var key, tile; - if (!this._map) { return; } + for (key in this._tiles) { + tile = this._tiles[key]; + tile.retain = tile.current; + } - this._retain = {}; - - var bounds = this._map.getBounds(), - z = this._tileZoom, - range = this._getTileRange(bounds, z), - i, j, key, found; - - for (i = range.min.x; i <= range.max.x; i++) { - for (j = range.min.y; j <= range.max.y; j++) { - - key = i + ':' + j + ':' + z; - - this._retain[key] = true; - - if (!this._loaded[key]) { - found = this._retainParent(i, j, z, z - 5) || this._retainChildren(i, j, z, z + 2); + for (key in this._tiles) { + tile = this._tiles[key]; + if (tile.current && !tile.active) { + var coords = tile.coords; + if (!this._retainParent(coords.x, coords.y, coords.z, coords.z - 5)) { + this._retainChildren(coords.x, coords.y, coords.z, coords.z + 2); } } } for (key in this._tiles) { - if (!this._retain[key]) { - if (!this._loaded[key]) { - this._removeTile(key); - this._tilesToLoad--; - } else if (this._map._fadeAnimated) { - setTimeout(L.bind(this._deferRemove, this, key), 250); - } else { - this._removeTile(key); - } + if (!this._tiles[key].retain) { + this._removeTile(key); } } }, @@ -243,13 +254,6 @@ L.GridLayer = L.Layer.extend({ for (var key in this._tiles) { this._removeTile(key); } - this._tilesToLoad = 0; - }, - - _deferRemove: function (key) { - if (!this._retain[key]) { - this._removeTile(key); - } }, _retainParent: function (x, y, z, minZoom) { @@ -257,13 +261,18 @@ L.GridLayer = L.Layer.extend({ y2 = Math.floor(y / 2), z2 = z - 1; - var key = x2 + ':' + y2 + ':' + z2; + var key = x2 + ':' + y2 + ':' + z2, + tile = this._tiles[key]; - if (this._loaded[key]) { - this._retain[key] = true; + if (tile && tile.active) { + tile.retain = true; return true; - } else if (z2 > minZoom) { + } else if (tile && tile.loaded) { + tile.retain = true; + } + + if (z2 > minZoom) { return this._retainParent(x2, y2, z2, minZoom); } @@ -275,34 +284,56 @@ L.GridLayer = L.Layer.extend({ for (var i = 2 * x; i < 2 * x + 2; i++) { for (var j = 2 * y; j < 2 * y + 2; j++) { - var key = i + ':' + j + ':' + (z + 1); + var key = i + ':' + j + ':' + (z + 1), + tile = this._tiles[key]; - if (this._loaded[key]) { - this._retain[key] = true; + if (tile && tile.active) { + tile.retain = true; + continue; - } else if (z + 1 < maxZoom) { + } else if (tile && tile.loaded) { + tile.retain = true; + } + + if (z + 1 < maxZoom) { this._retainChildren(i, j, z + 1, maxZoom); } } } }, - _reset: function (e) { - var map = this._map, - zoom = map.getZoom(), - tileZoom = Math.round(zoom), - tileZoomChanged = this._tileZoom !== tileZoom; + _viewReset: function (e) { + this._reset(this._map.getCenter(), this._map.getZoom(), e && e.hard); + }, + + _animateZoom: function (e) { + this._reset(e.center, e.zoom, false, true, e.noUpdate); + }, + + _reset: function (center, zoom, hard, noPrune, noUpdate) { + var tileZoom = Math.round(zoom), + tileZoomChanged = this._tileZoom !== tileZoom; + + if (!noUpdate && (hard || tileZoomChanged)) { - if (tileZoomChanged || (e && e.hard)) { if (this._abortLoading) { this._abortLoading(); } + this._tileZoom = tileZoom; this._updateLevels(); this._resetGrid(); + + if (!L.Browser.mobileWebkit) { + this._update(center, tileZoom); + } + + if (!noPrune) { + this._pruneTiles(); + } } - this._setZoomTransforms(map.getCenter(), zoom); + this._setZoomTransforms(center, zoom); }, _setZoomTransforms: function (center, zoom) { @@ -344,8 +375,14 @@ L.GridLayer = L.Layer.extend({ return this.options.tileSize; }, - _update: function () { - if (!this._map) { return; } + _move: function () { + this._update(); + this._pruneTiles(); + }, + + _update: function (center, zoom) { + var map = this._map; + if (!map) { return; } // TODO move to reset // var zoom = this._map.getZoom(); @@ -353,38 +390,30 @@ L.GridLayer = L.Layer.extend({ // if (zoom > this.options.maxZoom || // zoom < this.options.minZoom) { return; } - var bounds = this._map.getBounds(); + if (center === undefined) { center = map.getCenter(); } + if (zoom === undefined) { zoom = Math.round(map.getZoom()); } - if (this.options.unloadInvisibleTiles) { - this._removeOtherTiles(bounds); + var pixelBounds = map.getPixelBounds(center, zoom), + tileRange = this._pxBoundsToTileRange(pixelBounds), + tileCenter = tileRange.getCenter(), + queue = []; + + for (var key in this._tiles) { + this._tiles[key].current = false; } - this._addTiles(bounds); - }, - - // tile coordinates range for particular geo bounds and zoom - _getTileRange: function (bounds, zoom) { - var pxBounds = new L.Bounds( - this._map.project(bounds.getNorthWest(), zoom), - this._map.project(bounds.getSouthEast(), zoom)); - return this._pxBoundsToTileRange(pxBounds); - }, - - _addTiles: function (bounds) { - var queue = [], - tileRange = this._getTileRange(bounds, this._tileZoom), - center = tileRange.getCenter(), - j, i, coords; - // create a queue of coordinates to load tiles from - for (j = tileRange.min.y; j <= tileRange.max.y; j++) { - for (i = tileRange.min.x; i <= tileRange.max.x; i++) { + for (var j = tileRange.min.y; j <= tileRange.max.y; j++) { + for (var i = tileRange.min.x; i <= tileRange.max.x; i++) { + var coords = new L.Point(i, j); + coords.z = zoom; - coords = new L.Point(i, j); - coords.z = this._tileZoom; + if (!this._isValidTile(coords)) { continue; } - // add tile to queue if it's not in cache or out of bounds - if (!(this._tileCoordsToKey(coords) in this._tiles) && this._isValidTile(coords)) { + var tile = this._tiles[this._tileCoordsToKey(coords)]; + if (tile) { + tile.current = true; + } else { queue.push(coords); } } @@ -392,31 +421,24 @@ L.GridLayer = L.Layer.extend({ // sort tile queue to load tiles in order of their distance to center queue.sort(function (a, b) { - return a.distanceTo(center) - b.distanceTo(center); + return a.distanceTo(tileCenter) - b.distanceTo(tileCenter); }); - var tilesToLoad = queue.length; - - - if (tilesToLoad !== 0) { + if (queue.length !== 0) { // if its the first batch of tiles to load - if (!this._tilesToLoad) { + if (this._noTilesToLoad()) { this.fire('loading'); } - this._tilesToLoad += tilesToLoad; - // create DOM fragment to append tiles in one batch var fragment = document.createDocumentFragment(); - for (i = 0; i < tilesToLoad; i++) { + for (i = 0; i < queue.length; i++) { this._addTile(queue[i], fragment); } this._level.el.appendChild(fragment); } - - this._pruneTiles(); }, _isValidTile: function (coords) { @@ -468,27 +490,16 @@ L.GridLayer = L.Layer.extend({ return coords; }, - // remove any present tiles that are off the specified bounds - _removeOtherTiles: function (bounds) { - for (var key in this._tiles) { - var tileBounds = this._keyToBounds(key); - if (!bounds.intersects(tileBounds)) { - this._removeTile(key); - } - } - }, - _removeTile: function (key) { var tile = this._tiles[key]; if (!tile) { return; } - L.DomUtil.remove(tile); + L.DomUtil.remove(tile.el); delete this._tiles[key]; - delete this._loaded[key]; this.fire('tileunload', { - tile: tile, + tile: tile.el, coords: this._keyToTileCoords(key) }); }, @@ -534,7 +545,11 @@ L.GridLayer = L.Layer.extend({ L.DomUtil.setPosition(tile, tilePos, true); // save tile in cache - this._tiles[key] = tile; + this._tiles[key] = { + el: tile, + coords: coords, + current: true + }; container.appendChild(tile); this.fire('tileloadstart', { @@ -544,6 +559,8 @@ L.GridLayer = L.Layer.extend({ }, _tileReady: function (coords, err, tile) { + if (!this._map) { return; } + if (err) { this.fire('tileerror', { error: err, @@ -554,21 +571,27 @@ L.GridLayer = L.Layer.extend({ var key = this._tileCoordsToKey(coords); - if (!this._tiles[key]) { return; } + tile = this._tiles[key]; + if (!tile) { return; } - this._loaded[key] = true; - this._pruneTiles(); + tile.loaded = +new Date(); + if (this._map._fadeAnimated) { + L.DomUtil.setOpacity(tile.el, 0); + L.Util.cancelAnimFrame(this._fadeFrame); + this._fadeFrame = L.Util.requestAnimFrame(this._updateOpacity, this); + } else { + tile.active = true; + this._pruneTiles(); + } - L.DomUtil.addClass(tile, 'leaflet-tile-loaded'); + L.DomUtil.addClass(tile.el, 'leaflet-tile-loaded'); this.fire('tileload', { - tile: tile, + tile: tile.el, coords: coords }); - this._tilesToLoad--; - - if (this._tilesToLoad === 0) { + if (this._noTilesToLoad()) { this.fire('load'); } }, @@ -591,8 +614,11 @@ L.GridLayer = L.Layer.extend({ bounds.max.divideBy(this._tileSize).ceil().subtract([1, 1])); }, - _animateZoom: function (e) { - this._setZoomTransforms(e.center, e.zoom); + _noTilesToLoad: function () { + for (var key in this._tiles) { + if (!this._tiles[key].loaded) { return false; } + } + return true; } }); diff --git a/src/layer/tile/TileLayer.js b/src/layer/tile/TileLayer.js index 94f1f21a..c23e8688 100644 --- a/src/layer/tile/TileLayer.js +++ b/src/layer/tile/TileLayer.js @@ -99,18 +99,17 @@ L.TileLayer = L.GridLayer.extend({ _getTileSize: function () { var map = this._map, options = this.options, - zoom = map.getZoom() + options.zoomOffset, + zoom = this._tileZoom + options.zoomOffset, zoomN = options.maxNativeZoom; // increase tile size when overscaling return zoomN !== null && zoom > zoomN ? - Math.round(map.getZoomScale(zoomN, zoom) * options.tileSize) : + Math.round(options.tileSize / map.getZoomScale(zoomN, zoom)) : options.tileSize; }, _onTileRemove: function (e) { e.tile.onload = null; - e.tile.src = L.Util.emptyImageUrl; }, _getZoomForUrl: function () { @@ -136,7 +135,7 @@ L.TileLayer = L.GridLayer.extend({ _abortLoading: function () { var i, tile; for (i in this._tiles) { - tile = this._tiles[i]; + tile = this._tiles[i].el; tile.onload = L.Util.falseFn; tile.onerror = L.Util.falseFn; diff --git a/src/layer/vector/Canvas.js b/src/layer/vector/Canvas.js index 3de15af9..9445ff6a 100644 --- a/src/layer/vector/Canvas.js +++ b/src/layer/vector/Canvas.js @@ -114,7 +114,7 @@ L.Canvas = L.Renderer.extend({ len = parts.length, ctx = this._ctx; - if (!len) { return; } + if (!len) { return; } ctx.beginPath(); @@ -169,7 +169,7 @@ L.Canvas = L.Renderer.extend({ ctx.fill(options.fillRule || 'evenodd'); } - if (options.stroke) { + if (options.stroke && options.weight !== 0) { ctx.globalAlpha = clear ? 1 : options.opacity; // if clearing shape, do it with the previously drawn line width @@ -190,7 +190,8 @@ L.Canvas = L.Renderer.extend({ for (var id in this._layers) { if (this._layers[id]._containsPoint(point)) { - this._layers[id]._fireMouseEvent(e); + L.DomEvent._fakeStop(e); + this._fireEvent(this._layers[id], e); } } }, @@ -213,20 +214,24 @@ L.Canvas = L.Renderer.extend({ // if we just got inside the layer, fire mouseover if (!layer._mouseInside) { L.DomUtil.addClass(this._container, 'leaflet-interactive'); // change cursor - layer._fireMouseEvent(e, 'mouseover'); + this._fireEvent(layer, e, 'mouseover'); layer._mouseInside = true; } // fire mousemove - layer._fireMouseEvent(e); + this._fireEvent(layer, e); } else if (layer._mouseInside) { // if we're leaving the layer, fire mouseout L.DomUtil.removeClass(this._container, 'leaflet-interactive'); - layer._fireMouseEvent(e, 'mouseout'); + this._fireEvent(layer, e, 'mouseout'); layer._mouseInside = false; } }, + _fireEvent: function (layer, e, type) { + this._map._fireDOMEvent(layer, e, type || e.type); + }, + // TODO _bringToFront & _bringToBack, pretty tricky _bringToFront: L.Util.falseFn, diff --git a/src/layer/vector/Path.js b/src/layer/vector/Path.js index 6247c997..c305e516 100644 --- a/src/layer/vector/Path.js +++ b/src/layer/vector/Path.js @@ -74,10 +74,6 @@ L.Path = L.Layer.extend({ return this; }, - _fireMouseEvent: function (e, type) { - this._map._fireMouseEvent(this, e, type, true); - }, - _clickTolerance: function () { // used when doing hit detection for Canvas layers return (this.options.stroke ? this.options.weight / 2 : 0) + (L.Browser.touch ? 10 : 0); diff --git a/src/layer/vector/Polygon.js b/src/layer/vector/Polygon.js index 03438887..f35bc6df 100644 --- a/src/layer/vector/Polygon.js +++ b/src/layer/vector/Polygon.js @@ -58,7 +58,7 @@ L.Polygon = L.Polyline.extend({ this._parts = []; for (var i = 0, len = this._rings.length, clipped; i < len; i++) { - clipped = L.PolyUtil.clipPolygon(this._rings[i], bounds); + clipped = L.PolyUtil.clipPolygon(this._rings[i], bounds, true); if (clipped.length) { this._parts.push(clipped); } diff --git a/src/layer/vector/Polyline.js b/src/layer/vector/Polyline.js index a045b28a..9f2209fc 100644 --- a/src/layer/vector/Polyline.js +++ b/src/layer/vector/Polyline.js @@ -179,7 +179,7 @@ L.Polyline = L.Path.extend({ points = this._rings[i]; for (j = 0, len2 = points.length; j < len2 - 1; j++) { - segment = L.LineUtil.clipSegment(points[j], points[j + 1], bounds, j); + segment = L.LineUtil.clipSegment(points[j], points[j + 1], bounds, j, true); if (!segment) { continue; } diff --git a/src/layer/vector/Renderer.js b/src/layer/vector/Renderer.js index c45e885f..a9183f11 100644 --- a/src/layer/vector/Renderer.js +++ b/src/layer/vector/Renderer.js @@ -64,7 +64,7 @@ L.Renderer = L.Layer.extend({ L.Map.include({ // used by each vector layer to decide which renderer to use getRenderer: function (layer) { - var renderer = layer.options.renderer || this.options.renderer || this._renderer; + var renderer = layer.options.renderer || this._getPaneRenderer(layer.options.pane) || this.options.renderer || this._renderer; if (!renderer) { renderer = this._renderer = (L.SVG && L.svg()) || (L.Canvas && L.canvas()); @@ -74,5 +74,18 @@ L.Map.include({ this.addLayer(renderer); } return renderer; + }, + + _getPaneRenderer: function (name) { + if (name === 'overlayPane' || name === undefined) { + return false; + } + + var renderer = this._paneRenderers[name]; + if (renderer === undefined) { + renderer = (L.SVG && L.svg({pane: name})) || (L.Canvas && L.canvas({pane: name})); + this._paneRenderers[name] = renderer; + } + return renderer; } }); diff --git a/src/layer/vector/SVG.js b/src/layer/vector/SVG.js index cb538b35..04599781 100644 --- a/src/layer/vector/SVG.js +++ b/src/layer/vector/SVG.js @@ -7,9 +7,6 @@ L.SVG = L.Renderer.extend({ _initContainer: function () { this._container = L.SVG.create('svg'); - this._paths = {}; - this._initEvents(); - // makes it possible to click through svg root; we'll reset it back in individual paths this._container.setAttribute('pointer-events', 'none'); }, @@ -54,15 +51,13 @@ L.SVG = L.Renderer.extend({ }, _addPath: function (layer) { - var path = layer._path; - this._container.appendChild(path); - this._paths[L.stamp(path)] = layer; + this._container.appendChild(layer._path); + layer.addInteractiveTarget(layer._path); }, _removePath: function (layer) { - var path = layer._path; - L.DomUtil.remove(path); - delete this._paths[L.stamp(path)]; + L.DomUtil.remove(layer._path); + layer.removeInteractiveTarget(layer._path); }, _updatePath: function (layer) { @@ -122,7 +117,7 @@ L.SVG = L.Renderer.extend({ // drawing a circle with two half-arcs var d = layer._empty() ? 'M0 0' : 'M' + (p.x - r) + ',' + p.y + - arc + (r * 2) + ',0 ' + + arc + (r * 2) + ',0 ' + arc + (-r * 2) + ',0 '; this._setPath(layer, d); @@ -139,19 +134,6 @@ L.SVG = L.Renderer.extend({ _bringToBack: function (layer) { L.DomUtil.toBack(layer._path); - }, - - // TODO remove duplication with L.Map - _initEvents: function () { - L.DomEvent.on(this._container, 'click dblclick mousedown mouseup mouseover mouseout mousemove contextmenu', - this._fireMouseEvent, this); - }, - - _fireMouseEvent: function (e) { - var path = this._paths[L.stamp(e.target || e.srcElement)]; - if (path) { - path._fireMouseEvent(e); - } } }); diff --git a/src/map/Map.js b/src/map/Map.js index 2165c39a..74f44822 100644 --- a/src/map/Map.js +++ b/src/map/Map.js @@ -220,7 +220,7 @@ L.Map = L.Evented.extend({ remove: function () { - this._initEvents('off'); + this._initEvents(true); try { // throws error in IE6-8 @@ -328,8 +328,8 @@ L.Map = L.Evented.extend({ return this._size.clone(); }, - getPixelBounds: function () { - var topLeftPoint = this._getTopLeftPoint(); + getPixelBounds: function (center, zoom) { + var topLeftPoint = this._getTopLeftPoint(center, zoom); return new L.Bounds(topLeftPoint, topLeftPoint.add(this.getSize())); }, @@ -470,6 +470,7 @@ L.Map = L.Evented.extend({ _initPanes: function () { var panes = this._panes = {}; + this._paneRenderers = {}; this._mapPane = this.createPane('mapPane', this._container); @@ -545,16 +546,17 @@ L.Map = L.Evented.extend({ } }, - // map events + // DOM event handling - _initEvents: function (onOff) { + _initEvents: function (remove) { if (!L.DomEvent) { return; } - onOff = onOff || 'on'; + this._targets = {}; - L.DomEvent[onOff](this._container, - 'click dblclick mousedown mouseup mouseenter mouseleave mousemove contextmenu', - this._handleMouseEvent, this); + var onOff = remove ? 'off' : 'on'; + + L.DomEvent[onOff](this._container, 'click dblclick mousedown mouseup ' + + 'mouseover mouseout mousemove contextmenu keypress', this._handleDOMEvent, this); if (this.options.trackResize) { L.DomEvent[onOff](window, 'resize', this._onResize, this); @@ -567,46 +569,53 @@ L.Map = L.Evented.extend({ function () { this.invalidateSize({debounceMoveend: true}); }, this, false, this._container); }, - _handleMouseEvent: function (e) { - if (!this._loaded) { return; } + _handleDOMEvent: function (e) { + if (!this._loaded || L.DomEvent._skipped(e)) { return; } - this._fireMouseEvent(this, e, - e.type === 'mouseenter' ? 'mouseover' : - e.type === 'mouseleave' ? 'mouseout' : e.type); - }, + // find the layer the event is propagating from + var target = this._targets[L.stamp(e.target || e.srcElement)], + type = e.type === 'keypress' && e.keyCode === 13 ? 'click' : e.type; - _fireMouseEvent: function (obj, e, type, propagate, latlng) { - type = type || 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 (L.DomEvent._skipped(e)) { return; } - if (type === 'click') { - var draggableObj = obj.options.draggable === true ? obj : this; - if (!e._simulated && ((draggableObj.dragging && draggableObj.dragging.moved()) || - (this.boxZoom && this.boxZoom.moved()))) { - L.DomEvent.stopPropagation(e); - return; - } - obj.fire('preclick'); + // prevents outline when clicking on keyboard-focusable element + if (type === 'mousedown') { + L.DomUtil.preventOutline(e.target || e.srcElement); } - if (!obj.listens(type, propagate)) { return; } + this._fireDOMEvent(target || this, e, type); + }, + + _fireDOMEvent: function (target, e, type) { + if (!target.listens(type, true) && (type !== 'click' || !target.listens('preclick', true))) { return; } if (type === 'contextmenu') { L.DomEvent.preventDefault(e); } - if (type === 'click' || type === 'dblclick' || type === 'contextmenu') { - L.DomEvent.stopPropagation(e); - } + + // prevents firing click after you just dragged an object + if (e.type === 'click' && !e._simulated && this._draggableMoved(target)) { return; } var data = { - originalEvent: e, - containerPoint: this.mouseEventToContainerPoint(e) + 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); + } + target.fire(type, data, true); + }, - data.layerPoint = this.containerPointToLayerPoint(data.containerPoint); - data.latlng = latlng || this.layerPointToLatLng(data.layerPoint); - - obj.fire(type, data, propagate); + _draggableMoved: function (obj) { + obj = obj.options.draggable ? obj : this; + return (obj.dragging && obj.dragging.moved()) || (this.boxZoom && this.boxZoom.moved()); }, _clearHandlers: function () { @@ -636,8 +645,11 @@ L.Map = L.Evented.extend({ return pos && !pos.equals([0, 0]); }, - _getTopLeftPoint: function () { - return this.getPixelOrigin().subtract(this._getMapPanePos()); + _getTopLeftPoint: function (center, zoom) { + var pixelOrigin = center && zoom !== undefined ? + this._getNewPixelOrigin(center, zoom) : + this.getPixelOrigin(); + return pixelOrigin.subtract(this._getMapPanePos()); }, _getNewPixelOrigin: function (center, zoom) { diff --git a/src/map/anim/Map.FlyTo.js b/src/map/anim/Map.FlyTo.js index d9af494e..81128f1a 100644 --- a/src/map/anim/Map.FlyTo.js +++ b/src/map/anim/Map.FlyTo.js @@ -9,6 +9,7 @@ L.Map.include({ size = this.getSize(), startZoom = this._zoom; + targetCenter = L.latLng(targetCenter); targetZoom = targetZoom === undefined ? startZoom : targetZoom; var w0 = Math.max(size.x, size.y), diff --git a/src/map/anim/Map.PanAnimation.js b/src/map/anim/Map.PanAnimation.js index 9b20aef6..9dddff5f 100644 --- a/src/map/anim/Map.PanAnimation.js +++ b/src/map/anim/Map.PanAnimation.js @@ -96,6 +96,6 @@ L.Map.include({ this.panBy(offset, options); - return true; + return (options && options.animate) !== false; } }); diff --git a/src/map/anim/Map.ZoomAnimation.js b/src/map/anim/Map.ZoomAnimation.js index 085b2663..27e14a26 100644 --- a/src/map/anim/Map.ZoomAnimation.js +++ b/src/map/anim/Map.ZoomAnimation.js @@ -34,7 +34,15 @@ L.Map.include(!zoomAnimated ? {} : { this._panes.mapPane.appendChild(proxy); this.on('zoomanim', function (e) { + var prop = L.DomUtil.TRANSFORM, + transform = proxy.style[prop]; + L.DomUtil.setTransform(proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1)); + + // workaround for case when transform is the same and so transitionend event is not fired + if (transform === proxy.style[prop] && this._animatingZoom) { + this._onZoomTransitionEnd(); + } }, this); this.on('load moveend', function () { @@ -81,7 +89,7 @@ L.Map.include(!zoomAnimated ? {} : { return true; }, - _animateZoom: function (center, zoom, startAnim) { + _animateZoom: function (center, zoom, startAnim, noUpdate) { if (startAnim) { this._animatingZoom = true; @@ -97,7 +105,8 @@ L.Map.include(!zoomAnimated ? {} : { zoom: zoom, scale: this.getZoomScale(zoom), origin: this.latLngToLayerPoint(center), - offset: this._getCenterOffset(center).multiplyBy(-1) + offset: this._getCenterOffset(center).multiplyBy(-1), + noUpdate: noUpdate }); }, diff --git a/src/map/ext/Map.Geolocation.js b/src/map/ext/Map.Geolocation.js index 38235516..05fe552a 100644 --- a/src/map/ext/Map.Geolocation.js +++ b/src/map/ext/Map.Geolocation.js @@ -14,7 +14,7 @@ L.Map.include({ locate: function (/*Object*/ options) { - options = this._locateOptions = L.extend(this._defaultLocateOptions, options); + options = this._locateOptions = L.extend({}, this._defaultLocateOptions, options); if (!navigator.geolocation) { this._handleGeolocationError({ diff --git a/src/map/handler/Map.BoxZoom.js b/src/map/handler/Map.BoxZoom.js index fcca466c..d94478b5 100644 --- a/src/map/handler/Map.BoxZoom.js +++ b/src/map/handler/Map.BoxZoom.js @@ -27,7 +27,7 @@ L.Map.BoxZoom = L.Handler.extend({ }, _onMouseDown: function (e) { - if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; } + if (!e.shiftKey || ((e.which !== 1) && (e.button !== 0))) { return false; } this._moved = false; @@ -83,7 +83,7 @@ L.Map.BoxZoom = L.Handler.extend({ }, _onMouseUp: function (e) { - if ((e.which !== 1) && (e.button !== 1)) { return false; } + if ((e.which !== 1) && (e.button !== 0)) { return; } this._finish(); diff --git a/src/map/handler/Map.Drag.js b/src/map/handler/Map.Drag.js index 1df7d872..62ec3323 100644 --- a/src/map/handler/Map.Drag.js +++ b/src/map/handler/Map.Drag.js @@ -8,7 +8,6 @@ L.Map.mergeOptions({ inertia: !L.Browser.android23, inertiaDeceleration: 3400, // px/s^2 inertiaMaxSpeed: Infinity, // px/s - inertiaThreshold: L.Browser.touch ? 32 : 18, // ms easeLinearity: 0.2, // TODO refactor, move to CRS @@ -64,7 +63,7 @@ L.Map.Drag = L.Handler.extend({ } }, - _onDrag: function () { + _onDrag: function (e) { if (this._map.options.inertia) { var time = this._lastTime = +new Date(), pos = this._lastPos = this._draggable._absPos || this._draggable._newPos; @@ -72,15 +71,15 @@ L.Map.Drag = L.Handler.extend({ this._positions.push(pos); this._times.push(time); - if (time - this._times[0] > 100) { + if (time - this._times[0] > 50) { this._positions.shift(); this._times.shift(); } } this._map - .fire('move') - .fire('drag'); + .fire('move', e) + .fire('drag', e); }, _onViewReset: function () { @@ -108,9 +107,8 @@ L.Map.Drag = L.Handler.extend({ _onDragEnd: function (e) { var map = this._map, options = map.options, - delay = +new Date() - this._lastTime, - noInertia = !options.inertia || delay > options.inertiaThreshold || !this._positions[0]; + noInertia = !options.inertia || this._times.length < 2; map.fire('dragend', e); @@ -120,7 +118,7 @@ L.Map.Drag = L.Handler.extend({ } else { var direction = this._lastPos.subtract(this._positions[0]), - duration = (this._lastTime + delay - this._times[0]) / 1000, + duration = (this._lastTime - this._times[0]) / 1000, ease = options.easeLinearity, speedVector = direction.multiplyBy(ease / duration), diff --git a/src/map/handler/Map.Keyboard.js b/src/map/handler/Map.Keyboard.js index 789a50fb..51fbfdbc 100644 --- a/src/map/handler/Map.Keyboard.js +++ b/src/map/handler/Map.Keyboard.js @@ -35,14 +35,14 @@ L.Map.Keyboard = L.Handler.extend({ } L.DomEvent.on(container, { - focus: this._onFocus, - blur: this._onBlur, - mousedown: this._onMouseDown + focus: this._onFocus, + blur: this._onBlur, + mousedown: this._onMouseDown }, this); this._map.on({ focus: this._addHooks, - blur: this._removeHooks + blur: this._removeHooks }, this); }, @@ -50,14 +50,14 @@ L.Map.Keyboard = L.Handler.extend({ this._removeHooks(); L.DomEvent.off(this._map._container, { - focus: this._onFocus, - blur: this._onBlur, - mousedown: this._onMouseDown + focus: this._onFocus, + blur: this._onBlur, + mousedown: this._onMouseDown }, this); this._map.off({ focus: this._addHooks, - blur: this._removeHooks + blur: this._removeHooks }, this); }, diff --git a/src/map/handler/Map.Tap.js b/src/map/handler/Map.Tap.js index 214696a3..bc0b27e6 100644 --- a/src/map/handler/Map.Tap.js +++ b/src/map/handler/Map.Tap.js @@ -48,7 +48,7 @@ L.Map.Tap = L.Handler.extend({ this._simulateEvent('contextmenu', first); } }, this), 1000); - + this._simulateEvent('mousedown', first); L.DomEvent.on(document, { @@ -73,7 +73,7 @@ L.Map.Tap = L.Handler.extend({ if (el && el.tagName && el.tagName.toLowerCase() === 'a') { L.DomUtil.removeClass(el, 'leaflet-active'); } - + this._simulateEvent('mouseup', first); // simulate click if the touch didn't move too much diff --git a/src/map/handler/Map.TouchZoom.js b/src/map/handler/Map.TouchZoom.js index 49120ddc..e8b3754c 100644 --- a/src/map/handler/Map.TouchZoom.js +++ b/src/map/handler/Map.TouchZoom.js @@ -80,9 +80,12 @@ L.Map.TouchZoom = L.Handler.extend({ } else { this._center = map.layerPointToLatLng(this._getTargetCenter()); } + this._zoom = map.getScaleZoom(this._scale); - map._animateZoom(this._center, this._zoom); + if (this._scale !== 1 || this._delta.x !== 0 || this._delta.y !== 0) { + map._animateZoom(this._center, this._zoom, false, true); + } }, _onTouchEnd: function () { @@ -103,7 +106,7 @@ L.Map.TouchZoom = L.Handler.extend({ zoomDelta = this._zoom - oldZoom, finalZoom = map._limitZoom(zoomDelta > 0 ? Math.ceil(this._zoom) : Math.floor(this._zoom)); - map._animateZoom(this._center, finalZoom, true); + map._animateZoom(this._center, finalZoom, true, true); }, _getTargetCenter: function () {