diff --git a/package-lock.json b/package-lock.json
index 96efce21..d65d2b56 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -629,6 +629,10 @@
"resolved": "samples/ui-kit-place-search-text",
"link": true
},
+ "node_modules/@js-api-samples/weather-api-compact": {
+ "resolved": "samples/weather-api-compact",
+ "link": true
+ },
"node_modules/@loaders.gl/core": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/@loaders.gl/core/-/core-4.3.3.tgz",
@@ -1113,8 +1117,7 @@
"version": "3.58.1",
"resolved": "https://registry.npmjs.org/@types/google.maps/-/google.maps-3.58.1.tgz",
"integrity": "sha512-X9QTSvGJ0nCfMzYOnaVs/k6/4L+7F5uCS+4iUmkLEls6J9S/Phv+m/i3mDeyc49ZBgwab3EFO1HEoBY7k98EGQ==",
- "dev": true,
- "license": "MIT"
+ "dev": true
},
"node_modules/@types/node": {
"version": "22.15.29",
@@ -2480,6 +2483,9 @@
"samples/ui-kit-place-search-text": {
"name": "@js-api-samples/ui-kit-place-search-text",
"version": "1.0.0"
+ },
+ "samples/weather-api-compact": {
+ "version": "1.0.0"
}
}
}
diff --git a/samples/weather-api-current-compact/README.md b/samples/weather-api-current-compact/README.md
new file mode 100644
index 00000000..be15c0d7
--- /dev/null
+++ b/samples/weather-api-current-compact/README.md
@@ -0,0 +1,32 @@
+# Google Maps JavaScript Sample
+
+This sample is generated from @googlemaps/js-samples located at
+https://github.com/googlemaps-samples/js-api-samples.
+
+## Setup
+
+### Before starting run:
+
+`$npm i`
+
+### Run an example on a local web server
+
+First `cd` to the folder for the sample to run, then:
+
+`$npm start`
+
+### Build an individual example
+
+From `samples/`:
+
+`$npm run build --workspace=sample-name/`
+
+### Build all of the examples.
+
+From `samples/`:
+`$npm run build-all`
+
+## Feedback
+
+For feedback related to this sample, please open a new issue on
+[GitHub](https://github.com/googlemaps-samples/js-api-samples/issues).
diff --git a/samples/weather-api-current-compact/icons/air-pressure.svg b/samples/weather-api-current-compact/icons/air-pressure.svg
new file mode 100644
index 00000000..57ccf401
--- /dev/null
+++ b/samples/weather-api-current-compact/icons/air-pressure.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/samples/weather-api-current-compact/icons/cloud-cover-white.svg b/samples/weather-api-current-compact/icons/cloud-cover-white.svg
new file mode 100644
index 00000000..a79502c8
--- /dev/null
+++ b/samples/weather-api-current-compact/icons/cloud-cover-white.svg
@@ -0,0 +1,4 @@
+
\ No newline at end of file
diff --git a/samples/weather-api-current-compact/icons/cloud-cover.svg b/samples/weather-api-current-compact/icons/cloud-cover.svg
new file mode 100644
index 00000000..e1011733
--- /dev/null
+++ b/samples/weather-api-current-compact/icons/cloud-cover.svg
@@ -0,0 +1,16 @@
+
\ No newline at end of file
diff --git a/samples/weather-api-current-compact/icons/heat-index-white.svg b/samples/weather-api-current-compact/icons/heat-index-white.svg
new file mode 100644
index 00000000..ef2ec549
--- /dev/null
+++ b/samples/weather-api-current-compact/icons/heat-index-white.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/samples/weather-api-current-compact/icons/heat-index.svg b/samples/weather-api-current-compact/icons/heat-index.svg
new file mode 100644
index 00000000..3e1bef98
--- /dev/null
+++ b/samples/weather-api-current-compact/icons/heat-index.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/samples/weather-api-current-compact/icons/ice-thickness.svg b/samples/weather-api-current-compact/icons/ice-thickness.svg
new file mode 100644
index 00000000..083e591e
--- /dev/null
+++ b/samples/weather-api-current-compact/icons/ice-thickness.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/samples/weather-api-current-compact/icons/rain-probability-white.svg b/samples/weather-api-current-compact/icons/rain-probability-white.svg
new file mode 100644
index 00000000..372be77b
--- /dev/null
+++ b/samples/weather-api-current-compact/icons/rain-probability-white.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/samples/weather-api-current-compact/icons/rain-probability.svg b/samples/weather-api-current-compact/icons/rain-probability.svg
new file mode 100644
index 00000000..361b8527
--- /dev/null
+++ b/samples/weather-api-current-compact/icons/rain-probability.svg
@@ -0,0 +1,21 @@
+
\ No newline at end of file
diff --git a/samples/weather-api-current-compact/icons/relative-humidity-white.svg b/samples/weather-api-current-compact/icons/relative-humidity-white.svg
new file mode 100644
index 00000000..be03aba8
--- /dev/null
+++ b/samples/weather-api-current-compact/icons/relative-humidity-white.svg
@@ -0,0 +1,4 @@
+
\ No newline at end of file
diff --git a/samples/weather-api-current-compact/icons/relative-humidity.svg b/samples/weather-api-current-compact/icons/relative-humidity.svg
new file mode 100644
index 00000000..ca228cb1
--- /dev/null
+++ b/samples/weather-api-current-compact/icons/relative-humidity.svg
@@ -0,0 +1,4 @@
+
\ No newline at end of file
diff --git a/samples/weather-api-current-compact/icons/sunrise.svg b/samples/weather-api-current-compact/icons/sunrise.svg
new file mode 100644
index 00000000..922e21d3
--- /dev/null
+++ b/samples/weather-api-current-compact/icons/sunrise.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/samples/weather-api-current-compact/icons/sunset.svg b/samples/weather-api-current-compact/icons/sunset.svg
new file mode 100644
index 00000000..538a5f61
--- /dev/null
+++ b/samples/weather-api-current-compact/icons/sunset.svg
@@ -0,0 +1,4 @@
+
\ No newline at end of file
diff --git a/samples/weather-api-current-compact/icons/thunderstorm-white.svg b/samples/weather-api-current-compact/icons/thunderstorm-white.svg
new file mode 100644
index 00000000..dd5a4a89
--- /dev/null
+++ b/samples/weather-api-current-compact/icons/thunderstorm-white.svg
@@ -0,0 +1,4 @@
+
\ No newline at end of file
diff --git a/samples/weather-api-current-compact/icons/thunderstorm.svg b/samples/weather-api-current-compact/icons/thunderstorm.svg
new file mode 100644
index 00000000..717d9ce1
--- /dev/null
+++ b/samples/weather-api-current-compact/icons/thunderstorm.svg
@@ -0,0 +1,21 @@
+
\ No newline at end of file
diff --git a/samples/weather-api-current-compact/icons/uv-index-white.svg b/samples/weather-api-current-compact/icons/uv-index-white.svg
new file mode 100644
index 00000000..dc12a530
--- /dev/null
+++ b/samples/weather-api-current-compact/icons/uv-index-white.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/samples/weather-api-current-compact/icons/uv-index.svg b/samples/weather-api-current-compact/icons/uv-index.svg
new file mode 100644
index 00000000..ac37fb3f
--- /dev/null
+++ b/samples/weather-api-current-compact/icons/uv-index.svg
@@ -0,0 +1,23 @@
+
\ No newline at end of file
diff --git a/samples/weather-api-current-compact/icons/visibility.svg b/samples/weather-api-current-compact/icons/visibility.svg
new file mode 100644
index 00000000..1b3e7d5e
--- /dev/null
+++ b/samples/weather-api-current-compact/icons/visibility.svg
@@ -0,0 +1,17 @@
+
\ No newline at end of file
diff --git a/samples/weather-api-current-compact/icons/wind-arrow.svg b/samples/weather-api-current-compact/icons/wind-arrow.svg
new file mode 100644
index 00000000..a4fd6d88
--- /dev/null
+++ b/samples/weather-api-current-compact/icons/wind-arrow.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/samples/weather-api-current-compact/icons/wind-chill.svg b/samples/weather-api-current-compact/icons/wind-chill.svg
new file mode 100644
index 00000000..3678c889
--- /dev/null
+++ b/samples/weather-api-current-compact/icons/wind-chill.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/samples/weather-api-current-compact/icons/wind.svg b/samples/weather-api-current-compact/icons/wind.svg
new file mode 100644
index 00000000..3678c889
--- /dev/null
+++ b/samples/weather-api-current-compact/icons/wind.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/samples/weather-api-current-compact/index.html b/samples/weather-api-current-compact/index.html
new file mode 100644
index 00000000..1a6e662f
--- /dev/null
+++ b/samples/weather-api-current-compact/index.html
@@ -0,0 +1,33 @@
+
+
+
+
+
+ Simple Map
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/weather-api-current-compact/index.ts b/samples/weather-api-current-compact/index.ts
new file mode 100644
index 00000000..87badd21
--- /dev/null
+++ b/samples/weather-api-current-compact/index.ts
@@ -0,0 +1,342 @@
+/*
+ * @license
+ * Copyright 2025 Google LLC. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+// [START maps_weather_api_compact]
+import './simple-weather-widget'; // Import the custom element
+
+const CURRENT_CONDITIONS_API_URL = 'https://weather.googleapis.com/v1/currentConditions:lookup'; // Current Conditions API endpoint.
+const API_KEY = "AIzaSyA6myHzS10YXdcazAFalmXvDkrYCp5cLc8"; // Use the hardcoded API key from index.html
+const LIGHT_MAP_ID = 'c306b3c6dd3ed8d9';
+const DARK_MAP_ID = '6b73a9fe7e831a00';
+
+let map: google.maps.Map;
+let activeWeatherWidget: SimpleWeatherWidget | null = null; // To keep track of the currently active widget
+let allMarkers: google.maps.marker.AdvancedMarkerElement[] = []; // To store all active markers
+let markersLoaded = false; // Flag to track if button markers are loaded
+
+async function initMap(): Promise {
+ const { Map } = await google.maps.importLibrary("maps") as google.maps.MapsLibrary;
+ const { AdvancedMarkerElement } = await google.maps.importLibrary("marker") as google.maps.MarkerLibrary;
+
+ map = new Map(document.getElementById("map") as HTMLElement, {
+ center: { lat: 48.8566, lng: 2.3522 }, // Set center to Paris initially, will change based on markers
+ zoom: 6,
+ minZoom: 5, // Set minimum zoom level to 5
+ disableDefaultUI: true, // Disable default UI on basemap click
+ mapId: 'c306b3c6dd3ed8d9', // Use the specified map ID for light mode
+ clickableIcons: false, // Disable clicks on base map POIs
+ });
+
+ // Load a marker at the initial map center
+ const initialCenter = map.getCenter();
+ if (initialCenter) {
+ await createAndAddMarker({ name: 'Initial Location', lat: initialCenter.lat(), lng: initialCenter.lng() }, 'dynamic'); // Create and add dynamic marker at center
+ }
+
+
+ // Add a click listener to the map to handle creating a new marker or hiding the active widget
+ map.addListener('click', async (event: google.maps.MapMouseEvent) => {
+ // Check if the click was on a marker. If so, the marker's own click listener will handle it.
+ // If not, create a new dynamic marker or hide the active widget.
+ let target = event.domEvent.target as HTMLElement;
+ let isClickOnMarker = false;
+ while (target) {
+ if (target.tagName === 'SIMPLE-WEATHER-WIDGET' || target.classList.contains('gm-control-active')) { // Check for widget or default marker control
+ isClickOnMarker = true;
+ break;
+ }
+ target = target.parentElement as HTMLElement;
+ }
+
+ if (!isClickOnMarker && event.latLng) {
+ // If a widget is active, hide its rain details and reset zIndex
+ if (activeWeatherWidget) {
+ const rainDetailsElement = activeWeatherWidget.shadowRoot!.getElementById('rain-details') as HTMLDivElement;
+ rainDetailsElement.style.display = 'none';
+ const activeWidgetContainer = activeWeatherWidget.shadowRoot!.querySelector('.widget-container') as HTMLDivElement;
+ activeWidgetContainer.classList.remove('highlight');
+ // Find the marker associated with the active widget and reset its zIndex
+ const activeMarker = allMarkers.find(marker => marker.content === activeWeatherWidget);
+ if (activeMarker) {
+ activeMarker.zIndex = null;
+ }
+ activeWeatherWidget = null; // Clear the active widget
+ }
+
+ // Remove the previous dynamic marker if it exists
+ const currentDynamicMarkerIndex = allMarkers.findIndex(marker => (marker as any).markerType === 'dynamic');
+ if (currentDynamicMarkerIndex !== -1) {
+ allMarkers[currentDynamicMarkerIndex].map = null;
+ allMarkers.splice(currentDynamicMarkerIndex, 1);
+ }
+
+ // Create a new dynamic marker at the clicked location
+ await createAndAddMarker({ name: 'Clicked Location', lat: event.latLng.lat(), lng: event.latLng.lng() }, 'dynamic'); // Create and add dynamic marker
+ }
+ });
+}
+
+/**
+ * Creates a weather widget and marker and adds them to the map.
+ * @param location The location for the marker.
+ * @param markerType The type of marker ('initial', 'button', 'dynamic').
+ */
+async function createAndAddMarker(location: { name: string; lat: number; lng: number; }, markerType: 'initial' | 'button' | 'dynamic'): Promise {
+ const { AdvancedMarkerElement } = await google.maps.importLibrary("marker") as google.maps.MarkerLibrary;
+
+ const weatherWidget = document.createElement('simple-weather-widget') as SimpleWeatherWidget;
+
+ // Apply dark mode if the map container is in dark mode
+ const mapContainer = document.getElementById("map") as HTMLElement;
+ if (mapContainer.classList.contains('dark-mode')) {
+ weatherWidget.setMode('dark');
+ }
+
+ const marker = new AdvancedMarkerElement({
+ map: map,
+ position: { lat: location.lat, lng: location.lng },
+ content: weatherWidget,
+ title: location.name // Add a title for accessibility
+ });
+
+ // Store the marker type
+ (marker as any).markerType = markerType;
+
+ // Fetch and update weather data for this location
+ updateWeatherDisplayForMarker(marker, weatherWidget, new google.maps.LatLng(location.lat, location.lng));
+
+ // Add click listener to the marker
+ marker.addListener('click', () => {
+ const widgetContainer = weatherWidget.shadowRoot!.querySelector('.widget-container') as HTMLDivElement;
+
+ // If a widget is currently active and it's not the clicked one, remove its highlight class and reset zIndex
+ if (activeWeatherWidget && activeWeatherWidget !== weatherWidget) {
+ const activeWidgetContainer = activeWeatherWidget.shadowRoot!.querySelector('.widget-container') as HTMLDivElement;
+ activeWidgetContainer.classList.remove('highlight');
+ // Find the marker associated with the active widget and reset its zIndex
+ const activeMarker = allMarkers.find(marker => marker.content === activeWeatherWidget);
+ if (activeMarker) {
+ activeMarker.zIndex = null;
+ }
+ }
+
+ // Toggle the highlight class on the clicked widget's container
+ widgetContainer.classList.toggle('highlight');
+
+ // Update the activeWeatherWidget and set zIndex based on the highlight state
+ if (widgetContainer.classList.contains('highlight')) {
+ activeWeatherWidget = weatherWidget;
+ marker.zIndex = 1; // Set zIndex to 1 when highlighted
+ } else {
+ activeWeatherWidget = null;
+ marker.zIndex = null; // Reset zIndex when not highlighted
+ }
+ });
+
+ allMarkers.push(marker); // Add the marker to the allMarkers array
+}
+
+
+/**
+ * Toggles the visual mode of the weather widget and map between light and dark.
+ * Call this function to switch the mode.
+ */
+/**
+ * Toggles the dark mode class on the body element.
+ */
+async function toggleDarkMode() {
+ const mapContainer = document.getElementById("map") as HTMLElement;
+ mapContainer.classList.toggle('dark-mode');
+
+ const modeToggleButton = document.getElementById('mode-toggle');
+ if (modeToggleButton) {
+ if (mapContainer.classList.contains('dark-mode')) {
+ modeToggleButton.textContent = 'Light Mode';
+ } else {
+ modeToggleButton.textContent = 'Dark Mode';
+ }
+ }
+
+ // Remove all markers from the map
+ allMarkers.forEach(marker => {
+ marker.map = null;
+ });
+
+ // Re-initialize the map to apply the new map ID
+ const { Map } = await google.maps.importLibrary("maps") as google.maps.MapsLibrary;
+ const currentCenter = map.getCenter();
+ const currentZoom = map.getZoom();
+ const currentMapId = mapContainer.classList.contains('dark-mode') ? DARK_MAP_ID : LIGHT_MAP_ID;
+
+ map = new Map(mapContainer, {
+ center: currentCenter,
+ zoom: currentZoom,
+ minZoom: 5, // Set minimum zoom level to 5
+ disableDefaultUI: true,
+ mapId: currentMapId,
+ clickableIcons: false,
+ });
+
+ // Re-add all markers to the new map instance and update their widget mode
+ const markersToReAdd = [...allMarkers]; // Create a copy to avoid modifying the array while iterating
+ allMarkers = []; // Clear the array before re-adding
+
+ for (const marker of markersToReAdd) {
+ marker.map = map; // Add marker to the new map
+ const weatherWidget = marker.content as SimpleWeatherWidget;
+ const mapContainer = document.getElementById("map") as HTMLElement; // Re-get map container
+ if (mapContainer.classList.contains('dark-mode')) {
+ weatherWidget.setMode('dark');
+ } else {
+ weatherWidget.setMode('light');
+ }
+ allMarkers.push(marker); // Add back to the allMarkers array
+ }
+
+
+ // Re-add the map click listener
+ map.addListener('click', async (event: google.maps.MapMouseEvent) => {
+ // Check if the click was on a marker. If so, the marker's own click listener will handle it.
+ // If not, create a new dynamic marker or hide the active widget.
+ let target = event.domEvent.target as HTMLElement;
+ let isClickOnMarker = false;
+ while (target) {
+ if (target.tagName === 'SIMPLE-WEATHER-WIDGET' || target.classList.contains('gm-control-active')) { // Check for widget or default marker control
+ isClickOnMarker = true;
+ break;
+ }
+ target = target.parentElement as HTMLElement;
+ }
+
+ if (!isClickOnMarker && event.latLng) {
+ if (activeWeatherWidget) {
+ const rainDetailsElement = activeWeatherWidget.shadowRoot!.getElementById('rain-details') as HTMLDivElement;
+ rainDetailsElement.style.display = 'none';
+ const activeWidgetContainer = activeWeatherWidget.shadowRoot!.querySelector('.widget-container') as HTMLDivElement;
+ activeWidgetContainer.classList.remove('highlight');
+ // Find the marker associated with the active widget and reset its zIndex
+ const activeMarker = allMarkers.find(marker => marker.content === activeWeatherWidget);
+ if (activeMarker) {
+ activeMarker.zIndex = null;
+ }
+ activeWeatherWidget = null; // Clear the active widget
+ }
+
+ // Remove the previous dynamic marker if it exists
+ const currentDynamicMarkerIndex = allMarkers.findIndex(marker => (marker as any).markerType === 'dynamic');
+ if (currentDynamicMarkerIndex !== -1) {
+ allMarkers[currentDynamicMarkerIndex].map = null;
+ allMarkers.splice(currentDynamicMarkerIndex, 1);
+ }
+
+ // Create a new dynamic marker at the clicked location
+ await createAndAddMarker({ name: 'Clicked Location', lat: event.latLng.lat(), lng: event.latLng.lng() }, 'dynamic'); // Create and add dynamic marker
+ }
+ });
+}
+
+const locations = [
+ { name: 'London', lat: 51.5074, lng: -0.1278 },
+ { name: 'Brussels', lat: 50.8503, lng: 4.3517 },
+ { name: 'Luxembourg', lat: 49.8153, lng: 6.1296 },
+ { name: 'Amsterdam', lat: 52.3676, lng: 4.9041 },
+ { name: 'Berlin', lat: 52.5200, lng: 13.4050 },
+ { name: 'Rome', lat: 41.9028, lng: 12.4964 },
+ { name: 'Geneva', lat: 46.2044, lng:6.14324 },
+ { name: 'Barcelona', lat:41.3874, lng: -2.1686},
+ { name: 'Milan', lat:45.4685, lng:9.1824},
+];
+
+async function loadWeatherMarkers(): Promise {
+ const { AdvancedMarkerElement } = await google.maps.importLibrary("marker") as google.maps.MarkerLibrary;
+
+ for (const location of locations) {
+ await createAndAddMarker(location, 'button'); // Create and add button markers
+ }
+}
+
+function removeButtonMarkers(): void {
+ // If a button marker widget is active, hide its rain details and reset zIndex
+ if (activeWeatherWidget) {
+ const buttonMarker = allMarkers.find(marker => marker.content === activeWeatherWidget && (marker as any).markerType === 'button');
+ if (buttonMarker) {
+ const rainDetailsElement = activeWeatherWidget.shadowRoot!.getElementById('rain-details') as HTMLDivElement;
+ rainDetailsElement.style.display = 'none';
+ const activeWidgetContainer = activeWeatherWidget.shadowRoot!.querySelector('.widget-container') as HTMLDivElement;
+ activeWidgetContainer.classList.remove('highlight');
+ buttonMarker.zIndex = null;
+ activeWeatherWidget = null; // Clear the active widget
+ }
+ }
+
+ // Remove button markers from the map and the allMarkers array
+ const markersToRemove = allMarkers.filter(marker => (marker as any).markerType === 'button');
+ markersToRemove.forEach(marker => {
+ marker.map = null;
+ const index = allMarkers.indexOf(marker);
+ if (index > -1) {
+ allMarkers.splice(index, 1);
+ }
+ });
+}
+
+
+async function updateWeatherDisplayForMarker(marker: google.maps.marker.AdvancedMarkerElement, widget: SimpleWeatherWidget, location: google.maps.LatLng): Promise {
+ const lat = location.lat();
+ const lng = location.lng();
+
+ const currentConditionsUrl = `${CURRENT_CONDITIONS_API_URL}?key=${API_KEY}&location.latitude=${lat}&location.longitude=${lng}`;
+
+ try {
+ const response = await fetch(currentConditionsUrl);
+
+ if (!response.ok) {
+ const errorBody = await response.json();
+ console.error('API error response:', errorBody);
+
+ if (response.status === 404 && errorBody?.error?.status === 'NOT_FOUND') {
+ widget.data = { error: "Location not supported" };
+ } else {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+ } else {
+ const weatherData = await response.json();
+ console.log('Weather data fetched for marker:', weatherData);
+ widget.data = weatherData;
+ }
+ } catch (error) {
+ console.error('Error fetching weather data for marker:', error);
+ widget.data = { error: "Failed to fetch weather data" };
+ }
+}
+
+initMap();
+
+// Wait for the custom element to be defined before adding the event listener
+customElements.whenDefined('simple-weather-widget').then(() => {
+ const modeToggleButton = document.getElementById('mode-toggle');
+ if (modeToggleButton) {
+ modeToggleButton.addEventListener('click', () => {
+ toggleDarkMode();
+ });
+ }
+
+ const loadMarkersButton = document.getElementById('load-markers-button');
+ if (loadMarkersButton) {
+ loadMarkersButton.addEventListener('click', () => {
+ if (!markersLoaded) {
+ loadWeatherMarkers();
+ markersLoaded = true;
+ loadMarkersButton.textContent = 'Remove Markers';
+ } else {
+ removeButtonMarkers();
+ markersLoaded = false;
+ loadMarkersButton.textContent = 'Load Markers';
+ }
+ });
+ }
+});
+// [END maps_weather_api_compact]
\ No newline at end of file
diff --git a/samples/weather-api-current-compact/package.json b/samples/weather-api-current-compact/package.json
new file mode 100644
index 00000000..e145ecb6
--- /dev/null
+++ b/samples/weather-api-current-compact/package.json
@@ -0,0 +1,14 @@
+{
+ "name": "@js-api-samples/weather-api-compact",
+ "version": "1.0.0",
+ "scripts": {
+ "build": "tsc && bash ../jsfiddle.sh map-simple && bash ../app.sh map-simple && bash ../docs.sh map-simple && npm run build:vite --workspace=. && bash ../dist.sh map-simple",
+ "test": "tsc && npm run build:vite --workspace=.",
+ "start": "vite --port 5173",
+ "build:vite": "vite build --base './'",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+
+ }
+}
\ No newline at end of file
diff --git a/samples/weather-api-current-compact/simple-weather-widget.ts b/samples/weather-api-current-compact/simple-weather-widget.ts
new file mode 100644
index 00000000..5b5b8444
--- /dev/null
+++ b/samples/weather-api-current-compact/simple-weather-widget.ts
@@ -0,0 +1,252 @@
+/*
+ * @license
+ * Copyright 2025 Google LLC. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+class SimpleWeatherWidget extends HTMLElement {
+ constructor() {
+ super();
+ this.attachShadow({ mode: 'open' });
+ this.shadowRoot!.innerHTML = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `;
+ }
+
+ set data(weatherData: any) {
+ const iconElement = this.shadowRoot!.getElementById('condition-icon') as HTMLImageElement;
+ const temperatureElement = this.shadowRoot!.getElementById('temperature') as HTMLSpanElement;
+ const rainProbabilityElement = this.shadowRoot!.getElementById('rain-probability') as HTMLSpanElement;
+ const rainQpfElement = this.shadowRoot!.getElementById('rain-qpf') as HTMLSpanElement;
+ const rainDetailsElement = this.shadowRoot!.getElementById('rain-details') as HTMLDivElement;
+
+
+ if (!weatherData || weatherData.error) {
+ iconElement.style.display = 'none';
+ rainDetailsElement.style.display = 'none';
+ if (weatherData && weatherData.error) {
+ temperatureElement.textContent = weatherData.error;
+ temperatureElement.classList.add('error-message'); // Add error class
+ } else {
+ temperatureElement.textContent = 'N/A';
+ temperatureElement.classList.remove('error-message'); // Remove error class
+ }
+ return;
+ }
+
+ // Check if the data is current conditions or forecast day structure
+ const isForecastDay = weatherData.daytimeForecast || weatherData.nighttimeForecast;
+
+ let temperature: number | undefined, iconBaseUri: string | undefined, rainProbability: number | undefined, rainQpf: number | undefined;
+
+ if (isForecastDay) {
+ // Data is a forecast day object
+ const conditions = weatherData;
+ temperature = conditions.maxTemperature?.degrees;
+ iconBaseUri = conditions.daytimeForecast?.weatherCondition?.iconBaseUri || conditions.nighttimeForecast?.weatherCondition?.iconBaseUri;
+ rainProbability = conditions.precipitation?.probability?.percent;
+ rainQpf = conditions.precipitation?.qpf?.quantity;
+ } else {
+ // Data is a current conditions object
+ const conditions = weatherData;
+ temperature = conditions.temperature?.degrees;
+ iconBaseUri = conditions.weatherCondition?.iconBaseUri;
+ rainProbability = conditions.precipitation?.probability?.percent;
+ // For current conditions, prioritize qpf from history if available
+ rainQpf = conditions.currentConditionsHistory?.qpf?.quantity !== undefined ? conditions.currentConditionsHistory.qpf.quantity : conditions.precipitation?.qpf?.quantity;
+ }
+
+ let iconSrc = ''; // Initialize iconSrc
+
+ if (iconBaseUri) {
+ // Use the full iconBaseUri and append .svg
+ iconSrc = `${iconBaseUri}.svg`;
+ } else {
+ // Fallback to a default local icon if iconBaseUri is not available
+ iconSrc = '/icons/cloud-cover-white.svg';
+ }
+
+ iconElement.style.display = 'none'; // Explicitly hide the icon before setting src
+ iconElement.onload = () => {
+ iconElement.style.display = 'inline-block'; // Show the icon after loading
+ };
+ iconElement.onerror = () => {
+ console.error('Failed to load weather icon:', iconSrc);
+ iconElement.style.display = 'none'; // Hide the icon if loading fails
+ };
+ iconElement.src = iconSrc;
+
+
+ temperatureElement.textContent = `${temperature !== undefined ? temperature.toFixed(0) : 'N/A'}°C`; // Rounded temperature
+ temperatureElement.classList.remove('error-message'); // Remove error class if data is valid
+
+ if (rainProbability !== undefined && rainProbability !== null) {
+ rainProbabilityElement.textContent = `${rainProbability}%`;
+ } else {
+ rainProbabilityElement.textContent = '0%';
+ }
+
+ if (rainQpf !== undefined && rainQpf !== null) {
+ rainQpfElement.textContent = `${rainQpf.toFixed(1)}mm`; // Rounded QPF to 1 decimal place
+ } else {
+ rainQpfElement.textContent = '0.0mm'; // Display 0.0mm if data is not available
+ }
+ }
+
+ /**
+ * Sets the visual mode of the widget.
+ * @param mode 'light' or 'dark'
+ */
+ setMode(mode: 'light' | 'dark') {
+ if (mode === 'dark') {
+ this.classList.add('dark-mode');
+ } else {
+ this.classList.remove('dark-mode');
+ }
+ }
+}
+
+customElements.define('simple-weather-widget', SimpleWeatherWidget);
\ No newline at end of file
diff --git a/samples/weather-api-current-compact/style.css b/samples/weather-api-current-compact/style.css
new file mode 100644
index 00000000..70674b39
--- /dev/null
+++ b/samples/weather-api-current-compact/style.css
@@ -0,0 +1,240 @@
+/*
+ * @license
+ * Copyright 2025 Google LLC. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+/* [START maps_map_simple] */
+/*
+ * Always set the map height explicitly to define the size of the div element
+ * that contains the map.
+ */
+#map {
+ height: 100%;
+}
+
+/*
+ * Optional: Makes the sample page fill the window.
+ */
+html,
+body {
+ height: 100%;
+ margin: 0;
+ padding: 0;
+}
+
+/* [END maps_map_simple] */
+
+/* Styles for the weather widget */
+.widget-container {
+ background-color: white; /* Light mode background */
+ color: #222222; /* Light mode text color */
+ padding: 4px 8px; /* Adjust padding */
+ border-radius: 50px; /* Adjusted border-radius for round shape */
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3); /* Light mode box shadow */
+ font-family: 'Google Sans', Roboto, sans-serif; /* Using the requested font stack */
+ width: auto; /* Allow width to adjust to content */
+ text-align: center;
+ position: relative; /* Needed to position arrow relative to this container */
+ min-width: 78px; /* Adjusted minimum width as requested */
+ min-height: 30px; /* Adjusted minimum height as requested by user */
+ display: flex; /* Use flexbox for centering */
+ flex-direction: column; /* Stack children vertically */
+ justify-content: flex-start; /* Align content to the top initially */
+ align-items: center; /* Horizontally center content */
+ user-select: none; /* Prevent text selection */
+ transition: max-height 0.3s ease-out, padding 0.3s ease-out, border-radius 0.3s ease-out; /* Add transition for max-height and padding */
+ overflow: hidden; /* Hide overflowing content during transition */
+ box-sizing: border-box; /* Include padding and border in the element's total width and height */
+ max-height: 50px; /* Set max-height for default state */
+}
+
+/* Arrow indent */
+.widget-container::after {
+ content: '';
+ position: absolute;
+ bottom: -5px; /* Position below the widget container */
+ left: 50%;
+ transform: translateX(-50%);
+ width: 0;
+ height: 0;
+ border-left: 5px solid transparent;
+ border-right: 5px solid transparent;
+ border-top: 5px solid white; /* Match background color of widget-container */
+ transition: all 0.3s ease-out; /* Add transition for smooth arrow movement */
+}
+
+/* Dark mode styles */
+.dark-mode .widget-container {
+ background-color: #222222; /* Dark mode background */
+ color: white; /* Dark mode text color */
+ box-shadow: 0 2px 6px rgba(255, 255, 255, 0.3); /* Dark mode box shadow */
+}
+
+.dark-mode .widget-container::after {
+ border-top-color: #222222; /* Match dark mode background color */
+}
+
+.weather-info-basic {
+ display: flex;
+ align-items: center;
+ justify-content: center; /* Center items */
+ gap: 4px; /* Add gap between temperature and icon */
+ margin-bottom: 0; /* Remove bottom margin */
+ width: 100%; /* Take full width */
+}
+.weather-info-basic img {
+ width: 30px;
+ height: 30px;
+ filter: invert(0); /* Default filter for light mode */
+ flex-shrink: 0; /* Prevent shrinking */
+}
+#condition-icon {
+ display: none; /* Hide the image by default */
+}
+.temperature {
+ font-size: 1.5em; /* Adjust font size */
+ font-weight: bold;
+}
+.error-message {
+ font-size: 1.2em; /* Font size for error messages as requested */
+ font-weight: normal; /* Not bold for error messages */
+ width:80px;
+}
+.rain-details {
+ font-size: 0.9em; /* Match detail line font size */
+ display: none; /* Hide by default */
+ align-items: center;
+ justify-content: flex-start; /* Align rain info to the left */
+ flex-direction: row; /* Arrange rain details horizontally */
+ gap: 5px; /* Space between rain probability and qpf */
+ margin-top: 5px; /* Add space above rain details */
+ width: 100%; /* Take full width */
+}
+ .rain-details img {
+ width: 18px; /* Adjust icon size */
+ height: 18px;
+ margin-right: 5px;
+ }
+
+/* Dark mode rain icon filter */
+.dark-mode .rain-details img {
+ filter: none; /* Remove filter in dark mode */
+}
+
+
+/* Highlighted state styles (on click) */
+.widget-container.highlight {
+ border-radius: 8px; /* Match non-highlighted border-radius */
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3); /* Keep the same box shadow */
+ max-height: 150px; /* Set a larger max-height for expanded state */
+ padding: 10px 15px; /* Keep the same padding */
+ width: auto; /* Allow width to expand */
+ min-height: 70px; /* Increase min-height for expanded state */
+ justify-content: space-between; /* Space out basic and rain info */
+}
+
+.widget-container.highlight::after {
+ border-top: 5px solid white; /* Match background color */
+}
+
+.widget-container.highlight .rain-details {
+ display: flex; /* Show rain details when highlighted */
+}
+
+/* Dark mode highlighted state */
+.dark-mode .widget-container.highlight {
+ box-shadow: 0 2px 6px rgba(255, 255, 255, 0.3); /* Keep the same box shadow */
+}
+
+.dark-mode .widget-container.highlight::after {
+ border-top: 5px solid #222222; /* Match dark mode background color */
+}
+
+/* Styles for the button container wrapper */
+.button-container-wrapper {
+ position: absolute;
+ bottom: 10px;
+ left: 50%;
+ transform: translateX(-50%);
+ z-index: 10; /* Ensure it's above the map */
+ display: flex;
+ gap: 10px; /* Space between buttons */
+}
+
+/* Remove absolute positioning from individual button containers */
+.mode-toggle-container,
+.load-markers-container {
+ position: static;
+ top: auto;
+ left: auto;
+ transform: none;
+ z-index: auto;
+}
+
+/* Common styles for the buttons */
+.button-container-wrapper button {
+ background-color: #4285F4; /* Google Blue */
+ color: white; /* White text for contrast */
+ border: none;
+ padding: 8px 15px;
+ border-radius: 4px;
+ cursor: pointer;
+ font-family: 'Google Sans', Roboto, sans-serif;
+ font-size: 1em;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
+ width:170px;
+}
+
+/* Hover style for the buttons */
+.button-container-wrapper button:hover {
+ background-color: #3367D6; /* Darker shade on hover */
+}
+
+/* Media query for mobile devices */
+@media (max-width: 600px) {
+ .widget-container {
+ padding: 3px 5px; /* Reduce padding */
+ min-width: 60px; /* Reduce min-width */
+ min-height: 25px; /* Reduce min-height */
+ max-height: 40px; /* Adjust max-height */
+ }
+
+ .weather-info-basic img {
+ width: 25px; /* Reduce icon size */
+ height: 25px;
+ }
+
+ .temperature {
+ font-size: 1.2em; /* Reduce font size */
+ }
+
+ .rain-details {
+ font-size: 0.8em; /* Reduce font size */
+ gap: 3px; /* Reduce gap */
+ margin-top: 3px; /* Reduce margin-top */
+ }
+
+ .rain-details img {
+ width: 15px; /* Reduce icon size */
+ height: 15px;
+ margin-right: 3px; /* Reduce margin-right */
+ }
+
+ .widget-container.highlight {
+ max-height: 100px; /* Adjust max-height for expanded state */
+ padding: 8px 10px; /* Adjust padding */
+ min-height: 50px; /* Adjust min-height */
+ }
+
+ .button-container-wrapper {
+ flex-direction: column; /* Stack buttons vertically */
+ gap: 5px; /* Reduce gap between buttons */
+ bottom: 5px; /* Adjust bottom position */
+ }
+
+ .button-container-wrapper button {
+ width: 150px; /* Adjust button width */
+ padding: 6px 10px; /* Adjust button padding */
+ font-size: 0.9em; /* Adjust button font size */
+ }
+}
\ No newline at end of file
diff --git a/samples/weather-api-current-compact/tsconfig.json b/samples/weather-api-current-compact/tsconfig.json
new file mode 100644
index 00000000..f4060794
--- /dev/null
+++ b/samples/weather-api-current-compact/tsconfig.json
@@ -0,0 +1,18 @@
+{
+ "compilerOptions": {
+ "module": "esnext",
+ "target": "esnext",
+ "strict": true,
+ "noImplicitAny": false,
+ "lib": [
+ "es2015",
+ "esnext",
+ "es6",
+ "dom",
+ "dom.iterable"
+ ],
+ "moduleResolution": "Node",
+ "jsx": "preserve",
+ "types": ["@types/google.maps"]
+ }
+}
\ No newline at end of file