@tomickigrzegorz/leaflet-rotate 0.1.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.
- package/LICENSE +21 -0
- package/README.md +180 -0
- package/dist/index.d.ts +29 -0
- package/dist/leaflet-rotate.css +27 -0
- package/dist/leaflet-rotate.esm.js +1467 -0
- package/dist/leaflet-rotate.umd.js +1473 -0
- package/dist/leaflet-rotate.umd.min.js +2 -0
- package/package.json +52 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Grzegorz Tomicki
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# @tomickigrzegorz/leaflet-rotate
|
|
2
|
+
|
|
3
|
+
Leaflet map rotation: bearing, heading-up, touch/drag/keyboard rotate, and a compass control.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
### Bundler (ESM, via git)
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm i github:tomickigrzegorz/leaflet-rotation
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
```js
|
|
14
|
+
import "leaflet";
|
|
15
|
+
import "@tomickigrzegorz/leaflet-rotate";
|
|
16
|
+
import "@tomickigrzegorz/leaflet-rotate/css"; // only if you use the rotate/compass controls
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### Browser `<script>` (UMD)
|
|
20
|
+
|
|
21
|
+
```html
|
|
22
|
+
<link rel="stylesheet" href="leaflet.css" />
|
|
23
|
+
<link rel="stylesheet" href="dist/leaflet-rotate.css" /> <!-- optional, for controls -->
|
|
24
|
+
<script src="leaflet.js"></script>
|
|
25
|
+
<script src="dist/leaflet-rotate.umd.min.js"></script>
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Load it **after** Leaflet. `leaflet` is a peer dependency (>=1.9).
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Map options (`L.map(id, { ... })`)
|
|
33
|
+
|
|
34
|
+
| Option | Type | Default | Description |
|
|
35
|
+
|---|---|---|---|
|
|
36
|
+
| `rotate` | `boolean` | `false` | Enables the entire rotation system. **Required** for all other rotation options. Without it the map behaves as plain Leaflet. |
|
|
37
|
+
| `bearing` | `number` | `0` | Initial map rotation angle in degrees (clockwise). |
|
|
38
|
+
| `dragRotate` | `boolean` | `true` | Rotation with the **right mouse button** (horizontal drag). Active only when `rotate: true`. |
|
|
39
|
+
| `shiftKeyRotate` | `boolean` | `false` | Rotation via **Shift + scroll wheel**. Normal scroll-wheel zoom is suppressed while Shift is held. |
|
|
40
|
+
| `touchRotate` | `boolean` | `false` | Rotation with a **two-finger** gesture (pinch-rotate, Google Maps style). Rotation has a ~30° threshold to avoid colliding with pinch-zoom. |
|
|
41
|
+
| `rotateClockwise` | `boolean` | `true` | Direction of all rotation inputs (two-finger, right-mouse drag, Shift+wheel). `true` = clockwise gesture rotates the map clockwise (MapLibre-like). Set `false` to invert all three. |
|
|
42
|
+
| `rotateControl` | `boolean \| object` | `false` | Arrow control that resets bearing to north (see below). |
|
|
43
|
+
| `rotateCompassControl` | `boolean \| object` | `false` | Compass control that toggles rotation (see below). |
|
|
44
|
+
| `preventPageGestures` | `boolean` | `true` | Blocks native page pinch-zoom on iOS Safari (`preventDefault` on `gesturestart/change/end` events) so a pinch acts on the map rather than zooming the page. Not needed on desktop/Android (where `touch-action` suffices) but harmless. Set `false` to disable. Works independently of `rotate`. |
|
|
45
|
+
|
|
46
|
+
> Note: zoom (via buttons, scroll wheel, or around the cursor) is animated even when the map is rotated. If the map was panned, the offset is "committed" (reprojected without changing the view) just before the animation, keeping the zoom anchor in place and preventing grey tile flashes.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## `rotateCompassControl` — compass (main rotation toggle)
|
|
51
|
+
|
|
52
|
+
Value:
|
|
53
|
+
- `false` — control is not added (button invisible).
|
|
54
|
+
- `true` — control with default settings.
|
|
55
|
+
- `object` — control with custom settings:
|
|
56
|
+
|
|
57
|
+
```js
|
|
58
|
+
rotateCompassControl: { enabled: false, position: "bottomright" }
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
| Field | Type | Default | Description |
|
|
62
|
+
|---|---|---|---|
|
|
63
|
+
| `enabled` | `boolean` | `false` | Initial rotation state. `true` = rotation active immediately, button coloured. `false` = rotation off, enabled **only after clicking** the compass. |
|
|
64
|
+
| `position` | `string` | `"bottomright"` | Position: `"topleft"`, `"topright"`, `"bottomleft"`, `"bottomright"`. |
|
|
65
|
+
|
|
66
|
+
Behaviour:
|
|
67
|
+
- Click toggles rotation **on/off**. Disabling also resets `bearing` to 0 and disables all rotation gestures (`dragRotate`, `shiftKeyRotate`, `touchGestures`).
|
|
68
|
+
- The icon rotates with the current `bearing`.
|
|
69
|
+
- When visible but **inactive** → icon is greyscale. Active → coloured.
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## `rotateControl` — arrow that resets to north
|
|
74
|
+
|
|
75
|
+
```js
|
|
76
|
+
rotateControl: { position: "topleft", closeOnZeroBearing: true }
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
| Field | Type | Default | Description |
|
|
80
|
+
|---|---|---|---|
|
|
81
|
+
| `position` | `string` | `"topleft"` | Control position. |
|
|
82
|
+
| `closeOnZeroBearing` | `boolean` | `true` | Hides the control when `bearing === 0`. |
|
|
83
|
+
|
|
84
|
+
Click sets `bearing` to 0 (north).
|
|
85
|
+
|
|
86
|
+
### Adding controls programmatically
|
|
87
|
+
|
|
88
|
+
Both controls can also be created via factory functions (e.g. to add them after map init):
|
|
89
|
+
|
|
90
|
+
```js
|
|
91
|
+
L.control.rotate({ position: "topleft" }).addTo(map);
|
|
92
|
+
L.control.rotateCompass({ enabled: true, position: "bottomright" }).addTo(map);
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Marker options (`L.marker(latlng, { ... })`)
|
|
98
|
+
|
|
99
|
+
| Option | Type | Default | Description |
|
|
100
|
+
|---|---|---|---|
|
|
101
|
+
| `rotation` | `number` | `0` | Marker icon rotation in degrees (independent of the map). |
|
|
102
|
+
| `rotateWithView` | `boolean` | `false` | Marker rotates with the map (adds the map's `bearing` to `rotation`). |
|
|
103
|
+
| `scale` | `number` | `undefined` | Marker icon scale. |
|
|
104
|
+
|
|
105
|
+
After changing `marker.options.rotation` call `marker.update()`.
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## Map API
|
|
110
|
+
|
|
111
|
+
### Methods
|
|
112
|
+
|
|
113
|
+
| Method | Description |
|
|
114
|
+
|---|---|
|
|
115
|
+
| `map.setBearing(degrees)` | Sets the map rotation angle (0–360, normalised). |
|
|
116
|
+
| `map.getBearing()` | Returns the current rotation angle in degrees. |
|
|
117
|
+
| `map.setHeading(deg, options?)` | Heading-up mode: rotates the map so `deg` (0=N, clockwise) points to the top (`bearing = -deg`), smoothed via a rAF easing loop. `options.ease` (default `0.2`), `options.deadzone` (default `0.5`°). Pass `deg = null` to stop. |
|
|
118
|
+
| `map.stopHeadingUp()` | Disables heading-up mode (does not reset `bearing`). |
|
|
119
|
+
| `map.getHeadingUp()` | Returns whether heading-up mode is active. |
|
|
120
|
+
|
|
121
|
+
### Events
|
|
122
|
+
|
|
123
|
+
| Event | Description |
|
|
124
|
+
|---|---|
|
|
125
|
+
| `"rotatestart"` | Fired once when a rotation gesture begins (drag / shift-wheel / two-finger). |
|
|
126
|
+
| `"rotate"` | Fired on every `bearing` change (gestures, `setBearing`, heading-up easing). |
|
|
127
|
+
|
|
128
|
+
```js
|
|
129
|
+
map.setBearing(45); // rotate the map to 45°
|
|
130
|
+
map.getBearing(); // -> 45
|
|
131
|
+
|
|
132
|
+
map.setHeading(compass); // heading-up from your own source (0=N, clockwise)
|
|
133
|
+
map.setHeading(null); // stop heading-up easing
|
|
134
|
+
map.stopHeadingUp(); // disable heading-up (keeps current bearing)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Rotation controls / interactions
|
|
140
|
+
|
|
141
|
+
| Gesture | Condition (option) |
|
|
142
|
+
|---|---|
|
|
143
|
+
| Right mouse button + drag | `dragRotate: true` |
|
|
144
|
+
| Shift + scroll wheel | `shiftKeyRotate: true` |
|
|
145
|
+
| Two fingers (rotate) | `touchRotate: true` |
|
|
146
|
+
| Compass click | `rotateCompassControl` (toggles rotation on/off) |
|
|
147
|
+
|
|
148
|
+
All require `rotate: true`. When rotation is disabled via the compass, no gesture works until the compass is clicked again.
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## Theming the controls
|
|
153
|
+
|
|
154
|
+
Control styling lives in `@tomickigrzegorz/leaflet-rotate/css` and is driven by CSS variables — override them without `!important`:
|
|
155
|
+
|
|
156
|
+
```css
|
|
157
|
+
:root {
|
|
158
|
+
--lrc-control-bg: #1e1e1e;
|
|
159
|
+
--lrc-control-hover: #333;
|
|
160
|
+
--lrc-control-size: 34px;
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## Example
|
|
167
|
+
|
|
168
|
+
```js
|
|
169
|
+
const map = L.map("map", {
|
|
170
|
+
center: [52.23, 21.01],
|
|
171
|
+
zoom: 14,
|
|
172
|
+
rotate: true,
|
|
173
|
+
touchRotate: true,
|
|
174
|
+
shiftKeyRotate: true,
|
|
175
|
+
dragRotate: true,
|
|
176
|
+
rotateClockwise: true,
|
|
177
|
+
rotateControl: false,
|
|
178
|
+
rotateCompassControl: { enabled: false, position: "bottomright" },
|
|
179
|
+
});
|
|
180
|
+
```
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import "leaflet";
|
|
2
|
+
|
|
3
|
+
declare module "leaflet" {
|
|
4
|
+
interface MapOptions {
|
|
5
|
+
rotate?: boolean;
|
|
6
|
+
bearing?: number;
|
|
7
|
+
touchRotate?: boolean;
|
|
8
|
+
shiftKeyRotate?: boolean;
|
|
9
|
+
dragRotate?: boolean;
|
|
10
|
+
rotateControl?: boolean | { position?: string; closeOnZeroBearing?: boolean };
|
|
11
|
+
rotateClockwise?: boolean;
|
|
12
|
+
rotateCompassControl?: boolean | { enabled?: boolean; position?: string };
|
|
13
|
+
preventPageGestures?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface MarkerOptions {
|
|
17
|
+
rotation?: number;
|
|
18
|
+
rotateWithView?: boolean;
|
|
19
|
+
scale?: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface Map {
|
|
23
|
+
setBearing(theta: number): void;
|
|
24
|
+
getBearing(): number;
|
|
25
|
+
setHeading(deg: number | null, options?: { ease?: number; deadzone?: number }): this;
|
|
26
|
+
stopHeadingUp(): this;
|
|
27
|
+
getHeadingUp(): boolean;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/* @tomickigrzegorz/leaflet-rotate — control theme. Import only if you use the controls. */
|
|
2
|
+
:root {
|
|
3
|
+
--lrc-control-bg: #fff;
|
|
4
|
+
--lrc-control-hover: #f4f4f4;
|
|
5
|
+
--lrc-control-size: 30px;
|
|
6
|
+
}
|
|
7
|
+
.leaflet-control-rotate { background: var(--lrc-control-bg); }
|
|
8
|
+
.leaflet-control-rotate a {
|
|
9
|
+
display: block;
|
|
10
|
+
width: 26px;
|
|
11
|
+
height: 26px;
|
|
12
|
+
line-height: 26px;
|
|
13
|
+
text-align: center;
|
|
14
|
+
cursor: pointer;
|
|
15
|
+
}
|
|
16
|
+
.leaflet-control-rotate a:hover { background: var(--lrc-control-hover); }
|
|
17
|
+
.leaflet-control-rotate-compass { background: var(--lrc-control-bg); }
|
|
18
|
+
.leaflet-control-rotate-compass a {
|
|
19
|
+
display: flex;
|
|
20
|
+
align-items: center;
|
|
21
|
+
justify-content: center;
|
|
22
|
+
width: var(--lrc-control-size);
|
|
23
|
+
height: var(--lrc-control-size);
|
|
24
|
+
cursor: pointer;
|
|
25
|
+
}
|
|
26
|
+
.leaflet-control-rotate-compass a:hover { background: var(--lrc-control-hover); }
|
|
27
|
+
.leaflet-rotate-compass--inactive a svg { filter: grayscale(1); }
|