leaflet-rotate 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +74 -5
  2. package/build/test.js +18 -0
  3. package/dist/leaflet-rotate-src.js +1081 -303
  4. package/dist/leaflet-rotate-src.js.map +3 -3
  5. package/dist/leaflet-rotate.js +1 -1
  6. package/dist/leaflet-rotate.js.map +3 -3
  7. package/lib/debug.js +84 -66
  8. package/lib/debug.min.js +1 -1
  9. package/misc/leaflet-heat-rotate.js +14 -7
  10. package/misc/loremIpsum.js +1 -1
  11. package/misc/places.js +1 -1
  12. package/misc/route.js +1 -1
  13. package/package.json +5 -5
  14. package/src/control/Rotate.js +98 -29
  15. package/src/dom/DomUtil.js +42 -47
  16. package/src/dom/Draggable.js +6 -1
  17. package/src/geo/LatLngBounds.js +5 -0
  18. package/src/geometry/Bounds.js +5 -0
  19. package/src/geometry/Point.js +33 -10
  20. package/src/index.js +3 -1
  21. package/src/layer/DivOverlay.js +43 -12
  22. package/src/layer/Popup.js +29 -16
  23. package/src/layer/Tooltip.js +21 -5
  24. package/src/layer/marker/Icon.js +20 -8
  25. package/src/layer/marker/Marker.js +103 -24
  26. package/src/layer/tile/GridLayer.js +22 -6
  27. package/src/layer/vector/Renderer.js +102 -11
  28. package/src/map/Map.js +408 -121
  29. package/src/map/handler/CompassBearing.js +33 -7
  30. package/src/map/handler/ContainerMutation.js +20 -3
  31. package/src/map/handler/ShiftKeyRotate.js +156 -16
  32. package/src/map/handler/TouchGestures.js +91 -29
  33. package/src/map/handler/TouchRotate.js +20 -3
  34. package/src/map/handler/TouchZoom.js +25 -5
  35. package/test/features/example.feature +5 -0
  36. package/test/features/rotation.feature +30 -0
  37. package/test/index.html +60 -22
  38. package/test/steps/bearing.steps.js +345 -3
  39. package/test/steps/example.steps.js +72 -11
  40. package/test/steps/gestures.steps.js +4 -4
  41. package/test/steps/overlays.steps.js +32 -32
  42. package/test/support/hooks.js +38 -2
@@ -1,12 +1,20 @@
1
1
  /**
2
- * Rotates the map according to a smartphone's compass.
2
+ * Sets map bearing from device orientation when the browser exposes compass data.
3
3
  *
4
4
  * @typedef L.Map.CompassBearing
5
+ * @property {L.Map.CompassBearing} compassBearing Map handler, enabled to follow device heading.
6
+ *
7
+ * @since leaflet-rotate (v0.1.0)
5
8
  */
6
9
 
