leaflet-rotate 0.3.0 → 0.4.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.
@@ -7,6 +7,16 @@
7
7
  * @typedef L.Map.TouchGestures
8
8
  */
9
9
 
10
+ function touchToContainerPoint(map, touch) {
11
+ const container = map.getContainer(),
12
+ rect = container.getBoundingClientRect();
13
+
14
+ return new L.Point(
15
+ touch.clientX - rect.left - container.clientLeft,
16
+ touch.clientY - rect.top - container.clientTop,
17
+ );
18
+ }
19
+
10
20
  L.Map.mergeOptions({
11
21
 
12
22
  /**
@@ -22,27 +32,27 @@ L.Map.mergeOptions({
22
32
 
23
33
  L.Map.TouchGestures = L.Handler.extend({
24
34
 
25
- initialize: function(map) {
35
+ initialize(map) {
26
36
  this._map = map;
27
37
  this.rotate = !!this._map.options.touchRotate;
28
38
  this.zoom = !!this._map.options.touchZoom;
29
39
  },
30
40
 
31
- addHooks: function() {
41
+ addHooks() {
32
42
  L.DomEvent.on(this._map._container, 'touchstart', this._onTouchStart, this);
33
43
  },
34
44
 
35
- removeHooks: function() {
45
+ removeHooks() {
36
46
  L.DomEvent.off(this._map._container, 'touchstart', this._onTouchStart, this);
37
47
  },
38
48
 
39
- _onTouchStart: function(e) {
40
- var map = this._map;
49
+ _onTouchStart(e) {
50
+ const map = this._map;
41
51
 
42
52
  if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming || this._rotating) { return; }
43
53
 
44
- var p1 = map.mouseEventToContainerPoint(e.touches[0]),
45
- p2 = map.mouseEventToContainerPoint(e.touches[1]),
54
+ const p1 = touchToContainerPoint(map, e.touches[0]),
55
+ p2 = touchToContainerPoint(map, e.touches[1]),
46
56
  vector = p1.subtract(p2);
47
57
 
48
58
  this._centerPoint = map.getSize()._divideBy(2);
@@ -79,19 +89,19 @@ L.Map.TouchGestures = L.Handler.extend({
79
89
  L.DomEvent.preventDefault(e);
80
90
  },
81
91
 
82
- _onTouchMove: function(e) {
92
+ _onTouchMove(e) {
83
93
  if (!e.touches || e.touches.length !== 2 || !(this._zooming || this._rotating)) { return; }
84
94
 
85
- var map = this._map,
86
- p1 = map.mouseEventToContainerPoint(e.touches[0]),
87
- p2 = map.mouseEventToContainerPoint(e.touches[1]),
95
+ const map = this._map,
96
+ p1 = touchToContainerPoint(map, e.touches[0]),
97
+ p2 = touchToContainerPoint(map, e.touches[1]),
88
98
  vector = p1.subtract(p2),
89
- scale = p1.distanceTo(p2) / this._startDist,
90
- delta;
99
+ scale = p1.distanceTo(p2) / this._startDist;
100
+ let delta;
91
101
 
92
102
  if (this._rotating) {
93
- var theta = Math.atan(vector.x / vector.y);
94
- var bearingDelta = (theta - this._startTheta) * L.DomUtil.RAD_TO_DEG;
103
+ const theta = Math.atan(vector.x / vector.y);
104
+ let bearingDelta = (theta - this._startTheta) * (180 / Math.PI);
95
105
  if (vector.y < 0) { bearingDelta += 180; }
96
106
  if (bearingDelta) {
97
107
  /**
@@ -123,7 +133,7 @@ L.Map.TouchGestures = L.Handler.extend({
123
133
  delta = p1._add(p2)._divideBy(2)._subtract(this._centerPoint);
124
134
  if (scale === 1 && delta.x === 0 && delta.y === 0) { return; }
125
135
 
126
- var alpha = -map.getBearing() * L.DomUtil.DEG_TO_RAD;
136
+ const alpha = -map.getBearing() * (Math.PI / 180);
127
137
 
128
138
  this._center = map.unproject(map.project(this._pinchStartLatLng).subtract(delta.rotate(alpha)));
129
139
  }
@@ -135,10 +145,8 @@ L.Map.TouchGestures = L.Handler.extend({
135
145
  this._moved = true;
136
146
  }
137
147
 
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);
148
+ const moveFn = map._move.bind(map, this._center, this._zoom, { pinch: true, round: false }, undefined);
149
+ moveFn();
142
150
 
143
151
  if (this.zoom) {
144
152
  if (map.options.zoomAnimation) {
@@ -151,7 +159,7 @@ L.Map.TouchGestures = L.Handler.extend({
151
159
  L.DomEvent.preventDefault(e);
152
160
  },
153
161
 
154
- _onTouchEnd: function() {
162
+ _onTouchEnd() {
155
163
  if (!this._moved || !(this._zooming || this._rotating)) {
156
164
  this._zooming = false;
157
165
  return;
@@ -159,8 +167,6 @@ L.Map.TouchGestures = L.Handler.extend({
159
167
 
160
168
  this._zooming = false;
161
169
  this._rotating = false;
162
- L.Util.cancelAnimFrame(this._animRequest);
163
-
164
170
  L.DomEvent
165
171
  .off(document, 'touchmove', this._onTouchMove, this)
166
172
  .off(document, 'touchend touchcancel', this._onTouchEnd, this);
@@ -17,12 +17,12 @@ L.Map.mergeOptions({
17
17
 
18
18
  L.Map.TouchRotate = L.Handler.extend({
19
19
 
20
- addHooks: function() {
20
+ addHooks() {
21
21
  this._map.touchGestures.enable();
22
22
  this._map.touchGestures.rotate = true;
23
23
  },
24
24
 
25
- removeHooks: function() {
25
+ removeHooks() {
26
26
  this._map.touchGestures.rotate = false;
27
27
  },
28
28
 
@@ -34,14 +34,14 @@ L.Map.mergeOptions({
34
34
 
35
35
  L.Map.TouchZoom = L.Handler.extend({
36
36
 
37
- addHooks: function() {
38
- L.DomUtil.addClass(this._map._container, 'leaflet-touch-zoom');
37
+ addHooks() {
38
+ this._map._container.classList.add('leaflet-touch-zoom');
39
39
  this._map.touchGestures.enable();
40
40
  this._map.touchGestures.zoom = true;
41
41
  },
42
42
 
43
- removeHooks: function() {
44
- L.DomUtil.removeClass(this._map._container, 'leaflet-touch-zoom');
43
+ removeHooks() {
44
+ this._map._container.classList.remove('leaflet-touch-zoom');
45
45
  this._map.touchGestures.zoom = false;
46
46
  },
47
47
 
@@ -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
package/test/index.html CHANGED
@@ -6,18 +6,12 @@
6
6
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
7
7
  <meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
8
8
 
9
- <!-- Leaflet JS / CSS -->
10
- <script src="https://unpkg.com/leaflet@1.9/dist/leaflet-src.js"></script>
11
- <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9/dist/leaflet.css">
12
-
13
- <!-- Leaflet-Rotate -->
14
- <script src="../dist/leaflet-rotate-src.js"></script>
9
+ <link id="leaflet-css" rel="stylesheet">
15
10
 
16
11
  <!-- include some miscellaneous -->
17
12
  <script src="../misc/route.js"></script>
18
13
  <script src="../misc/places.js"></script>
19
14
  <script src="../misc/loremIpsum.js"></script>
20
- <script src="../lib/debug.js"></script>
21
15
 
22
16
  <style>
23
17
  #map {
@@ -36,6 +30,12 @@
36
30
 
37
31
  <body>
38
32
 
33
+ <label for="leaflet-version">Leaflet version:</label>
34
+ <select id="leaflet-version">
35
+ <option value="1">1.9.4</option>
36
+ <option value="2">2.0.0-alpha.1</option>
37
+ </select>
38
+
39
39
  <div id="map"></div>
40
40
 
41
41
  <hr>
@@ -61,8 +61,37 @@
61
61
  </p>
62
62
  </div>
63
63
 
64
- <script>
65
- var esri = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
64
+ <script type="module">
65
+ const versionSelect = document.getElementById('leaflet-version');
66
+ const params = new URLSearchParams(location.search);
67
+ versionSelect.value = params.get('leaflet') === '2' ? '2' : '1';
68
+ versionSelect.addEventListener('change', () => {
69
+ location.search = `?leaflet=${versionSelect.value}`;
70
+ });
71
+
72
+ const loadScript = src => new Promise((resolve, reject) => {
73
+ const script = document.createElement('script');
74
+ script.src = src;
75
+ script.onload = resolve;
76
+ script.onerror = () => reject(new Error(`Could not load ${src}`));
77
+ document.head.appendChild(script);
78
+ });
79
+
80
+ const version = versionSelect.value;
81
+ document.getElementById('leaflet-css').href = version === '2'
82
+ ? 'https://unpkg.com/leaflet@2.0.0-alpha.1/dist/leaflet.css'
83
+ : 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.css';
84
+
85
+ if (version === '2') {
86
+ globalThis.L = await import('https://unpkg.com/leaflet@2.0.0-alpha.1/dist/leaflet.js');
87
+ } else {
88
+ await loadScript('https://unpkg.com/leaflet@1.9.4/dist/leaflet-src.js');
89
+ }
90
+ await loadScript('../dist/leaflet-rotate-src.js');
91
+ await loadScript('../lib/debug.js');
92
+ const L = globalThis.L;
93
+
94
+ const esri = new L.TileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
66
95
  id: 'mapbox.streets',
67
96
  maxZoom: 24,
68
97
  maxNativeZoom: 18,
@@ -70,14 +99,14 @@
70
99
  // noWrap: true
71
100
  });
72
101
 
73
- var osm = L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
102
+ const osm = new L.TileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
74
103
  maxZoom: 24,
75
104
  maxNativeZoom: 19,
76
105
  attribution: '&copy; <a href="http://openstreetmap.org/copyright">OpenStreetMap</a> contributors',
77
106
  // noWrap: true
78
107
  });
79
108
 
80
- var map = L.map('map', {
109
+ const map = new L.Map('map', {
81
110
  center: [55, 10],
82
111
  zoom: 2,
83
112
  layers: [esri],
@@ -99,6 +128,9 @@
99
128
  touchRotate: true,
100
129
  // touchZoom: true
101
130
  });
131
+ window.map = map;
132
+ window.esri = esri;
133
+ window.osm = osm;
102
134
 
103
135
  // map.setBearing(90);
104
136
  // map.touchRotate.enable();
@@ -111,17 +143,18 @@
111
143
  // map.rotateControl.setPosition('bottomleft');
112
144
  // map.setMaxBounds([[-90,-180], [90,180]]);
113
145
 
114
- var layers = L.control.layers({
115
- 'Empty': L.tileLayer(''),
146
+ const layers = new L.Control.Layers({
147
+ 'Empty': new L.TileLayer(''),
116
148
  'Streets': osm,
117
149
  'Satellite': esri,
118
150
  }, null, {
119
151
  collapsed: false
120
152
  }).addTo(map);
153
+ window.layers = layers;
121
154
 
122
- var markers = [];
123
- for (var i in places) {
124
- markers.push(L.marker(places[i], {
155
+ const markers = [];
156
+ for (const i in places) {
157
+ markers.push(new L.Marker(places[i], {
125
158
  draggable: true
126
159
  })
127
160
  .bindPopup('<b>' + i + '</b><br>' + loremIpsum)
@@ -133,30 +166,34 @@
133
166
  )
134
167
  .addTo(map));
135
168
  }
169
+ window.markers = markers;
136
170
 
137
- var path = L.polyline(route, {
138
- renderer: L.canvas()
171
+ const path = new L.Polyline(route, {
172
+ renderer: new L.Canvas()
139
173
  }).addTo(map);
174
+ window.path = path;
140
175
 
141
- var circle = L.circle([53, 4], 111111)
176
+ const circle = new L.Circle([53, 4], 111111)
142
177
  .addTo(map);
178
+ window.circle = circle;
143
179
 
144
- var polygon = L.polygon([
180
+ const polygon = new L.Polygon([
145
181
  [48, -3],
146
182
  [50, -4],
147
183
  [52, 4]
148
184
  ]).addTo(map);
185
+ window.polygon = polygon;
149
186
 
150
- const rectangle = L.rectangle([
187
+ const rectangle = new L.Rectangle([
151
188
  [60.81123, 25.71632],
152
189
  [60.81028, 25.73166],
153
190
  [60.7997, 25.72892],
154
191
  [60.80061, 25.71362]
155
192
  ]).addTo(map);
193
+ window.rectangle = rectangle;
156
194
 
157
195
  // Display some debug info
158
196
  L.Rotate.debug(map);
159
-
160
197
  </script>
161
198
 
162
199
  <a href="https://github.com/Raruto/leaflet-rotate" class="view-on-github" style="position: fixed;bottom: 10px;left: calc(50% - 60px);z-index: 9999;"> <img alt="View on Github" src="https://raruto.github.io/img/view-on-github.png" title="View on Github"
@@ -74,7 +74,7 @@ Then('the bearing reaches 90 degrees gradually', function () {
74
74
  When('I fit the same bounds twice', async function () {
75
75
  this.zooms = await this.page.evaluate(() => {
76
76
  const bounds = [[60.81123, 25.71632], [60.81028, 25.73166], [60.7997, 25.72892], [60.80061, 25.71362]];
77
- L.rectangle(bounds).addTo(map);
77
+ new L.Rectangle(bounds).addTo(map);
78
78
  map.setZoom(0, { animate: false });
79
79
  map.fitBounds(bounds, { animate: false });
80
80
  const first = map.getZoom();
@@ -148,8 +148,8 @@ When('I rotate and zoom two fingers over a vector path', async function () {
148
148
  this.gesturePathPositions = await this.page.evaluate(() => {
149
149
  map.options.zoomAnimation = true;
150
150
  map.setView([55, 10], 6, { animate: false });
151
- const location = L.latLng(55.1, 10.1);
152
- const path = L.circleMarker(location, { radius: 8 }).addTo(map);
151
+ const location = new L.LatLng(55.1, 10.1);
152
+ const path = new L.CircleMarker(location, { radius: 8 }).addTo(map);
153
153
  const container = map.getContainer();
154
154
  const center = map.getSize().divideBy(2);
155
155
  const initial = map.getBearing(),
@@ -64,7 +64,7 @@ When('I rotate a map with the default rotate control', async function () {
64
64
  const container = document.createElement('div');
65
65
  container.style.cssText = 'width: 400px; height: 300px';
66
66
  document.body.append(container);
67
- const testMap = L.map(container, {
67
+ const testMap = new L.Map(container, {
68
68
  center: [55, 10],
69
69
  zoom: 6,
70
70
  rotate: true,
@@ -166,7 +166,7 @@ When('I apply compass heading readings', async function () {
166
166
  const container = document.createElement('div');
167
167
  container.style.cssText = 'width: 400px; height: 300px';
168
168
  document.body.append(container);
169
- const absoluteMap = L.map(container, {
169
+ const absoluteMap = new L.Map(container, {
170
170
  center: [55, 10],
171
171
  zoom: 6,
172
172
  rotate: true,
@@ -212,6 +212,50 @@ Then('valid compass readings update bearing and invalid readings are ignored', f
212
212
  assert.ok(Math.abs(absoluteEventBearing - 60) < 1e-6);
213
213
  });
214
214
 
215
+ When('I use shift-wheel rotation with the handler disabled', async function () {
216
+ this.shiftWheelDisabled = await this.page.evaluate(async () => {
217
+ const container = document.createElement('div');
218
+ container.style.cssText = 'width: 400px; height: 300px';
219
+ document.body.append(container);
220
+ const testMap = new L.Map(container, {
221
+ center: [55, 10],
222
+ zoom: 6,
223
+ rotate: true,
224
+ rotateControl: false,
225
+ shiftKeyRotate: false,
226
+ zoomControl: false,
227
+ zoomAnimation: false,
228
+ });
229
+ const initialZoom = testMap.getZoom();
230
+ const initialBearing = testMap.getBearing();
231
+ container.dispatchEvent(new WheelEvent('wheel', {
232
+ bubbles: true,
233
+ cancelable: true,
234
+ deltaY: -60,
235
+ shiftKey: true,
236
+ }));
237
+ await new Promise(resolve => setTimeout(resolve, 150));
238
+ const result = {
239
+ handlerDisabled: !testMap.shiftKeyRotate.enabled(),
240
+ wheelZoomEnabled: testMap.scrollWheelZoom.enabled(),
241
+ zoomChanged: testMap.getZoom() > initialZoom,
242
+ bearingUnchanged: testMap.getBearing() === initialBearing,
243
+ };
244
+ testMap.remove();
245
+ container.remove();
246
+ return result;
247
+ });
248
+ });
249
+
250
+ Then('shift-wheel input zooms without changing the map bearing', function () {
251
+ assert.deepEqual(this.shiftWheelDisabled, {
252
+ handlerDisabled: true,
253
+ wheelZoomEnabled: true,
254
+ zoomChanged: true,
255
+ bearingUnchanged: true,
256
+ });
257
+ });
258
+
215
259
  When('I change the debugger bounds padding from the keyboard', async function () {
216
260
  await this.page.evaluate(() => {
217
261
  map.setView([55, 10], 6, { animate: false });
@@ -244,13 +288,30 @@ When('I inspect debugger geometry after resizing and moving the map', async func
244
288
  map.panBy([20, 15], { animate: false });
245
289
  map.setZoom(6, { animate: false });
246
290
  window.debugMouseEvent = null;
247
- map.once('mousemove', event => {
248
- window.debugMouseEvent = {
249
- latlng: [event.latlng.lng, event.latlng.lat],
250
- layerPoint: [event.layerPoint.x, event.layerPoint.y],
251
- containerPoint: [event.containerPoint.x, event.containerPoint.y],
252
- };
253
- });
291
+ if (L.version.startsWith('2.')) {
292
+ map.getContainer().addEventListener('mousemove', event => {
293
+ const rect = map.getContainer().getBoundingClientRect();
294
+ const containerPoint = new L.Point(
295
+ event.clientX - rect.left - map.getContainer().clientLeft,
296
+ event.clientY - rect.top - map.getContainer().clientTop,
297
+ );
298
+ const latlng = map.containerPointToLatLng(containerPoint);
299
+ const layerPoint = map.containerPointToLayerPoint(containerPoint);
300
+ window.debugMouseEvent = {
301
+ latlng: [latlng.lng, latlng.lat],
302
+ layerPoint: [layerPoint.x, layerPoint.y],
303
+ containerPoint: [containerPoint.x, containerPoint.y],
304
+ };
305
+ }, { once: true });
306
+ } else {
307
+ map.once('mousemove', event => {
308
+ window.debugMouseEvent = {
309
+ latlng: [event.latlng.lng, event.latlng.lat],
310
+ layerPoint: [event.layerPoint.x, event.layerPoint.y],
311
+ containerPoint: [event.containerPoint.x, event.containerPoint.y],
312
+ };
313
+ });
314
+ }
254
315
  });
255
316
 
256
317
  const bounds = await this.page.locator('#map').boundingBox();
@@ -339,9 +400,9 @@ When('I rotate the example markers from the debugger', async function () {
339
400
  When('I select {string} from the example page', async function (place) {
340
401
  await this.page.getByRole('button', { name: place, exact: true }).click();
341
402
  await this.page.waitForFunction(name =>
342
- map.getZoom() === 10 && map.getCenter().distanceTo(L.latLng(places[name])) < 1, place);
403
+ map.getZoom() === 10 && map.getCenter().distanceTo(new L.LatLng(...places[name])) < 1, place);
343
404
  this.destination = await this.page.evaluate(name => ({
344
- distance: map.getCenter().distanceTo(L.latLng(places[name])),
405
+ distance: map.getCenter().distanceTo(new L.LatLng(...places[name])),
345
406
  zoom: map.getZoom(),
346
407
  }), place);
347
408
  });
@@ -60,7 +60,7 @@ When('I drag a constrained marker on a rotated map', async function () {
60
60
  map.setView([23.1799, 113.4103], 15, { animate: false });
61
61
  map.setBearing(30);
62
62
  map.setZoom(16, { animate: false });
63
- window.dragMarker = L.marker([23.1799, 113.4103], { draggable: true }).addTo(map);
63
+ window.dragMarker = new L.Marker([23.1799, 113.4103], { draggable: true }).addTo(map);
64
64
  window.dragMarkerStart = window.dragMarker.getLatLng();
65
65
  window.dragCallbackLatLngs = [];
66
66
  window.dragMarker.on('drag dragend', event => {
@@ -100,8 +100,8 @@ When('I drag a map with interactive layers while rotated', async function () {
100
100
  await this.page.evaluate(() => {
101
101
  map.options.zoomAnimation = false;
102
102
  map.setView([55, 10], 6, { animate: false });
103
- const marker = L.marker([55.1, 10.1]).addTo(map);
104
- const path = L.circleMarker([55.2, 10.2], { radius: 8 }).addTo(map);
103
+ const marker = new L.Marker([55.1, 10.1]).addTo(map);
104
+ const path = new L.CircleMarker([55.2, 10.2], { radius: 8 }).addTo(map);
105
105
  map.setBearing(35);
106
106
  window.interactiveLayers = { marker, path };
107
107
  });
@@ -134,7 +134,7 @@ When('I drag a map with interactive layers while rotated', async function () {
134
134
  pathScreen.y - mapRect.top - pathExpected.y,
135
135
  ];
136
136
  };
137
- const moved = !map.getCenter().equals(L.latLng(55, 10));
137
+ const moved = !map.getCenter().equals(new L.LatLng(55, 10));
138
138
  const positions = [measure()];
139
139
  map.setBearing(0);
140
140
  positions.push(measure());