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
48 changes: 48 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ This project is a fork of [lit-google-map](https://github.com/arkadiuszwojcik/li
- update dependencies and keep current with Dependabot
- add `zoom_changed`, `center_changed`, and `view_changed` events
- move to [AdvancedMarkerElement](https://developers.google.com/maps/documentation/javascript/advanced-markers/migration)
- add location button control for centering map on user's current location

## Table of contents

Expand All @@ -23,6 +24,8 @@ This project is a fork of [lit-google-map](https://github.com/arkadiuszwojcik/li

[Polygon shape element attributes](#Polygon-shape-element-attributes)

[Location button control](#Location-button-control)

[How to build](#How-to-build)

[License](#License)
Expand Down Expand Up @@ -208,6 +211,51 @@ Example:
</lit-google-map-polygon>
```

## Location button control

The location button control adds a native-looking button to the map that centers the map on the user's current location using the browser's Geolocation API.

**Note:** Geolocation requires HTTPS (or localhost for development).

### Location button attributes

- '_position_' - Control position on map (default: 'RIGHT_BOTTOM')
- Valid values: 'TOP_LEFT', 'TOP_CENTER', 'TOP_RIGHT', 'LEFT_TOP', 'LEFT_CENTER', 'LEFT_BOTTOM', 'RIGHT_TOP', 'RIGHT_CENTER', 'RIGHT_BOTTOM', 'BOTTOM_LEFT', 'BOTTOM_CENTER', 'BOTTOM_RIGHT'
- '_label_' - Accessible label for screen readers (default: 'My Location')
- '_disabled_' - Disable the button (default: false)

### Location button events

- '_location-requested_' - Fired when button is clicked and location request begins
- '_location-found_' - Fired when location is successfully obtained
- Detail: `{lat: number, lng: number}`
- '_location-error_' - Fired when geolocation fails
- Detail: `{code: number, message: string}`

### Example

Basic usage:

```html
<lit-google-map api-key="YOUR_GOOGLE_MAPS_API_KEY">
<lit-google-map-location-button slot="controls">
</lit-google-map-location-button>
</lit-google-map>
```

With custom position:

```html
<lit-google-map api-key="YOUR_GOOGLE_MAPS_API_KEY">
<lit-google-map-location-button
slot="controls"
position="TOP_RIGHT"
label="Find Me"
>
</lit-google-map-location-button>
</lit-google-map>
```

## How to build

Before build install all required packages:
Expand Down
221 changes: 221 additions & 0 deletions dist/lit-google-map.bundle.js
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ var LitGoogleMap = (function (exports) {
});
this.updateMarkers();
this.updateShapes();
this.updateControls();
}
getMapOptions() {
return {
Expand Down Expand Up @@ -257,6 +258,15 @@ var LitGoogleMap = (function (exports) {
s.attachToMap(this.map);
}
}
updateControls() {
const controlsSelector = this.shadowRoot.getElementById("controls-selector");
if (!controlsSelector)
return;
this.controls = controlsSelector.items;
for (const c of this.controls) {
c.changeMap(this.map);
}
}
fitToMarkersChanged(retryAttempt = 0) {
if (this.map && this.fitToMarkers && this.markers.length > 0) {
const latLngBounds = new google.maps.LatLngBounds();
Expand Down Expand Up @@ -316,6 +326,13 @@ var LitGoogleMap = (function (exports) {
>
<slot id="shapes" name="shapes"></slot>
</lit-selector>
<lit-selector
id="controls-selector"
selected-attribute="open"
activate-event="google-map-control-activate"
>
<slot id="controls" name="controls"></slot>
</lit-selector>
<div id="map"></div>
`;
}
Expand Down Expand Up @@ -471,6 +488,210 @@ var LitGoogleMap = (function (exports) {
t("lit-google-map-circle")
], exports.LitGoogleMapCircle);

exports.LitGoogleMapLocationButton = class LitGoogleMapLocationButton extends i {
constructor() {
super(...arguments);
this.position = "RIGHT_BOTTOM";
this.label = "My Location";
this.disabled = false;
this.map = null;
this.controlDiv = null;
this.controlButton = null;
this.isRequesting = false;
}
changeMap(newMap) {
this.map = newMap;
this.mapChanged();
}
mapChanged() {
if (this.controlDiv && this.map) {
this.controlDiv = null;
this.controlButton = null;
}
if (this.map && this.map instanceof google.maps.Map) {
this.mapReady();
}
}
mapReady() {
this.controlDiv = this.createControlButton();
const controlPosition = google.maps.ControlPosition[this.position];
if (controlPosition !== undefined) {
this.map.controls[controlPosition].push(this.controlDiv);
}
}
createControlButton() {
const controlDiv = document.createElement("div");
controlDiv.style.margin = "10px";
const controlButton = document.createElement("button");
controlButton.type = "button";
controlButton.title = this.label;
controlButton.setAttribute("aria-label", this.label);
controlButton.innerHTML = `
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18">
<path fill="currentColor" d="M12 8c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4zm8.94 3A8.994 8.994 0 0 0 13 3.06V1h-2v2.06A8.994 8.994 0 0 0 3.06 11H1v2h2.06A8.994 8.994 0 0 0 11 20.94V23h2v-2.06A8.994 8.994 0 0 0 20.94 13H23v-2h-2.06zM12 19c-3.87 0-7-3.13-7-7s3.13-7 7-7 7 3.13 7 7-3.13 7-7 7z"/>
</svg>
`;
Object.assign(controlButton.style, {
backgroundColor: "#fff",
border: "0",
borderRadius: "2px",
boxShadow: "0 1px 4px rgba(0,0,0,0.3)",
cursor: "pointer",
padding: "10px",
display: "flex",
alignItems: "center",
justifyContent: "center",
width: "40px",
height: "40px",
color: "#666",
});
controlButton.addEventListener("mouseenter", () => {
if (!this.disabled && !this.isRequesting) {
controlButton.style.backgroundColor = "#f8f8f8";
}
});
controlButton.addEventListener("mouseleave", () => {
if (!this.disabled && !this.isRequesting) {
controlButton.style.backgroundColor = "#fff";
}
});
controlButton.addEventListener("click", () => {
if (!this.disabled && !this.isRequesting) {
this.handleLocationRequest();
}
});
this.controlButton = controlButton;
controlDiv.appendChild(controlButton);
return controlDiv;
}
async handleLocationRequest() {
if (!navigator.geolocation) {
this.dispatchEvent(new CustomEvent("location-error", {
detail: {
code: -1,
message: "Geolocation is not supported by your browser",
},
bubbles: true,
composed: true,
}));
return;
}
this.isRequesting = true;
this.setLoadingState(true);
this.dispatchEvent(new CustomEvent("location-requested", {
bubbles: true,
composed: true,
}));
try {
const position = await this.getCurrentPosition();
const lat = position.coords.latitude;
const lng = position.coords.longitude;
this.map.setCenter({ lat, lng });
this.map.setZoom(14);
this.dispatchEvent(new CustomEvent("location-found", {
detail: { lat, lng },
bubbles: true,
composed: true,
}));
}
catch (error) {
let message = "Unable to retrieve your location";
let code = -1;
if (error instanceof GeolocationPositionError) {
code = error.code;
if (error.code === error.PERMISSION_DENIED) {
message =
"Location access denied. Please enable location permissions.";
}
else if (error.code === error.TIMEOUT) {
message = "Location request timed out. Please try again.";
}
else if (error.code === error.POSITION_UNAVAILABLE) {
message = "Location information is unavailable.";
}
}
this.dispatchEvent(new CustomEvent("location-error", {
detail: { code, message },
bubbles: true,
composed: true,
}));
}
finally {
this.isRequesting = false;
this.setLoadingState(false);
}
}
getCurrentPosition() {
return new Promise((resolve, reject) => {
navigator.geolocation.getCurrentPosition(resolve, reject, {
timeout: 10000,
enableHighAccuracy: true,
});
});
}
setLoadingState(loading) {
if (!this.controlButton)
return;
if (loading) {
this.controlButton.style.backgroundColor = "#f0f0f0";
this.controlButton.style.cursor = "wait";
const svg = this.controlButton.querySelector("svg");
if (svg) {
svg.style.animation = "spin 1s linear infinite";
const style = document.createElement("style");
style.textContent = `
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
`;
if (!document.querySelector("style[data-location-button-spin]")) {
style.setAttribute("data-location-button-spin", "true");
document.head.appendChild(style);
}
}
}
else {
this.controlButton.style.backgroundColor = "#fff";
this.controlButton.style.cursor = "pointer";
const svg = this.controlButton.querySelector("svg");
if (svg) {
svg.style.animation = "";
}
}
}
attributeChangedCallback(name, oldval, newval) {
super.attributeChangedCallback(name, oldval, newval);
if (name === "disabled" && this.controlButton) {
if (this.disabled) {
this.controlButton.style.backgroundColor = "#f0f0f0";
this.controlButton.style.cursor = "not-allowed";
this.controlButton.style.opacity = "0.6";
}
else {
this.controlButton.style.backgroundColor = "#fff";
this.controlButton.style.cursor = "pointer";
this.controlButton.style.opacity = "1";
}
}
}
};
__decorate([
n({ type: String, reflect: true }),
__metadata("design:type", Object)
], exports.LitGoogleMapLocationButton.prototype, "position", void 0);
__decorate([
n({ type: String, reflect: true }),
__metadata("design:type", Object)
], exports.LitGoogleMapLocationButton.prototype, "label", void 0);
__decorate([
n({ type: Boolean, reflect: true }),
__metadata("design:type", Object)
], exports.LitGoogleMapLocationButton.prototype, "disabled", void 0);
exports.LitGoogleMapLocationButton = __decorate([
t("lit-google-map-location-button")
], exports.LitGoogleMapLocationButton);

exports.LitGoogleMapMarker = class LitGoogleMapMarker extends i {
constructor() {
super(...arguments);
Expand Down
Loading