7
10
  L.Map.CompassBearing = L.Handler.extend({
8
11
 
9
- initialize: function(map) {
12
+ /**
13
+ * @param {L.Map} map Map whose bearing follows device orientation.
14
+ *
15
+ * @since leaflet-rotate (v0.1.0)
16
+ */
17
+ initialize(map) {
10
18
  this._map = map;
11
19
  /** @see https://caniuse.com/?search=DeviceOrientation */
12
20
  if ('ondeviceorientationabsolute' in window) {
@@ -17,7 +25,12 @@ L.Map.CompassBearing = L.Handler.extend({
17
25
  this._throttled = L.Util.throttle(this._onDeviceOrientation, 100, this);
18
26
  },
19
27
 
20
- addHooks: function() {
28
+ /**
29
+ * Subscribe to the supported device-orientation event, if available.
30
+ *
31
+ * @since leaflet-rotate (v0.1.0)
32
+ */
33
+ addHooks() {
21
34
  if (this._map._rotate && this.__deviceOrientationEvent) {
22
35
  L.DomEvent.on(window, this.__deviceOrientationEvent, this._throttled, this);
23
36
  } else {
@@ -27,13 +40,20 @@ L.Map.CompassBearing = L.Handler.extend({
27
40
  }
28
41
  },
29
42
 
30
- removeHooks: function() {
43
+ /**
44
+ * Unsubscribe from device orientation when the handler is disabled.
45
+ *
46
+ * @since leaflet-rotate (v0.1.0)
47
+ */
48
+ removeHooks() {
31
49
  if (this._map._rotate && this.__deviceOrientationEvent) {
32
50
  L.DomEvent.off(window, this.__deviceOrientationEvent, this._throttled, this);
33
51
  }
34
52
  },
35
53
 
36
54
  /**
55
+ * Apply a device orientation reading to the map bearing.
56
+ *
37
57
  * `DeviceOrientationEvent.absolute` - Indicates whether the device is providing absolute
38
58
  * orientation values (relatives to Magnetic North) or
39
59
  * using some arbitrary frame determined by the device.
@@ -50,10 +70,14 @@ L.Map.CompassBearing = L.Handler.extend({
50
70
  * @see https://developer.mozilla.org/en-US/docs/Web/API/DeviceOrientationEvent/absolute
51
71
  * @see https://developer.mozilla.org/en-US/docs/Web/API/DeviceOrientationEvent/alpha
52
72
  * @see https://developer.mozilla.org/en-US/docs/Web/API/Window/orientation
73
+ *
74
+ * @param {DeviceOrientationEvent} e Orientation reading from the browser.
75
+ *
76
+ * @since leaflet-rotate (v0.1.0)
53
77
  */
54
- _onDeviceOrientation: function(e) {
55
- var angle = e.webkitCompassHeading != null ? e.webkitCompassHeading : e.alpha;
56
- var deviceOrientation = 0;
78
+ _onDeviceOrientation(e) {
79
+ let angle = e.webkitCompassHeading != null ? e.webkitCompassHeading : e.alpha;
80
+ let deviceOrientation = 0;
57
81
 
58
82
  if (!Number.isFinite(angle)) { return; }
59
83
 
@@ -76,5 +100,7 @@ L.Map.CompassBearing = L.Handler.extend({
76
100
  * Add Compass bearing handler to L.Map (disabled unless `window.DeviceOrientationEvent` is set).
77
101
  *
78
102
  * @property {L.Map.CompassBearing} compassBearing
103
+ *
104
+ * @since leaflet-rotate (v0.1.0)
79
105
  */
80
106
  L.Map.addInitHook('addHandler', 'compassBearing', L.Map.CompassBearing);
@@ -1,7 +1,10 @@
1
1
  /**
2
- * Triggers `invalidateResize` when the map's DOM container mutates.
2
+ * Calls `invalidateSize()` when the map container's inline style changes.
3
3
  *
4
4
  * @typedef L.Map.ContainerMutation
5
+ * @property {boolean} [trackContainerMutation=false] Observe inline style changes on the map container.
6
+ *
7
+ * @since leaflet-rotate (v0.1.0)
5
8
  */
6
9
 
7
10
  /**
@@ -19,6 +22,8 @@ L.Map.mergeOptions({
19
22
  *
20
23
  * @type {Boolean}
21
24
  *
25
+ * @since leaflet-rotate (v0.1.0)
26
+ *
22
27
  * @see https://developer.mozilla.org/docs/Web/API/MutationObserver
23
28
  */
24
29
  trackContainerMutation: false
@@ -27,7 +32,12 @@ L.Map.mergeOptions({
27
32
 
28
33
  L.Map.ContainerMutation = L.Handler.extend({
29
34
 
30
- addHooks: function() {
35
+ /**
36
+ * Start observing the map container's style attribute.
37
+ *
38
+ * @since leaflet-rotate (v0.1.0)
39
+ */
40
+ addHooks() {
31
41
  // if (!L.Browser.mutation) { return; }
32
42
  if (!this._observer) {
33
43
  this._observer = new MutationObserver(L.Util.bind(this._map.invalidateSize, this._map));
@@ -41,7 +51,12 @@ L.Map.ContainerMutation = L.Handler.extend({
41
51
  });
42
52
  },
43
53
 
44
- removeHooks: function() {
54
+ /**
55
+ * Stop observing container mutations.
56
+ *
57
+ * @since leaflet-rotate (v0.1.0)
58
+ */
59
+ removeHooks() {
45
60
  // if (!L.Browser.mutation) { return; }
46
61
  this._observer.disconnect();
47
62
  },
@@ -52,5 +67,7 @@ L.Map.ContainerMutation = L.Handler.extend({
52
67
  * Add Container mutation handler to L.Map (disabled unless `trackContainerMutation` is set).
53
68
  *
54
69
  * @property {L.Map.ContainerMutation} trackContainerMutation
70
+ *
71
+ * @since leaflet-rotate (v0.1.0)
55
72
  */
56
73
  L.Map.addInitHook('addHandler', 'trackContainerMutation', L.Map.ContainerMutation);
@@ -1,55 +1,195 @@
1
1
 
2
2
  /**
3
- * Rotates the map on shift key + mousewheel scrolling (desktop).
3
+ * Handles Shift+wheel bearing changes and Ctrl+drag bearing/pitch changes.
4
4
  *
5
5
  * @typedef L.Map.ShiftKeyRotate
6
+ * @property {boolean} [shiftKeyRotate=true] Enable Shift+wheel bearing rotation.
7
+ *
8
+ * @since leaflet-rotate (v0.1.0)
6
9
  */
7
10
 
8
11
  L.Map.mergeOptions({
9
12
 
10
13
  /**
11
- * Whether the map can be rotated with shift + wheel scroll
14
+ * Whether Shift+wheel changes the map bearing.
15
+ *
12
16
  * @type {Boolean}
17
+ *
18
+ * @since leaflet-rotate (v0.1.0)
13
19
  */
14
20
  shiftKeyRotate: true,
15
21
 
22
+ /**
23
+ * Whether Ctrl+drag changes bearing and pitch.
24
+ *
25
+ * @type {Boolean}
26
+ *
27
+ * @since leaflet-rotate (0.5.0)
28
+ */
29
+ pitchDrag: true,
30
+
16
31
  });
17
32
 
18
33
  L.Map.ShiftKeyRotate = L.Handler.extend({
19
34
 
20
- addHooks: function() {
21
- L.DomEvent.on(this._map._container, "wheel", this._handleShiftScroll, this);
22
- // this._map.shiftKeyRotate.enable();
23
- this._map.shiftKeyRotate.rotate = true;
35
+ /**
36
+ * @param {L.Map} map Map controlled by this handler.
37
+ *
38
+ * @since leaflet-rotate (v0.1.0)
39
+ */
40
+ initialize(map) {
41
+ this._map = map;
42
+ this._onMouseDownBound = this._onMouseDown.bind(this);
43
+ this._onMouseMoveBound = this._onMouseMove.bind(this);
44
+ this._onMouseUpBound = this._onMouseUp.bind(this);
45
+ },
46
+
47
+ /**
48
+ * Attach enabled wheel and pointer/mouse gesture listeners.
49
+ *
50
+ * @since leaflet-rotate (v0.1.0)
51
+ */
52
+ addHooks() {
53
+ if (this._map.options.shiftKeyRotate) {
54
+ L.DomEvent.on(this._map._container, "wheel", this._handleShiftScroll, this);
55
+ }
56
+ if (this._map.options.pitchDrag) {
57
+ this.addPitchHooks();
58
+ }
24
59
  },
25
60
 
26
- removeHooks: function() {
61
+ /**
62
+ * Detach gesture listeners and cancel any active drag.
63
+ *
64
+ * @since leaflet-rotate (v0.1.0)
65
+ */
66
+ removeHooks() {
27
67
  L.DomEvent.off(this._map._container, "wheel", this._handleShiftScroll, this);
28
- this._map.shiftKeyRotate.rotate = false;
68
+ this.removePitchHooks();
69
+ },
70
+
71
+ /**
72
+ * Listen for the start of Ctrl+drag pitch gestures.
73
+ *
74
+ * @since leaflet-rotate (0.5.0)
75
+ */
76
+ addPitchHooks() {
77
+ if (this._pitchHooksAdded) { return; }
78
+ this._pitchEvents = L.version.startsWith('2.')
79
+ ? ['pointerdown', 'pointermove', 'pointerup pointercancel']
80
+ : ['mousedown', 'mousemove', 'mouseup'];
81
+ this._map._container.addEventListener(this._pitchEvents[0], this._onMouseDownBound, true);
82
+ this._pitchHooksAdded = true;
83
+ },
84
+
85
+ /**
86
+ * Stop listening for pitch gestures and remove active document listeners.
87
+ *
88
+ * @since leaflet-rotate (0.5.0)
89
+ */
90
+ removePitchHooks() {
91
+ if (!this._pitchHooksAdded) { return; }
92
+ this._map._container.removeEventListener(this._pitchEvents[0], this._onMouseDownBound, true);
93
+ this._onMouseUp();
94
+ delete this._pitchEvents;
95
+ this._pitchHooksAdded = false;
29
96
  },
30
97
 
31
- _handleShiftScroll: function(e) {
98
+ /**
99
+ * Rotate the map in fixed increments while Shift is held during wheel input.
100
+ *
101
+ * @param {WheelEvent} e Wheel event from the map container.
102
+ *
103
+ * @since leaflet-rotate (v0.1.0)
104
+ */
105
+ _handleShiftScroll(e) {
32
106
  if (e.shiftKey) {
33
107
  e.preventDefault();
34
108
  this._map.scrollWheelZoom.disable();
35
- this._map.setBearing((this._map._bearing * L.DomUtil.RAD_TO_DEG) + Math.sign(e.deltaY) * 5);
109
+ this._map.setBearing(this._map.getBearing() + Math.sign(e.deltaY) * 5);
36
110
  } else {
37
111
  this._map.scrollWheelZoom.enable();
38
112
  }
39
113
  },
40
114
 
115
+ /**
116
+ * Start Ctrl+drag and capture subsequent movement outside the map.
117
+ *
118
+ * @param {MouseEvent|PointerEvent} e Gesture start event.
119
+ *
120
+ * @since leaflet-rotate (0.5.0)
121
+ */
122
+ _onMouseDown(e) {
123
+ if (!this._map._rotate || !this._map.options.pitchDrag || !e.ctrlKey || e.button !== 0) { return; }
124
+
125
+ const scrollWheelZoom = this._map.scrollWheelZoom;
126
+ if (scrollWheelZoom && scrollWheelZoom._timer) {
127
+ clearTimeout(scrollWheelZoom._timer);
128
+ scrollWheelZoom._timer = null;
129
+ scrollWheelZoom._delta = 0;
130
+ scrollWheelZoom._startTime = null;
131
+ }
132
+
133
+ e.preventDefault();
134
+ e.stopPropagation();
135
+ e.stopImmediatePropagation();
136
+ this._lastX = e.clientX;
137
+ this._lastY = e.clientY;
138
+ document.addEventListener(this._pitchEvents[1], this._onMouseMoveBound);
139
+ for (const event of this._pitchEvents[2].split(' ')) {
140
+ document.addEventListener(event, this._onMouseUpBound);
141
+ }
142
+ },
143
+
144
+ /**
145
+ * Apply pointer movement to pitch (vertical) and bearing (horizontal).
146
+ *
147
+ * @param {MouseEvent|PointerEvent} e Gesture movement event.
148
+ *
149
+ * @since leaflet-rotate (0.5.0)
150
+ */
151
+ _onMouseMove(e) {
152
+ this._map._setPitchAndBearing(
153
+ this._map.getPitch() + (this._lastY - e.clientY) * 0.5,
154
+ this._map.getBearing() + (e.clientX - this._lastX) * 0.8
155
+ );
156
+ this._lastX = e.clientX;
157
+ this._lastY = e.clientY;
158
+ e.preventDefault();
159
+ },
160
+
161
+ /**
162
+ * End Ctrl+drag and clear document-level listeners.
163
+ *
164
+ * @since leaflet-rotate (0.5.0)
165
+ */
166
+ _onMouseUp() {
167
+ if (!this._pitchEvents) { return; }
168
+ document.removeEventListener(this._pitchEvents[1], this._onMouseMoveBound);
169
+ for (const event of this._pitchEvents[2].split(' ')) {
170
+ document.removeEventListener(event, this._onMouseUpBound);
171
+ }
172
+ delete this._lastX;
173
+ delete this._lastY;
174
+ },
175
+
41
176
  });
42
177
 
43
178
  /**
44
- * Add ShiftKey handler to L.Map (enabled unless `shiftKeyRotate` is unset).
179
+ * Create the handler when either desktop rotation gesture is enabled.
45
180
  *
46
- * @property {L.Map.ShiftKeyRotate} shiftKeyRotate
181
+ * @since leaflet-rotate (v0.1.0)
47
182
  */
48
- L.Map.addInitHook('addHandler', 'shiftKeyRotate', L.Map.ShiftKeyRotate);
49
-
50
- // decrease `scrollWheelZoom` handler priority over `shiftKeyRotate` handler
51
183
  L.Map.addInitHook(function() {
52
- if (this.scrollWheelZoom.enabled() && this.shiftKeyRotate.enabled()) {
184
+ if (this.options.shiftKeyRotate) {
185
+ this.shiftKeyRotate = new L.Map.ShiftKeyRotate(this);
186
+ this.shiftKeyRotate.enable();
187
+ } else if (this.options.pitchDrag) {
188
+ this.shiftKeyRotate = new L.Map.ShiftKeyRotate(this);
189
+ this.shiftKeyRotate.addPitchHooks();
190
+ this.on('unload', this.shiftKeyRotate.removePitchHooks, this.shiftKeyRotate);
191
+ }
192
+ if (this.scrollWheelZoom.enabled() && this.options.shiftKeyRotate) {
53
193
  this.scrollWheelZoom.disable();
54
194
  this.scrollWheelZoom.enable();
55
195
  }
@@ -1,20 +1,45 @@
1
1
  /**
2
- * TouchGestures is both TouchZoom plus TouchRotate
2
+ * Shared implementation for two-finger pinch zoom and map rotation.
3
3
  *
4
4
  * @see https://github.com/fnicollet/Leaflet/commit/a77af51a6b10f308d1b9a16552091d1d0aee8834
5
5
  * @see https://github.com/Leaflet/Leaflet/blob/v1.9.3/src/map/handler/Map.TouchZoom.js
6
6
  *
7
7
  * @typedef L.Map.TouchGestures
8
+ * @property {boolean} [bounceAtZoomLimits] Whether pinch zoom may temporarily exceed zoom limits.
9
+ * @property {boolean} zoom Whether pinch zoom is enabled.
10
+ * @property {boolean} rotate Whether touch rotation is enabled.
11
+ *
12
+ * @since leaflet-rotate (v0.1.0)
8
13
  */
9
14
 
15
+ /**
16
+ * Convert a touch's client coordinates to coordinates relative to the map container.
17
+ *
18
+ * @param {L.Map} map
19
+ * @param {Touch} touch
20
+ *
21
+ * @returns {L.Point}
22
+ *
23
+ * @since leaflet-rotate (v0.1.0)
24
+ */
25
+ function touchToContainerPoint(map, touch) {
26
+ const container = map.getContainer(),
27
+ rect = container.getBoundingClientRect();
28
+
29
+ return new L.Point(
30
+ touch.clientX - rect.left - container.clientLeft,
31
+ touch.clientY - rect.top - container.clientTop,
32
+ );
33
+ }
34
+
10
35
  L.Map.mergeOptions({
11
36
 
12
37
  /**
13
- * Set it to false if you don't want the map to
14
- * zoom beyond min/max zoom and then bounce back
15
- * when pinch-zooming.
38
+ * Whether pinch zoom may temporarily exceed min/max zoom and bounce back.
16
39
  *
17
40
  * @type {Boolean}
41
+ *
42
+ * @since leaflet-rotate (v0.1.0)
18
43
  */
19
44
  bounceAtZoomLimits: true,
20
45
 
@@ -22,27 +47,51 @@ L.Map.mergeOptions({
22
47
 
23
48
  L.Map.TouchGestures = L.Handler.extend({
24
49
 
25
- initialize: function(map) {
50
+ /**
51
+ * Initialize the shared handler from the map's touch zoom/rotation options.
52
+ *
53
+ * @param {L.Map} map Map whose touch gestures are handled.
54
+ *
55
+ * @since leaflet-rotate (v0.1.0)
56
+ */
57
+ initialize(map) {
26
58
  this._map = map;
27
59
  this.rotate = !!this._map.options.touchRotate;
28
60
  this.zoom = !!this._map.options.touchZoom;
29
61
  },
30
62
 
31
- addHooks: function() {
63
+ /**
64
+ * Listen for the start of two-finger gestures on the map container.
65
+ *
66
+ * @since leaflet-rotate (v0.1.0)
67
+ */
68
+ addHooks() {
32
69
  L.DomEvent.on(this._map._container, 'touchstart', this._onTouchStart, this);
33
70
  },
34
71
 
35
- removeHooks: function() {
72
+ /**
73
+ * Remove the touch-start listener.
74
+ *
75
+ * @since leaflet-rotate (v0.1.0)
76
+ */
77
+ removeHooks() {
36
78
  L.DomEvent.off(this._map._container, 'touchstart', this._onTouchStart, this);
37
79
  },
38
80
 
39
- _onTouchStart: function(e) {
40
- var map = this._map;
81
+ /**
82
+ * Record initial touch points and start tracking a two-finger gesture.
83
+ *
84
+ * @param {TouchEvent} e Touch-start event.
85
+ *
86
+ * @since leaflet-rotate (v0.1.0)
87
+ */
88
+ _onTouchStart(e) {
89
+ const map = this._map;
41
90
 
42
91
  if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming || this._rotating) { return; }
43
92
 
44
- var p1 = map.mouseEventToContainerPoint(e.touches[0]),
45
- p2 = map.mouseEventToContainerPoint(e.touches[1]),
93
+ const p1 = touchToContainerPoint(map, e.touches[0]),
94
+ p2 = touchToContainerPoint(map, e.touches[1]),
46
95
  vector = p1.subtract(p2);
47
96
 
48
97
  this._centerPoint = map.getSize()._divideBy(2);
@@ -79,19 +128,26 @@ L.Map.TouchGestures = L.Handler.extend({
79
128
  L.DomEvent.preventDefault(e);
80
129
  },
81
130
 
82
- _onTouchMove: function(e) {
131
+ /**
132
+ * Update bearing and/or zoom while the two touch points move.
133
+ *
134
+ * @param {TouchEvent} e Touch-move event.
135
+ *
136
+ * @since leaflet-rotate (v0.1.0)
137
+ */
138
+ _onTouchMove(e) {
83
139
  if (!e.touches || e.touches.length !== 2 || !(this._zooming || this._rotating)) { return; }
84
140
 
85
- var map = this._map,
86
- p1 = map.mouseEventToContainerPoint(e.touches[0]),
87
- p2 = map.mouseEventToContainerPoint(e.touches[1]),
141
+ const map = this._map,
142
+ p1 = touchToContainerPoint(map, e.touches[0]),
143
+ p2 = touchToContainerPoint(map, e.touches[1]),
88
144
  vector = p1.subtract(p2),
89
- scale = p1.distanceTo(p2) / this._startDist,
90
- delta;
145
+ scale = p1.distanceTo(p2) / this._startDist;
146
+ let delta;
91
147
 
92
148
  if (this._rotating) {
93
- var theta = Math.atan(vector.x / vector.y);
94
- var bearingDelta = (theta - this._startTheta) * L.DomUtil.RAD_TO_DEG;
149
+ const theta = Math.atan(vector.x / vector.y);
150
+ let bearingDelta = (theta - this._startTheta) * (180 / Math.PI);
95
151
  if (vector.y < 0) { bearingDelta += 180; }
96
152
  if (bearingDelta) {
97
153
  /**
@@ -123,9 +179,12 @@ L.Map.TouchGestures = L.Handler.extend({
123
179
  delta = p1._add(p2)._divideBy(2)._subtract(this._centerPoint);
124
180
  if (scale === 1 && delta.x === 0 && delta.y === 0) { return; }
125
181
 
126
- var alpha = -map.getBearing() * L.DomUtil.DEG_TO_RAD;
182
+ const mapDelta = map._pitch
183
+ ? map.containerPointToLayerPoint(this._centerPoint.add(delta))
184
+ .subtract(map.containerPointToLayerPoint(this._centerPoint))
185
+ : delta.rotate(-map.getBearing() * (Math.PI / 180));
127
186
 
128
- this._center = map.unproject(map.project(this._pinchStartLatLng).subtract(delta.rotate(alpha)));
187
+ this._center = map.unproject(map.project(this._pinchStartLatLng).subtract(mapDelta));
129
188
  }
130
189
 
131
190
  }
@@ -135,10 +194,8 @@ L.Map.TouchGestures = L.Handler.extend({
135
194
  this._moved = true;
136
195
  }
137
196
 
138
- L.Util.cancelAnimFrame(this._animRequest);
139
-
140
- var moveFn = map._move.bind(map, this._center, this._zoom, { pinch: true, round: false }, undefined);
141
- this._animRequest = L.Util.requestAnimFrame(moveFn, this, true);
197
+ const moveFn = map._move.bind(map, this._center, this._zoom, { pinch: true, round: false }, undefined);
198
+ moveFn();
142
199
 
143
200
  if (this.zoom) {
144
201
  if (map.options.zoomAnimation) {
@@ -151,7 +208,12 @@ L.Map.TouchGestures = L.Handler.extend({
151
208
  L.DomEvent.preventDefault(e);
152
209
  },
153
210
 
154
- _onTouchEnd: function() {
211
+ /**
212
+ * Finish the gesture, settle the zoom and remove document listeners.
213
+ *
214
+ * @since leaflet-rotate (v0.1.0)
215
+ */
216
+ _onTouchEnd() {
155
217
  if (!this._moved || !(this._zooming || this._rotating)) {
156
218
  this._zooming = false;
157
219
  return;
@@ -159,8 +221,6 @@ L.Map.TouchGestures = L.Handler.extend({
159
221
 
160
222
  this._zooming = false;
161
223
  this._rotating = false;
162
- L.Util.cancelAnimFrame(this._animRequest);
163
-
164
224
  L.DomEvent
165
225
  .off(document, 'touchmove', this._onTouchMove, this)
166
226
  .off(document, 'touchend touchcancel', this._onTouchEnd, this);
@@ -178,8 +238,10 @@ L.Map.TouchGestures = L.Handler.extend({
178
238
  });
179
239
 
180
240
  /**
181
- * Add Touch Gestures handler (enabled unless `touchGestures` is unset).
241
+ * Add the shared touch gesture handler to the map.
182
242
  *
183
243
  * @property {L.Map.TouchGestures} touchGestures
244
+ *
245
+ * @since leaflet-rotate (v0.1.0)
184
246
  */
185
247
  L.Map.addInitHook('addHandler', 'touchGestures', L.Map.TouchGestures);
@@ -1,7 +1,10 @@
1
1
  /**
2
- * Rotates the map on two-finger (touch devices).
2
+ * Enables two-finger rotation through the shared touch-gesture handler.
3
3
  *
4
4
  * @typedef L.Map.TouchRotate
5
+ * @property {boolean} [touchRotate=false] Enable two-finger map rotation.
6
+ *
7
+ * @since leaflet-rotate (v0.1.0)
5
8
  */
6
9
 
7
10
  L.Map.mergeOptions({
@@ -10,6 +13,8 @@ L.Map.mergeOptions({
10
13
  * Whether the map can be rotated with a two-finger rotation gesture
11
14
  *
12
15
  * @type {Boolean}
16
+ *
17
+ * @since leaflet-rotate (v0.1.0)
13
18
  */
14
19
  touchRotate: false,
15
20
 
@@ -17,12 +22,22 @@ L.Map.mergeOptions({
17
22
 
18
23
  L.Map.TouchRotate = L.Handler.extend({
19
24
 
20
- addHooks: function() {
25
+ /**
26
+ * Enable rotation handling in the map's shared touch gesture handler.
27
+ *
28
+ * @since leaflet-rotate (v0.1.0)
29
+ */
30
+ addHooks() {
21
31
  this._map.touchGestures.enable();
22
32
  this._map.touchGestures.rotate = true;
23
33
  },
24
34
 
25
- removeHooks: function() {
35
+ /**
36
+ * Disable rotation handling without affecting pinch zoom.
37
+ *
38
+ * @since leaflet-rotate (v0.1.0)
39
+ */
40
+ removeHooks() {
26
41
  this._map.touchGestures.rotate = false;
27
42
  },
28
43
 
@@ -32,5 +47,7 @@ L.Map.TouchRotate = L.Handler.extend({
32
47
  * Add Touch Rotate handler (disabled unless `touchGestures` is set).
33
48
  *
34
49
  * @property {L.Map.TouchGestures} touchGestures
50
+ *
51
+ * @since leaflet-rotate (v0.1.0)
35
52
  */
36
53
  L.Map.addInitHook('addHandler', 'touchRotate', L.Map.TouchRotate);
@@ -1,9 +1,13 @@
1
1
  /**
2
- * Adds pinch zoom rotation on mobile browsers
2
+ * Adds pinch zoom support through the shared touch-gesture handler.
3
3
  *
4
4
  * @see https://github.com/Leaflet/Leaflet/blob/v1.9.3/src/map/handler/Map.TouchZoom.js
5
5
  *
6
6
  * @external L.Map.TouchZoom
7
+ * @property {boolean|string} [touchZoom=L.Browser.touch] Enable pinch zoom; `'center'` zooms around the view center.
8
+ * @property {boolean} [bounceAtZoomLimits=false] Allow pinch zoom to exceed zoom limits before settling.
9
+ *
10
+ * @since leaflet-rotate (v0.1.0)
7
11
  */
8
12
 
9
13
  L.Map.mergeOptions({
@@ -16,6 +20,8 @@ L.Map.mergeOptions({
16
20
  * for touch-capable web browsers.
17
21
  *
18
22
  * @type {(Boolean|String)}
23
+ *
24
+ * @since leaflet-rotate (v0.1.0)
19
25
  */
20
26
  touchZoom: L.Browser.touch,
21
27
 
@@ -27,6 +33,8 @@ L.Map.mergeOptions({
27
33
  * when pinch-zooming.
28
34
  *
29
35
  * @type {Boolean}
36
+ *
37
+ * @since leaflet-rotate (v0.1.0)
30
38
  */
31
39
  bounceAtZoomLimits: false,
32
40
 
@@ -34,14 +42,24 @@ L.Map.mergeOptions({
34
42
 
35
43
  L.Map.TouchZoom = L.Handler.extend({
36
44
 
37
- addHooks: function() {
38
- L.DomUtil.addClass(this._map._container, 'leaflet-touch-zoom');
45
+ /**
46
+ * Enable pinch zoom handling and mark the shared touch handler as active.
47
+ *
48
+ * @since leaflet-rotate (v0.1.0)
49
+ */
50
+ addHooks() {
51
+ this._map._container.classList.add('leaflet-touch-zoom');
39
52
  this._map.touchGestures.enable();
40
53
  this._map.touchGestures.zoom = true;
41
54
  },
42
55
 
43
- removeHooks: function() {
44
- L.DomUtil.removeClass(this._map._container, 'leaflet-touch-zoom');
56
+ /**
57
+ * Disable pinch zoom handling while preserving touch rotation state.
58
+ *
59
+ * @since leaflet-rotate (v0.1.0)
60
+ */
61
+ removeHooks() {
62
+ this._map._container.classList.remove('leaflet-touch-zoom');
45
63
  this._map.touchGestures.zoom = false;
46
64
  },
47
65
 
@@ -51,5 +69,7 @@ L.Map.TouchZoom = L.Handler.extend({
51
69
  * Add Touch Zoom handler (disabled unless `L.Browser.touch` is set).
52
70
  *
53
71
  * @property {L.Map.TouchGestures} touchGestures
72
+ *
73
+ * @since leaflet-rotate (v0.1.0)
54
74
  */
55
75
  L.Map.addInitHook('addHandler', 'touchZoom', L.Map.TouchZoom);
@@ -27,6 +27,11 @@ Feature: Rotating a Leaflet map
27
27
  When I apply compass heading readings
28
28
  Then valid compass readings update bearing and invalid readings are ignored
29
29
 
30
+ @issue-33 @issue-41
31
+ Scenario: Shift-wheel rotation can be disabled without disabling wheel zoom
32
+ When I use shift-wheel rotation with the handler disabled
33
+ Then shift-wheel input zooms without changing the map bearing
34
+
30
35
  @debugger
31
36
  Scenario: Debugger rotation presets and keyboard slider stay synchronized
32
37
  When I use the debugger rotation controls