Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions src/ui/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@ import type {SourceSpecification} from '../style-spec/types';
export type MapMouseEventType =
| 'mousedown'
| 'mouseup'
| 'mouseupWindow'
| 'preclick'
| 'click'
| 'dblclick'
| 'mousemove'
| 'mousemoveWindow'
| 'mouseover'
| 'mouseenter'
| 'mouseleave'
Expand Down Expand Up @@ -530,6 +532,32 @@ export type MapEvents = {
*/
'mouseup': MapMouseEvent;

/**
* Fired when a pointing device (usually a mouse) is released anywhere on the page during a
* gesture that started inside the map. Unlike `mouseup`, this fires when the release happens
* outside the map's canvas container as well, allowing consumers to terminate gestures cleanly
* when the cursor has left the map.
*
* @event mouseupWindow
* @memberof Map
* @instance
* @type {MapMouseEvent}
*/
'mouseupWindow': MapMouseEvent;

/**
* Fired when a pointing device (usually a mouse) is moved anywhere on the page during a
* gesture that started inside the map. Unlike `mousemove`, this fires while the cursor is
* outside the map's canvas container as well, allowing consumers to track movement during
* gestures that extend outside the map.
*
* @event mousemoveWindow
* @memberof Map
* @instance
* @type {MapMouseEvent}
*/
'mousemoveWindow': MapMouseEvent;

/**
* Fired when a pointing device (usually a mouse) is moved within the map.
* As you move the cursor across a web page containing a map,
Expand Down
8 changes: 8 additions & 0 deletions src/ui/handler/map_event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ export class MapEventHandler implements Handler {
this._map.fire(new MapMouseEvent(e.type as 'mouseup', this._map, e));
}

mouseupWindow(e: MouseEvent) {
this._map.fire(new MapMouseEvent('mouseupWindow', this._map, e));
}

preclick(e: MouseEvent) {
const synth = new MouseEvent('preclick', e);
this._map.fire(new MapMouseEvent(synth.type as 'preclick', this._map, synth));
Expand Down Expand Up @@ -126,6 +130,10 @@ export class BlockableMapEventHandler {
this._map.fire(new MapMouseEvent(e.type as 'mousemove', this._map, e));
}

mousemoveWindow(e: MouseEvent) {
this._map.fire(new MapMouseEvent('mousemoveWindow', this._map, e));
}

mousedown() {
this._delayContextMenu = true;
}
Expand Down
4 changes: 4 additions & 0 deletions src/ui/marker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -286,8 +286,10 @@ export default class Marker extends Evented<MarkerEvents> {
map.off('touchstart', this._addDragHandler);
map.off('mouseup', this._onUp);
map.off('touchend', this._onUp);
map.off('mouseupWindow', this._onUp);
map.off('mousemove', this._onMove);
map.off('touchmove', this._onMove);
map.off('mousemoveWindow', this._onMove);
map.off('remove', this._clearFadeTimer);
map._removeMarker(this);
this._map = undefined;
Expand Down Expand Up @@ -842,8 +844,10 @@ export default class Marker extends Evented<MarkerEvents> {
this._state = 'pending';
map.on('mousemove', this._onMove);
map.on('touchmove', this._onMove);
map.on('mousemoveWindow', this._onMove);
map.once('mouseup', this._onUp);
map.once('touchend', this._onUp);
map.once('mouseupWindow', this._onUp);
}
}

Expand Down
21 changes: 21 additions & 0 deletions test/unit/ui/marker.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -633,6 +633,27 @@ test('Marker with draggable:true fires dragstart, drag, and dragend events at ap
map.remove();
});

test('Marker with draggable:true completes drag when the pointer is released outside the map canvas', () => {
const map = createMap();
const marker = new Marker({draggable: true})
.setLngLat([0, 0])
.addTo(map);
const el = marker.getElement();

const dragend = vi.fn();
marker.on('dragend', dragend);

// eslint-disable-next-line @typescript-eslint/no-unsafe-call
simulate.mousedown(el, {clientX: 0, clientY: 0});
document.dispatchEvent(new MouseEvent('mousemove', {bubbles: true, clientX: 10, clientY: 0}));
document.dispatchEvent(new MouseEvent('mouseup', {bubbles: true, clientX: 10, clientY: 0}));

expect(dragend).toHaveBeenCalledTimes(1);
expect(el.style.pointerEvents).toEqual('auto');

map.remove();
});

test('Marker with draggable:true fires dragstart, drag, and dragend events at appropriate times in response to mouse-triggered drag with marker-specific clickTolerance', () => {
const map = createMap();
const marker = new Marker({draggable: true, clickTolerance: 4})
Expand Down
Loading