@fusion.dev/helix-map 0.115.0 → 0.117.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/dist/connect-map/connect-map.d.ts +9 -0
- package/dist/connect-map/connect-map.js +16 -0
- package/dist/leaflet/leaflet-driver.d.ts +22 -0
- package/dist/leaflet/leaflet-driver.js +48 -0
- package/dist/maplibre/maplibre-driver.d.ts +14 -0
- package/dist/maplibre/maplibre-driver.js +33 -0
- package/package.json +5 -5
|
@@ -42,6 +42,15 @@ export interface MapDriver {
|
|
|
42
42
|
* `:state(ready)`.
|
|
43
43
|
*/
|
|
44
44
|
ready?(done: () => void): () => void;
|
|
45
|
+
/**
|
|
46
|
+
* Which gestures the renderer takes from the page, as the frame's
|
|
47
|
+
* `gestures` decides. `free` is what the renderer did before it was told
|
|
48
|
+
* otherwise. `cooperative` leaves the wheel and one finger to the page and
|
|
49
|
+
* takes the wheel with Ctrl or ⌘, and two fingers. `off` takes none, while a
|
|
50
|
+
* map waiting to be pressed has not been. The frame tells the reader what
|
|
51
|
+
* passed to the page, so hide the renderer's own message where it has one.
|
|
52
|
+
*/
|
|
53
|
+
gestures?(mode: 'cooperative' | 'free' | 'off'): void;
|
|
45
54
|
/**
|
|
46
55
|
* The element the renderer made focusable, which the connector takes out of
|
|
47
56
|
* the tab order while connected so the frame's region is the one stop.
|
|
@@ -792,8 +792,21 @@ export const connectMap = (frame, driver, options = {}) => {
|
|
|
792
792
|
queueMicrotask(flush);
|
|
793
793
|
}
|
|
794
794
|
};
|
|
795
|
+
let gestured = 'free';
|
|
796
|
+
const tellGestures = () => {
|
|
797
|
+
const mode = frame.gestureMode ?? 'free';
|
|
798
|
+
if (mode !== gestured) {
|
|
799
|
+
gestured = mode;
|
|
800
|
+
driver.gestures?.(mode);
|
|
801
|
+
}
|
|
802
|
+
};
|
|
803
|
+
tellGestures();
|
|
795
804
|
const handlePartChange = (event) => {
|
|
796
805
|
const { kind } = event.detail;
|
|
806
|
+
if (kind === 'gestures') {
|
|
807
|
+
tellGestures();
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
797
810
|
if (kind === 'menu') {
|
|
798
811
|
drawPreviews();
|
|
799
812
|
return;
|
|
@@ -905,6 +918,9 @@ export const connectMap = (frame, driver, options = {}) => {
|
|
|
905
918
|
clearTimeout(looking);
|
|
906
919
|
stopWaiting?.();
|
|
907
920
|
frame.drawn?.(false);
|
|
921
|
+
if (gestured !== 'free') {
|
|
922
|
+
driver.gestures?.('free');
|
|
923
|
+
}
|
|
908
924
|
stopTheming?.();
|
|
909
925
|
styled.disconnect();
|
|
910
926
|
children.disconnect();
|
|
@@ -21,6 +21,12 @@ export interface LeafletAttribution {
|
|
|
21
21
|
};
|
|
22
22
|
remove(): unknown;
|
|
23
23
|
}
|
|
24
|
+
/** One of Leaflet's gesture handlers, as far as the driver switches it. */
|
|
25
|
+
export interface LeafletHandler {
|
|
26
|
+
disable(): unknown;
|
|
27
|
+
enable(): unknown;
|
|
28
|
+
enabled(): boolean;
|
|
29
|
+
}
|
|
24
30
|
/**
|
|
25
31
|
* The part of a Leaflet `Map` the driver uses, so this package imports no
|
|
26
32
|
* renderer and a page brings whichever version it has.
|
|
@@ -58,6 +64,22 @@ export interface LeafletMap {
|
|
|
58
64
|
} | undefined;
|
|
59
65
|
/** How far the view is zoomed in. */
|
|
60
66
|
getZoom?(): number;
|
|
67
|
+
/** The wheel's zoom, one of the handlers a frame's `gestures` switches. */
|
|
68
|
+
scrollWheelZoom?: LeafletHandler | undefined;
|
|
69
|
+
/** Dragging the map, by a mouse or one finger. */
|
|
70
|
+
dragging?: LeafletHandler | undefined;
|
|
71
|
+
/** Two fingers zooming the map. */
|
|
72
|
+
touchZoom?: LeafletHandler | undefined;
|
|
73
|
+
/** The place under a pointer, which a zoom with the wheel is made round. */
|
|
74
|
+
mouseEventToLatLng?(event: MouseEvent): {
|
|
75
|
+
lat: number;
|
|
76
|
+
lng: number;
|
|
77
|
+
};
|
|
78
|
+
/** Zooms keeping a place where it is on screen. */
|
|
79
|
+
setZoomAround?(latlng: {
|
|
80
|
+
lat: number;
|
|
81
|
+
lng: number;
|
|
82
|
+
}, zoom: number): unknown;
|
|
61
83
|
zoomIn(delta?: number, options?: {
|
|
62
84
|
animate?: boolean;
|
|
63
85
|
}): unknown;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const animation = (motion) => motion ? { animate: true, duration: motion.duration / 1000 } : { animate: false };
|
|
2
|
+
const gestureState = new WeakMap();
|
|
2
3
|
export const leafletDriver = (map, shapes) => ({
|
|
3
4
|
credits(changed) {
|
|
4
5
|
const control = map.attributionControl;
|
|
@@ -38,6 +39,53 @@ export const leafletDriver = (map, shapes) => ({
|
|
|
38
39
|
});
|
|
39
40
|
},
|
|
40
41
|
focusTarget: () => map.getContainer(),
|
|
42
|
+
gestures: (mode) => {
|
|
43
|
+
const handlers = { drag: map.dragging, scroll: map.scrollWheelZoom, touch: map.touchZoom };
|
|
44
|
+
const state = gestureState.get(map) ?? {
|
|
45
|
+
had: {
|
|
46
|
+
drag: handlers.drag?.enabled() ?? false,
|
|
47
|
+
scroll: handlers.scroll?.enabled() ?? false,
|
|
48
|
+
touch: handlers.touch?.enabled() ?? false,
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
gestureState.set(map, state);
|
|
52
|
+
const finger = typeof matchMedia === 'function' && matchMedia('(pointer: coarse)').matches;
|
|
53
|
+
const want = mode === 'free'
|
|
54
|
+
? state.had
|
|
55
|
+
: mode === 'cooperative'
|
|
56
|
+
? { drag: !finger, scroll: false, touch: true }
|
|
57
|
+
: { drag: false, scroll: false, touch: false };
|
|
58
|
+
for (const name of ['drag', 'scroll', 'touch']) {
|
|
59
|
+
const handler = handlers[name];
|
|
60
|
+
if (handler && handler.enabled() !== want[name]) {
|
|
61
|
+
if (want[name]) {
|
|
62
|
+
handler.enable();
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
handler.disable();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const box = map.getContainer();
|
|
70
|
+
if (state.wheel) {
|
|
71
|
+
box.removeEventListener('wheel', state.wheel);
|
|
72
|
+
state.wheel = undefined;
|
|
73
|
+
}
|
|
74
|
+
if (mode === 'cooperative' && map.mouseEventToLatLng && map.setZoomAround && map.getZoom) {
|
|
75
|
+
state.wheel = (event) => {
|
|
76
|
+
if (!(event.ctrlKey || event.metaKey)) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
event.preventDefault();
|
|
80
|
+
const at = map.mouseEventToLatLng?.(event);
|
|
81
|
+
const zoom = map.getZoom?.();
|
|
82
|
+
if (at && zoom !== undefined) {
|
|
83
|
+
map.setZoomAround?.(at, zoom - Math.sign(event.deltaY) * 0.5);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
box.addEventListener('wheel', state.wheel, { passive: false });
|
|
87
|
+
}
|
|
88
|
+
},
|
|
41
89
|
onView: (moved) => {
|
|
42
90
|
map.on('move zoom resize', moved);
|
|
43
91
|
return () => {
|
|
@@ -105,6 +105,12 @@ interface MapLibreAnimation {
|
|
|
105
105
|
duration?: number;
|
|
106
106
|
easing?: (t: number) => number;
|
|
107
107
|
}
|
|
108
|
+
/** One of MapLibre's gesture handlers, as far as the driver switches it. */
|
|
109
|
+
export interface MapLibreHandler {
|
|
110
|
+
disable(): unknown;
|
|
111
|
+
enable(): unknown;
|
|
112
|
+
isEnabled(): boolean;
|
|
113
|
+
}
|
|
108
114
|
/**
|
|
109
115
|
* The part of a MapLibre `Map` the driver uses, so this package imports no
|
|
110
116
|
* renderer and a page brings whichever version it has.
|
|
@@ -123,6 +129,14 @@ export interface MapLibreMap {
|
|
|
123
129
|
isSourceLoaded?(id: string): boolean;
|
|
124
130
|
/** The canvas, which MapLibre makes focusable. */
|
|
125
131
|
getCanvas(): HTMLElement;
|
|
132
|
+
/** The wheel's zoom, one of the handlers a frame's `gestures` switches. */
|
|
133
|
+
scrollZoom?: MapLibreHandler;
|
|
134
|
+
/** Dragging the map, by a mouse or one finger. */
|
|
135
|
+
dragPan?: MapLibreHandler;
|
|
136
|
+
/** Two fingers zooming and turning the map. */
|
|
137
|
+
touchZoomRotate?: MapLibreHandler;
|
|
138
|
+
/** MapLibre's own cooperative gestures: Ctrl or ⌘ with the wheel, and two fingers. */
|
|
139
|
+
cooperativeGestures?: MapLibreHandler;
|
|
126
140
|
/** The middle of the view, which a map-type picture is drawn around. */
|
|
127
141
|
getCenter?(): {
|
|
128
142
|
lat: number;
|
|
@@ -287,6 +287,7 @@ const answerImages = (holder) => {
|
|
|
287
287
|
return () => holder.off?.('styleimagemissing', missing);
|
|
288
288
|
};
|
|
289
289
|
const PICTURE = { height: 90, width: 120 };
|
|
290
|
+
const originalGestures = new WeakMap();
|
|
290
291
|
export const mapLibreDriver = (map) => ({
|
|
291
292
|
bearing: () => map.getBearing(),
|
|
292
293
|
behind: ({ lat, lng }) => (map.transform ?? map._camera?.transform)?.isLocationOccluded?.({ lat, lng }) === true,
|
|
@@ -323,6 +324,38 @@ export const mapLibreDriver = (map) => ({
|
|
|
323
324
|
], { ...animation(motion), padding });
|
|
324
325
|
},
|
|
325
326
|
focusTarget: () => map.getCanvas(),
|
|
327
|
+
gestures: (mode) => {
|
|
328
|
+
const handlers = {
|
|
329
|
+
cooperative: map.cooperativeGestures,
|
|
330
|
+
drag: map.dragPan,
|
|
331
|
+
scroll: map.scrollZoom,
|
|
332
|
+
touch: map.touchZoomRotate,
|
|
333
|
+
};
|
|
334
|
+
const had = (originalGestures.get(map) ??
|
|
335
|
+
Object.fromEntries(Object.entries(handlers).map(([name, handler]) => [name, handler?.isEnabled() ?? false])));
|
|
336
|
+
originalGestures.set(map, had);
|
|
337
|
+
const want = mode === 'free'
|
|
338
|
+
? had
|
|
339
|
+
: mode === 'cooperative'
|
|
340
|
+
? { cooperative: true, drag: true, scroll: true, touch: true }
|
|
341
|
+
: { cooperative: false, drag: false, scroll: false, touch: false };
|
|
342
|
+
for (const [name, handler] of Object.entries(handlers)) {
|
|
343
|
+
if (handler && handler.isEnabled() !== want[name]) {
|
|
344
|
+
if (want[name]) {
|
|
345
|
+
handler.enable();
|
|
346
|
+
}
|
|
347
|
+
else {
|
|
348
|
+
handler.disable();
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
const screen = map
|
|
353
|
+
.getContainer()
|
|
354
|
+
.querySelector('.maplibregl-cooperative-gesture-screen');
|
|
355
|
+
if (screen) {
|
|
356
|
+
screen.style.display = 'none';
|
|
357
|
+
}
|
|
358
|
+
},
|
|
326
359
|
labels: (shown) => {
|
|
327
360
|
const heard = bared.has(map);
|
|
328
361
|
bared.set(map, !shown);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fusion.dev/helix-map",
|
|
3
3
|
"description": "Connects the Helix Map frame to a map renderer: MapLibre, Leaflet, or a driver of your own.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.117.0",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Ross Sheppard <ross.sheppard@fusion.dev>",
|
|
7
7
|
"repository": {
|
|
@@ -43,14 +43,14 @@
|
|
|
43
43
|
},
|
|
44
44
|
"bugs": "https://github.com/project-fusion/fusion/issues",
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@fusion.dev/helix-core": "0.
|
|
47
|
-
"@fusion.dev/helix-icons": "0.
|
|
46
|
+
"@fusion.dev/helix-core": "0.117.0",
|
|
47
|
+
"@fusion.dev/helix-icons": "0.117.0",
|
|
48
48
|
"@mapbox/vector-tile": "^3.0.0",
|
|
49
49
|
"pbf": "^5.1.2"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
|
-
"@fusion.dev/helix-headless": "0.
|
|
53
|
-
"@fusion.dev/helix-testing": "0.
|
|
52
|
+
"@fusion.dev/helix-headless": "0.117.0",
|
|
53
|
+
"@fusion.dev/helix-testing": "0.117.0",
|
|
54
54
|
"@maplibre/maplibre-gl-style-spec": "^26.4.4",
|
|
55
55
|
"@types/bun": "^1.3.0",
|
|
56
56
|
"@types/leaflet": "^1.9.20",
|