milestones
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
import * as L from 'leaflet';
|
||||
|
||||
/**
|
||||
* Options interface extending standard Circle options
|
||||
* to include custom callback hooks.
|
||||
*/
|
||||
interface ActionCircleOptions extends L.CircleOptions {
|
||||
onAccept: (instance: ActionCircle) => void;
|
||||
onClear: (instance: ActionCircle) => void;
|
||||
}
|
||||
|
||||
class ActionCircle extends L.Circle {
|
||||
declare options: ActionCircleOptions;
|
||||
private _btnContainer: HTMLDivElement | null = null;
|
||||
|
||||
constructor(latlng: L.LatLngExpression, options: ActionCircleOptions) {
|
||||
super(latlng, options);
|
||||
}
|
||||
|
||||
// Called when the circle is added to the map
|
||||
|
||||
override onAdd(map: L.Map): this {
|
||||
super.onAdd(map);
|
||||
this._initUI(map);
|
||||
// map.on('zoom, zoomend, move, moveend', this._repositionUI, this);
|
||||
|
||||
// Keep buttons synced with circle position during pan/zoom
|
||||
map.on('zoom, zoomend', this._repositionUI, this);
|
||||
map.on('move, moveend', this._repositionUI, this);
|
||||
map.on('viewreset', this._repositionUI, this);
|
||||
return this;
|
||||
}
|
||||
|
||||
override setLatLng(latLng: L.LatLngExpression): this {
|
||||
super.setLatLng(latLng);
|
||||
this._repositionUI();
|
||||
return this;
|
||||
}
|
||||
|
||||
// Called when circle.remove() or map.removeLayer(circle) is executed
|
||||
override onRemove(map: L.Map): this {
|
||||
if (this._btnContainer) {
|
||||
L.DomUtil.remove(this._btnContainer);
|
||||
this._btnContainer = null;
|
||||
}
|
||||
map.off('zoom, zoomend', this._repositionUI, this);
|
||||
map.off('move, moveend', this._repositionUI, this);
|
||||
map.off('viewreset', this._repositionUI, this);
|
||||
super.onRemove(map);
|
||||
return this;
|
||||
}
|
||||
|
||||
override setRadius(radius: number): this {
|
||||
super.setRadius(radius);
|
||||
this._repositionUI();
|
||||
return this;
|
||||
}
|
||||
|
||||
private _initUI(map: L.Map): void {
|
||||
// Create container in the markerPane (above the circle path)
|
||||
const container = L.DomUtil.create('div', 'leaflet-circle-actions', map.getPane('markerPane'));
|
||||
this._btnContainer = container;
|
||||
|
||||
// Prevent map clicks/drags from triggering when interacting with buttons
|
||||
L.DomEvent.disableClickPropagation(container);
|
||||
|
||||
// Accept Button
|
||||
const acceptBtn = L.DomUtil.create('button', 'circle-btn-accept', container);
|
||||
L.DomUtil.create('i', 'q-icon mdi mdi-check', acceptBtn);
|
||||
|
||||
L.DomEvent.on(acceptBtn, 'click', (e) => {
|
||||
L.DomEvent.stopPropagation(e);
|
||||
if (this.options.onAccept) this.options.onAccept(this);
|
||||
});
|
||||
|
||||
// Clear Button
|
||||
const clearBtn = L.DomUtil.create('button', 'circle-btn-clear', container);
|
||||
L.DomUtil.create('i', 'q-icon mdi mdi-close', clearBtn);
|
||||
L.DomEvent.on(clearBtn, 'click', (e) => {
|
||||
L.DomEvent.stopPropagation(e);
|
||||
if (this.options.onClear) this.options.onClear(this);
|
||||
this.remove(); // Self-destruct
|
||||
});
|
||||
|
||||
this._repositionUI();
|
||||
}
|
||||
|
||||
private readonly _repositionUI = (): void => {
|
||||
if (!this._btnContainer || !this._map) return;
|
||||
|
||||
const bounds = this.getBounds();
|
||||
const southEast = bounds.getSouthEast();
|
||||
const pixelPoint = this._map.latLngToLayerPoint(southEast);
|
||||
|
||||
const radius = this.getRadius();
|
||||
const radiusRat = radius / 1000;
|
||||
const translate = `translate(${radiusRat}, ${radiusRat})`;
|
||||
/*
|
||||
if (radius < 30) {
|
||||
translate = 'translate(25%, 25%)';
|
||||
}
|
||||
if (radius > 200) {
|
||||
translate = 'translate(75%, 75%)';
|
||||
}
|
||||
*/
|
||||
// Convert LatLng center to pixel coordinates
|
||||
// const centerPoint = this._map.latLngToLayerPoint(this.getLatLng());
|
||||
|
||||
// Apply styles to overlay the center
|
||||
Object.assign(this._btnContainer.style, {
|
||||
position: 'absolute',
|
||||
left: `${pixelPoint.x}px`,
|
||||
top: `${pixelPoint.y}px`,
|
||||
zIndex: '1000',
|
||||
display: 'flex',
|
||||
gap: '8px',
|
||||
transform: translate, // Centering trick
|
||||
pointerEvents: 'auto',
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// Factory function for cleaner initialization
|
||||
export const actionCircle = (latlng: L.LatLngExpression, options: ActionCircleOptions) => {
|
||||
return new ActionCircle(latlng, options);
|
||||
};
|
||||
Reference in New Issue
Block a user