Skip to content
Merged
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
64 changes: 64 additions & 0 deletions spec/L.Control.Locate.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,70 @@ describe("LocateControl", () => {
control.setView();
assert.strictEqual(map.getZoom(), 15);
});

it("should not break following when flyTo is used with untilPanOrZoom", () => {
const control = new LocateControl({
flyTo: true,
setView: "untilPanOrZoom",
initialZoomLevel: 15
});
map.addControl(control);

// Activate to bind event listeners (zoomstart -> _onZoom)
control._activate();

// Simulate first location found (user just clicked)
control._justClicked = true;
control._event = {
latlng: { lat: 51.5, lng: -0.09 },
accuracy: 100,
bounds: { getSouthWest: () => ({ lat: 51.49, lng: -0.1 }), getNorthEast: () => ({ lat: 51.51, lng: -0.08 }) }
};

control.setView();

// flyTo triggers zoomstart internally, but _ignoreEvent should prevent
// _userZoomed from being set to true
assert.strictEqual(control._userZoomed, false, "_userZoomed should remain false after flyTo");
});

it("should set _ignoreEvent when using keepCurrentZoomLevel path", async () => {
const control = new LocateControl({
flyTo: true,
setView: "untilPanOrZoom",
keepCurrentZoomLevel: true
});
map.addControl(control);
control._activate();

control._event = {
latlng: { lat: 51.5, lng: -0.09 },
accuracy: 100,
bounds: { getSouthWest: () => ({ lat: 51.49, lng: -0.1 }), getNorthEast: () => ({ lat: 51.51, lng: -0.08 }) }
};

// Capture _ignoreEvent state during setView
let ignoreEventDuringCall = null;
const originalFlyTo = map.flyTo;
map.flyTo = function (...args) {
ignoreEventDuringCall = control._ignoreEvent;
return originalFlyTo.apply(this, args);
};

try {
control.setView();

// _ignoreEvent should have been true during the flyTo call
assert.strictEqual(ignoreEventDuringCall, true, "_ignoreEvent should be set during flyTo call");

// Wait for requestAnimationFrame to complete
await new Promise((resolve) => requestAnimationFrame(resolve));
assert.strictEqual(control._ignoreEvent, false, "_ignoreEvent should be reset after requestAnimationFrame");
} finally {
// Restore original method
map.flyTo = originalFlyTo;
}
});
});

describe("Popup Binding", () => {
Expand Down
50 changes: 33 additions & 17 deletions src/L.Control.Locate.js
Original file line number Diff line number Diff line change
Expand Up @@ -618,7 +618,8 @@ const LocateControl = Control.extend({
},

/**
* Zoom (unless we should keep the zoom level) and an to the current view.
* Pan and/or zoom the map to the current location.
* Respects keepCurrentZoomLevel and initialZoomLevel options.
*/
setView() {
this._drawMarker();
Expand All @@ -628,27 +629,42 @@ const LocateControl = Control.extend({
return;
}

const latlng = this._event.latlng;
const { latlng } = this._event;
const fly = this.options.flyTo;
let method, args;

if (this._justClicked && this.options.initialZoomLevel !== false) {
const f = this.options.flyTo ? this._map.flyTo : this._map.setView;
f.bind(this._map)(latlng, this.options.initialZoomLevel);
method = fly ? "flyTo" : "setView";
args = [latlng, this.options.initialZoomLevel];
} else if (this._shouldKeepCurrentZoom()) {
const f = this.options.flyTo ? this._map.flyTo : this._map.panTo;
f.bind(this._map)(latlng);
method = fly ? "flyTo" : "panTo";
args = [latlng];
} else {
const f = this.options.flyTo ? this._map.flyToBounds : this._map.fitBounds;
// Ignore zoom events while setting the viewport as these would stop following
this._ignoreEvent = true;
f.bind(this._map)(this.options.getLocationBounds(this._event), {
padding: this.options.circlePadding,
maxZoom: this.options.initialZoomLevel || this.options.locateOptions.maxZoom
});
requestAnimationFrame(() => {
// Wait until after the next animFrame because the flyTo can be async
this._ignoreEvent = false;
});
method = fly ? "flyToBounds" : "fitBounds";
args = [
this.options.getLocationBounds(this._event),
{
padding: this.options.circlePadding,
maxZoom: this.options.locateOptions.maxZoom
}
];
}

this._setViewIgnoringEvents(method, args);
},

/**
* Execute a map view method while ignoring zoom/pan events to prevent breaking following mode.
* @param {string} method - The map method name to call ('flyTo', 'setView', 'panTo', 'fitBounds', 'flyToBounds')
* @param {Array} args - Arguments to pass to the method
*/
_setViewIgnoringEvents(method, args) {
this._ignoreEvent = true;
this._map[method](...args);
requestAnimationFrame(() => {
// Wait until after the next animFrame because flyTo/flyToBounds can be async
this._ignoreEvent = false;
});
},

/**
Expand Down