diff --git a/build/deps.js b/build/deps.js
index b493e7dd8..29cf31131 100644
--- a/build/deps.js
+++ b/build/deps.js
@@ -200,6 +200,12 @@ var deps = {
desc: 'Makes markers draggable (by mouse or touch).'
},
+ PathDrag: {
+ src: ['layer/vector/Path.Drag.js'],
+ deps: ['Path', 'Draggable'],
+ desc: 'Makes paths draggable (by mouse or touch).'
+ },
+
ControlZoom: {
src: ['control/Control.js',
'control/Control.Zoom.js'],
diff --git a/debug/vector/path-drag.html b/debug/vector/path-drag.html
new file mode 100644
index 000000000..760afab15
--- /dev/null
+++ b/debug/vector/path-drag.html
@@ -0,0 +1,37 @@
+
+
+
+ Leaflet debug page
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/layer/vector/Path.Drag.js b/src/layer/vector/Path.Drag.js
new file mode 100644
index 000000000..abdb16210
--- /dev/null
+++ b/src/layer/vector/Path.Drag.js
@@ -0,0 +1,124 @@
+/* A Draggable that does not update the element position
+and takes care of only bubbling to targetted path in Canvas mode. */
+L.PathDraggable = L.Draggable.extend({
+
+ initialize: function (path) {
+ this._path = path;
+ this._canvas = (path._map.getRenderer(path) instanceof L.Canvas);
+ var element = this._canvas ? this._path._map.getRenderer(this._path)._container : this._path._path;
+ L.Draggable.prototype.initialize.call(this, element, element, true);
+ },
+
+ _updatePosition: function () {
+ var e = {originalEvent: this._lastEvent};
+ this.fire('drag', e);
+ },
+
+ _onDown: function (e) {
+ var first = e.touches ? e.touches[0] : e;
+ this._startPoint = new L.Point(first.clientX, first.clientY);
+ if (this._canvas && !this._path._containsPoint(this._startPoint)) { return; }
+ L.Draggable.prototype._onDown.call(this, e);
+ }
+
+});
+
+
+L.Handler.PathDrag = L.Handler.extend({
+
+ initialize: function (path) {
+ this._path = path;
+ },
+
+ getEvents: function () {
+ return {
+ dragstart: this._onDragStart,
+ drag: this._onDrag,
+ dragend: this._onDragEnd
+ };
+ },
+
+ addHooks: function () {
+ if (!this._draggable) { this._draggable = new L.PathDraggable(this._path); }
+ this._draggable.on(this.getEvents(), this).enable();
+ L.DomUtil.addClass(this._draggable._element, 'leaflet-path-draggable');
+ },
+
+ removeHooks: function () {
+ this._draggable.off(this.getEvents(), this).disable();
+ L.DomUtil.removeClass(this._draggable._element, 'leaflet-path-draggable');
+ },
+
+ moved: function () {
+ return this._draggable && this._draggable._moved;
+ },
+
+ _onDragStart: function () {
+ this._startPoint = this._draggable._startPoint;
+ this._path
+ .closePopup()
+ .fire('movestart')
+ .fire('dragstart');
+ },
+
+ _onDrag: function (e) {
+ var path = this._path,
+ event = (e.originalEvent.touches && e.originalEvent.touches.length === 1 ? e.originalEvent.touches[0] : e.originalEvent),
+ newPoint = L.point(event.clientX, event.clientY),
+ latlng = path._map.layerPointToLatLng(newPoint);
+
+ this._offset = newPoint.subtract(this._startPoint);
+ this._startPoint = newPoint;
+
+ this._path.eachLatLng(this.updateLatLng, this);
+ path.redraw();
+
+ e.latlng = latlng;
+ e.offset = this._offset;
+ path.fire('move', e)
+ .fire('drag', e);
+ },
+
+ _onDragEnd: function (e) {
+ this._path.fire('moveend')
+ .fire('dragend', e);
+ },
+
+ updateLatLng: function (latlng) {
+ var oldPoint = this._path._map.latLngToLayerPoint(latlng);
+ oldPoint._add(this._offset);
+ var newLatLng = this._path._map.layerPointToLatLng(oldPoint);
+ latlng.lat = newLatLng.lat;
+ latlng.lng = newLatLng.lng;
+ }
+
+});
+
+L.Path.include({
+
+ eachLatLng: function (callback, context) {
+ context = context || this;
+ var loop = function (latlngs) {
+ for (var i = 0; i < latlngs.length; i++) {
+ if (L.Util.isArray(latlngs[i])) {
+ loop(latlngs[i]);
+ } else {
+ callback.call(context, latlngs[i]);
+ }
+ }
+ };
+ loop(this.getLatLngs ? this.getLatLngs() : [this.getLatLng()]);
+ }
+
+});
+
+L.Path.addInitHook(function () {
+
+ if (this.options.draggable) {
+ this._dragging = new L.Handler.PathDrag(this);
+ this.once('add', function () {
+ this._dragging.enable();
+ });
+ }
+
+});