diff --git a/e2e/samples.spec.ts b/e2e/samples.spec.ts index bd5493604..844b1924e 100644 --- a/e2e/samples.spec.ts +++ b/e2e/samples.spec.ts @@ -14,8 +14,6 @@ * limitations under the License. */ -/* eslint-disable @typescript-eslint/no-unsafe-return */ - import { test, expect } from '@playwright/test'; import fs from 'node:fs'; import path from 'node:path'; @@ -138,7 +136,7 @@ foldersToTest.forEach((sampleFolder) => { test(`test ${sampleFolder}`, async ({ page }) => { // START run the preview // Get an available port - const port = 8080; + const port = '8080'; const url = `http://localhost:${port}/`; const viteProcess = childProcess.spawn( @@ -205,8 +203,9 @@ foldersToTest.forEach((sampleFolder) => { // Wait for Google Maps to load. await page.waitForFunction( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - () => (window as any).google?.maps, + () => + (window as typeof window & { google?: { maps?: unknown } }) + .google?.maps, { timeout: 500 } ); @@ -216,12 +215,10 @@ foldersToTest.forEach((sampleFolder) => { // Assertions. These must be met or the test will fail. // The sample must load the Google Maps API. const hasGoogleMaps = await page.evaluate(() => { - return ( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - typeof (window as any).google !== 'undefined' && - // eslint-disable-next-line @typescript-eslint/no-explicit-any - typeof (window as any).google.maps !== 'undefined' - ); + const w = window as typeof window & { + google?: { maps?: unknown }; + }; + return typeof w.google?.maps !== 'undefined'; }); expect(hasGoogleMaps).toBeTruthy(); @@ -240,7 +237,7 @@ foldersToTest.forEach((sampleFolder) => { process.kill(viteProcess.pid, 'SIGINT'); } catch (error) { console.warn( - `Failed to kill Vite process for ${sampleFolder} (PID: ${viteProcess.pid}):`, + `Failed to kill Vite process for ${sampleFolder} (PID: ${String(viteProcess.pid)}):`, error ); } diff --git a/e2e/utils.ts b/e2e/utils.ts index c708f4518..a7a59baf9 100644 --- a/e2e/utils.ts +++ b/e2e/utils.ts @@ -23,8 +23,9 @@ import process from 'node:process'; export async function waitForGoogleMapsToLoad(page: Page) { await page.waitForFunction( - // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-return - () => (window as any).google?.maps + () => + (window as typeof window & { google?: { maps?: unknown } }).google + ?.maps ); await page.waitForTimeout(100); } diff --git a/eslint.config.mjs b/eslint.config.mjs index fe8aeb4e4..35a519bc2 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -72,7 +72,14 @@ export default defineConfig([ '@typescript-eslint/no-non-null-assertion': 'off', // downgraded to warn for historic reasons: - '@typescript-eslint/restrict-template-expressions': 'warn', + '@typescript-eslint/restrict-template-expressions': [ + 'warn', + { + allowNumber: true, + allowNullish: true, + allowBoolean: true, + }, + ], '@typescript-eslint/restrict-plus-operands': 'warn', '@typescript-eslint/prefer-nullish-coalescing': 'warn', diff --git a/samples/3d-accessibility-features/index.ts b/samples/3d-accessibility-features/index.ts index 55c8cfa1a..6266356b0 100644 --- a/samples/3d-accessibility-features/index.ts +++ b/samples/3d-accessibility-features/index.ts @@ -51,7 +51,7 @@ async function init() { tourStops.forEach(({ position, title }, i) => { const pin = new PinElement({ - glyphText: `${i + 1}`, + glyphText: String(i + 1), scale: 1.5, glyphColor: '#FFFFFF', }); diff --git a/samples/3d-camera-position/index.ts b/samples/3d-camera-position/index.ts index e58f8330d..8fb204798 100644 --- a/samples/3d-camera-position/index.ts +++ b/samples/3d-camera-position/index.ts @@ -43,7 +43,6 @@ async function initMap(): Promise { const fov = fovClamped.toString(); const roll = map3DElement.roll?.toFixed(0) ?? '0'; const center = map3DElement.center; - const mode = map3DElement.mode; headingVal.textContent = heading; tiltVal.textContent = tilt; @@ -68,7 +67,7 @@ async function initMap(): Promise { lngSlider.value = lng; altitudeVal.textContent = alt; - codeElem.textContent = ``; + codeElem.textContent = ``; } }; @@ -124,8 +123,14 @@ async function initMap(): Promise { map3DElement.tilt = Math.max(0, val); } else if (prop === 'fov') { map3DElement.fov = Math.min(80, Math.max(5, val)); + } else if (prop === 'heading') { + map3DElement.heading = val; + } else if (prop === 'range') { + map3DElement.range = val; + } else if (prop === 'roll') { + map3DElement.roll = val; } else { - // eslint-disable-next-line @typescript-eslint/no-explicit-any + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access (map3DElement as any)[prop] = val; } updateUI(); diff --git a/samples/3d-clamp-mode/index.ts b/samples/3d-clamp-mode/index.ts index dfc9ea12f..1edd4d107 100644 --- a/samples/3d-clamp-mode/index.ts +++ b/samples/3d-clamp-mode/index.ts @@ -5,7 +5,7 @@ */ // [START maps_3d_clamp_mode] -let polyline: google.maps.maps3d.Polyline3DElement; +let polyline: google.maps.maps3d.Polyline3DElement | undefined; async function init() { const { Map3DElement, Polyline3DElement } = diff --git a/samples/3d-coverage-map/index.ts b/samples/3d-coverage-map/index.ts index 9c2d6f8a4..7c7ff4bed 100644 --- a/samples/3d-coverage-map/index.ts +++ b/samples/3d-coverage-map/index.ts @@ -40,7 +40,6 @@ async function init() { placeAutocomplete.addEventListener( 'gmp-select', async ({ placePrediction }) => { - if (!placePrediction) return; const place = placePrediction.toPlace(); await place.fetchFields({ fields: ['location'], diff --git a/samples/3d-map-events/index.ts b/samples/3d-map-events/index.ts index 07eb03ae9..847de60c8 100644 --- a/samples/3d-map-events/index.ts +++ b/samples/3d-map-events/index.ts @@ -15,7 +15,7 @@ async function init() { (i) => i.textContent ); for (const event of events) { - mapElement?.addEventListener(event, () => { + mapElement.addEventListener(event, () => { const eventElement = document.querySelector(`#${event}`); eventElement?.classList.add('active'); setTimeout(() => { diff --git a/samples/3d-places-autocomplete/index.ts b/samples/3d-places-autocomplete/index.ts index 8512400c3..69008d43f 100644 --- a/samples/3d-places-autocomplete/index.ts +++ b/samples/3d-places-autocomplete/index.ts @@ -4,9 +4,6 @@ * SPDX-License-Identifier: Apache-2.0 */ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable @typescript-eslint/no-unsafe-call */ - // [START maps_3d_places_autocomplete] let map: google.maps.maps3d.Map3DElement; @@ -29,11 +26,11 @@ async function init() { } async function initAutocomplete() { - // @ts-expect-error - currently missing. bug fix pending const { PlaceAutocompleteElement } = await google.maps.importLibrary('places'); - const placeAutocomplete = new PlaceAutocompleteElement(); + const options: google.maps.places.PlaceAutocompleteElementOptions = {}; + const placeAutocomplete = new PlaceAutocompleteElement(options); placeAutocomplete.id = 'place-autocomplete-input'; const card = document.getElementById('pac-container')!; card.appendChild(placeAutocomplete); @@ -49,7 +46,7 @@ async function initAutocomplete() { }); // If the place has a geometry, then present it on a map. if (!place.location) { - window.alert('No viewport for input: ' + place.displayName); + window.alert(`No viewport for input: ${place.displayName}`); return; } void flyToPlace(place); @@ -107,7 +104,7 @@ async function getElevationforPoint( locations: [location], }); - if (!elevationResponse?.results.length) { + if (!elevationResponse.results.length) { window.alert( `Insufficient elevation data for place: ${place.displayName}` ); diff --git a/samples/3d-places-autocomplete/package.json b/samples/3d-places-autocomplete/package.json index 77f8df9cb..2992f8041 100644 --- a/samples/3d-places-autocomplete/package.json +++ b/samples/3d-places-autocomplete/package.json @@ -8,7 +8,5 @@ "build:vite": "vite build --base './'", "preview": "vite preview" }, - "dependencies": { - - } + "dependencies": {} } diff --git a/samples/3d-places/index.ts b/samples/3d-places/index.ts index 72db50a49..8e25ed9a7 100644 --- a/samples/3d-places/index.ts +++ b/samples/3d-places/index.ts @@ -29,9 +29,9 @@ async function init() { // Display place details. document.getElementById('placeName')!.innerHTML = - 'Name :
 ' + place.displayName; + `Name :
 ${place.displayName ?? ''}`; document.getElementById('placeId')!.innerHTML = - 'Id :
 ' + place.id; + `Id :
 ${place.id}`; document.getElementById('placeType')!.innerHTML = 'Types :'; for (const type of place.types ?? []) { diff --git a/samples/3d-polygon-click-event/index.ts b/samples/3d-polygon-click-event/index.ts index cf3e584ef..38e39a97d 100644 --- a/samples/3d-polygon-click-event/index.ts +++ b/samples/3d-polygon-click-event/index.ts @@ -54,7 +54,7 @@ function randomizeHexColor(originalHexColor: string) { const g = Math.floor(Math.random() * 256); const b = Math.floor(Math.random() * 256); - console.log(r + ' ' + g + ' ' + b); + console.log(`${r} ${g} ${b}`); // Convert decimal to 2-digit hex, padding with '0' if needed const rHex = ('0' + r.toString(16)).slice(-2); diff --git a/samples/address-validation/index.ts b/samples/address-validation/index.ts index e6f5d92b1..ca0fbfe54 100644 --- a/samples/address-validation/index.ts +++ b/samples/address-validation/index.ts @@ -88,7 +88,7 @@ async function handleValidationSubmit(event: Event) { // Verdict messages const verdictMessages: Record< string, - { trueMessage: string; falseMessage: string } + { trueMessage: string; falseMessage: string } | undefined > = { addressComplete: { trueMessage: @@ -155,7 +155,7 @@ interface ExampleAddress { } // Example Address Data -const examples: Record = { +const examples: Record = { google: { streetAddress1: '1600 Amphitheatre Parkway', streetAddress2: '', // Explicitly empty @@ -215,7 +215,7 @@ function populateAddressFields(exampleAddress: ExampleAddress | null) { // Get values from example, providing empty string as default streetAddress1Input.value = exampleAddress.streetAddress1 || ''; - streetAddress2Input.value = exampleAddress.streetAddress2 || ''; + streetAddress2Input.value = exampleAddress.streetAddress2 ?? ''; cityInput.value = exampleAddress.city || ''; stateInput.value = exampleAddress.state || ''; zipCodeInput.value = exampleAddress.zipCode || ''; diff --git a/samples/advanced-markers-accessibility/index.ts b/samples/advanced-markers-accessibility/index.ts index 46ea868f3..7d85b9e53 100644 --- a/samples/advanced-markers-accessibility/index.ts +++ b/samples/advanced-markers-accessibility/index.ts @@ -48,7 +48,7 @@ async function init() { tourStops.forEach(({ position, title }, i) => { // [START maps_advanced_markers_accessibility_marker] const pin = new PinElement({ - glyphText: `${i + 1}`, + glyphText: String(i + 1), scale: 1.5, }); const marker = new AdvancedMarkerElement({ diff --git a/samples/advanced-markers-animation/index.ts b/samples/advanced-markers-animation/index.ts index 65baeaf25..8e41a7750 100644 --- a/samples/advanced-markers-animation/index.ts +++ b/samples/advanced-markers-animation/index.ts @@ -99,7 +99,7 @@ function createMarker( content.style.opacity = '1'; }); const time = 2 + Math.random(); // 2s delay for easy to see the animation - content.style.setProperty('--delay-time', time + 's'); + content.style.setProperty('--delay-time', `${time}s`); intersectionObserver.observe(content); } diff --git a/samples/ai-powered-summaries/index.ts b/samples/ai-powered-summaries/index.ts index f35ca52a7..57994bae5 100644 --- a/samples/ai-powered-summaries/index.ts +++ b/samples/ai-powered-summaries/index.ts @@ -88,10 +88,8 @@ function updateSummaryPanel(place: google.maps.places.Place) { summaryContent.textContent = ''; aiDisclosure.textContent = ''; - placeName.textContent = place.displayName || ''; - placeAddress.textContent = place.formattedAddress || ''; - - let firstTabActivated = false; + placeName.textContent = place.displayName ?? ''; + placeAddress.textContent = place.formattedAddress ?? ''; /** * Safe Helper: Accepts either a text string or a DOM Node (like a div or DocumentFragment). @@ -125,7 +123,7 @@ function updateSummaryPanel(place: google.maps.places.Place) { } // Set the disclosure text. - aiDisclosure.textContent = disclosure || 'AI-generated content.'; + aiDisclosure.textContent = disclosure ?? 'AI-generated content.'; // Add the content flag URI. if (flagUrl) { @@ -135,12 +133,6 @@ function updateSummaryPanel(place: google.maps.places.Place) { }; tabContainer.appendChild(btn); - - // Auto-select the first available summary. - if (!firstTabActivated) { - btn.click(); - firstTabActivated = true; - } }; // --- 1. Generative Summary (Place) --- @@ -227,8 +219,10 @@ function updateSummaryPanel(place: google.maps.places.Place) { } } - // Safely handle the empty state. - if (!firstTabActivated) { + // Safely handle the empty state or activate first tab. + if (tabContainer.firstChild) { + (tabContainer.firstChild as HTMLButtonElement).click(); + } else { const msg = document.createElement('em'); msg.textContent = 'No AI summaries are available for this specific location.'; diff --git a/samples/boundaries-click/index.ts b/samples/boundaries-click/index.ts index 12e2c002f..f7f5dcc3e 100644 --- a/samples/boundaries-click/index.ts +++ b/samples/boundaries-click/index.ts @@ -57,7 +57,7 @@ async function init() { // If the map gets a mousemove, that means there are no feature layers // with listeners registered under the mouse, so we clear the last // interacted feature ids. - if (lastInteractedFeatureIds?.length) { + if (lastInteractedFeatureIds.length) { lastInteractedFeatureIds = []; featureLayer.style = applyStyle; } diff --git a/samples/control-replacement/index.ts b/samples/control-replacement/index.ts index 95f0acd49..1c2f12eb7 100644 --- a/samples/control-replacement/index.ts +++ b/samples/control-replacement/index.ts @@ -29,11 +29,11 @@ function initZoomControl(map: google.maps.Map) { const zoomOutButton = document.querySelector('.zoom-control-out')!; zoomInButton.addEventListener('click', () => { - map.setZoom((map.getZoom() || 0) + 1); + map.setZoom((map.getZoom() ?? 0) + 1); }); zoomOutButton.addEventListener('click', () => { - map.setZoom((map.getZoom() || 0) - 1); + map.setZoom((map.getZoom() ?? 0) - 1); }); } diff --git a/samples/dds-datasets-polygon-click/index.ts b/samples/dds-datasets-polygon-click/index.ts index 088beb9eb..58d995741 100644 --- a/samples/dds-datasets-polygon-click/index.ts +++ b/samples/dds-datasets-polygon-click/index.ts @@ -14,20 +14,16 @@ let datasetLayer: google.maps.FeatureLayer; // [START maps_dds_datasets_polygon_click_eventhandler] // Note, 'globalid' is an attribute in this Dataset. function handleClick(event: google.maps.FeatureMouseEvent) { - if (event.features) { - lastClickedFeatureIds = event.features.map( - (f) => (f as google.maps.DatasetFeature).datasetAttributes.globalid - ); - } + lastClickedFeatureIds = event.features.map( + (f) => (f as google.maps.DatasetFeature).datasetAttributes.globalid + ); datasetLayer.style = applyStyle; } function handleMouseMove(event: google.maps.FeatureMouseEvent) { - if (event.features) { - lastInteractedFeatureIds = event.features.map( - (f) => (f as google.maps.DatasetFeature).datasetAttributes.globalid - ); - } + lastInteractedFeatureIds = event.features.map( + (f) => (f as google.maps.DatasetFeature).datasetAttributes.globalid + ); datasetLayer.style = applyStyle; } // [END maps_dds_datasets_polygon_click_eventhandler] @@ -54,7 +50,7 @@ async function init() { // If the map gets a mousemove, that means there are no feature layers // with listeners registered under the mouse, so we clear the last // interacted feature ids. - if (lastInteractedFeatureIds?.length) { + if (lastInteractedFeatureIds.length) { lastInteractedFeatureIds = []; datasetLayer.style = applyStyle; } diff --git a/samples/dds-region-viewer/index.ts b/samples/dds-region-viewer/index.ts index 58f061e1f..a99c1ca1f 100644 --- a/samples/dds-region-viewer/index.ts +++ b/samples/dds-region-viewer/index.ts @@ -4,10 +4,6 @@ * SPDX-License-Identifier: Apache-2.0 */ -/* eslint-disable @typescript-eslint/no-explicit-any */ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable @typescript-eslint/no-unsafe-call */ - /** * Data-driven styling region coverage viewer! * - View feature boundary availability around the world. @@ -37,6 +33,24 @@ let selectedPlaceId: string; import * as countries from './src/countries.json'; +interface CountryFeatures { + country: boolean; + administrative_area_level_1: boolean; + administrative_area_level_2: boolean; + postal_code: boolean; + locality: boolean; + school_district: boolean; +} + +interface CountryInfo { + name: string; + code: string; + feature: CountryFeatures; +} + +const countriesList = (countries as unknown as { default: CountryInfo[] }) + .default; + async function init() { await Promise.all([ google.maps.importLibrary('maps'), @@ -275,7 +289,7 @@ function applyStyle(placeid?: string) { // Populate the countries menu. function buildMenu() { - for (const item of (countries as any).default) { + for (const item of countriesList) { const countryOption = document.createElement('option'); countryOption.textContent = item.name; countryOption.value = item.code; @@ -307,7 +321,7 @@ function updateFeatureMenuAvailability(countryCode: string) { // Do a comparison on the map, and disable any false items. for (const [feature, isAvailable] of featureAvailabilityMap) { - const menuItem = featureMenu.namedItem(feature)!; + const menuItem = featureMenu.namedItem(feature); if (menuItem) menuItem.disabled = !isAvailable; } } @@ -315,12 +329,12 @@ function updateFeatureMenuAvailability(countryCode: string) { // Return a map of feature availability for a country. function getFeatureAvailability(countryCode: string) { // Return the data for the selected country. - const selectedCountry = (countries as any).default.find((country: any) => { + const selectedCountry = countriesList.find((country) => { return country.code === countryCode; }); // Create a map for our values. - const featureAvailabilityMap = new Map([ + const featureAvailabilityMap = new Map([ ['country', selectedCountry?.feature.country], [ 'administrative_area_level_1', diff --git a/samples/deckgl-arclayer/index.ts b/samples/deckgl-arclayer/index.ts index 640a898ec..f3a904ebb 100644 --- a/samples/deckgl-arclayer/index.ts +++ b/samples/deckgl-arclayer/index.ts @@ -4,10 +4,6 @@ * SPDX-License-Identifier: Apache-2.0 */ -/* eslint-disable @typescript-eslint/no-explicit-any */ -/* eslint-disable @typescript-eslint/no-unsafe-call */ -/* eslint-disable @typescript-eslint/no-unsafe-return */ - // [START maps_deckgl_arclayer] import { GoogleMapsOverlay } from '@deck.gl/google-maps'; import { ArcLayer } from '@deck.gl/layers'; @@ -40,8 +36,10 @@ async function init() { id: 'flights', data: dataUrl, - dataTransform: (data: any) => - data.features.filter((f: any) => f.properties.scalerank < 4), + // @ts-expect-error - DeckGL typings are incompatible with GeoJSON Feature Arrays + dataTransform: ( + data: GeoJSON.FeatureCollection + ) => data.features.filter((f) => f.properties.scalerank < 4), getSourcePosition: () => [14.42076, 50.08804], // Prague getTargetPosition: (f: Feature) => f.geometry.coordinates as [number, number], diff --git a/samples/deckgl-heatmap/index.ts b/samples/deckgl-heatmap/index.ts index 9519f7774..2fb29717b 100644 --- a/samples/deckgl-heatmap/index.ts +++ b/samples/deckgl-heatmap/index.ts @@ -4,10 +4,6 @@ * SPDX-License-Identifier: Apache-2.0 */ -/* eslint-disable @typescript-eslint/no-explicit-any */ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable @typescript-eslint/no-unsafe-return */ - /* [START maps_deckgl_heatmap] */ // Initialize and add the map let map: google.maps.Map; @@ -30,18 +26,42 @@ declare namespace mdc { } } +interface BikeParking { + COORDINATES: [number, number]; + SPACES: number; + ADDRESS?: string; + RACKS?: number; +} + +interface PickingInfo { + object?: T; + x: number; + y: number; +} + +interface HeatmapLayerProps { + id: string; + data: string; + getPosition: (d: BikeParking) => [number, number]; + getWeight: (d: BikeParking) => number; + radiusPixels?: number; + visible?: boolean; + pickable?: boolean; + onHover?: (info: PickingInfo) => void; +} + // Declare global namespace for Deck.gl to satisfy TypeScript compiler declare namespace deck { class HeatmapLayer { - constructor(props: any); - props: any; - clone(props: any): HeatmapLayer; + constructor(props: HeatmapLayerProps); + props: HeatmapLayerProps; + clone(props: Partial): HeatmapLayer; pickable: boolean; // Added pickable property } class GoogleMapsOverlay { - constructor(props: any); + constructor(props: { layers: HeatmapLayer[]; controller?: boolean }); setMap(map: google.maps.Map | null): void; - setProps(props: any): void; + setProps(props: { layers: HeatmapLayer[] }): void; } // Add other Deck.gl types used globally if needed } @@ -94,13 +114,13 @@ async function init(): Promise { // Assign to the outer heatmapLayer id: 'HeatmapLayer', // Change layer ID data: 'https://raw.githubusercontent.com/visgl/deck.gl-data/master/website/sf-bike-parking.json', // Use the loaded data - getPosition: (d: any) => d.COORDINATES, // Use 'any' for simplicity, or define a proper type - getWeight: (d: any) => d.SPACES, // Use 'any' for simplicity, or define a proper type + getPosition: (d: BikeParking) => d.COORDINATES, + getWeight: (d: BikeParking) => d.SPACES, radiusPixels: 25, // Adjust radius as in user's example visible: true, pickable: true, - onHover: (info: any) => { + onHover: (info: PickingInfo) => { // Use 'any' for info for simplicity, or define a proper type const tooltip = document.getElementById('tooltip'); if (tooltip) { @@ -114,12 +134,10 @@ async function init(): Promise { if (info.object.RACKS !== undefined) { tooltipContent += `Racks: ${info.object.RACKS}
`; } - if (info.object.SPACES !== undefined) { - tooltipContent += `Spaces: ${info.object.SPACES}
`; - } + tooltipContent += `Spaces: ${info.object.SPACES}
`; tooltip.innerHTML = tooltipContent; - tooltip.style.left = info.x + 'px'; - tooltip.style.top = info.y + 'px'; + tooltip.style.left = String(info.x) + 'px'; + tooltip.style.top = String(info.y) + 'px'; tooltip.style.display = 'block'; } else { tooltip.style.display = 'none'; @@ -170,7 +188,7 @@ async function init(): Promise { infoWindow.close(); const content = `
Location: ${latLng.lat().toFixed(3)}, ${latLng.lng().toFixed(3)}
- + `; infoWindow.setContent(content); infoWindow.open(map, marker); @@ -179,7 +197,7 @@ async function init(): Promise { // Open InfoWindow immediately on first click const content = `
Location: ${latLng.lat().toFixed(3)}, ${latLng.lng().toFixed(3)}
- + `; infoWindow.setContent(content); infoWindow.open(map, marker); @@ -189,7 +207,7 @@ async function init(): Promise { // InfoWindow remains open const content = `
Location: ${latLng.lat().toFixed(3)}, ${latLng.lng().toFixed(3)}
- + `; infoWindow.setContent(content); infoWindow.open(map, marker); diff --git a/samples/deckgl-kml-updated/index.ts b/samples/deckgl-kml-updated/index.ts index dd0c211db..61c49191f 100644 --- a/samples/deckgl-kml-updated/index.ts +++ b/samples/deckgl-kml-updated/index.ts @@ -5,9 +5,6 @@ */ /* eslint-disable @typescript-eslint/no-explicit-any */ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable @typescript-eslint/no-unsafe-call */ -/* eslint-disable @typescript-eslint/no-unsafe-argument */ /* [START maps_deckgl_kml_updated] */ // Import necessary loader @@ -60,6 +57,12 @@ declare namespace mdc { } } +interface KmlFeature { + properties: { + description: string; + }; +} + // Initialize and add the map let map: google.maps.Map; let geojsonLayer: deck.GeoJsonLayer; @@ -67,8 +70,8 @@ let googleMapsOverlay: deck.GoogleMapsOverlay; async function init(): Promise { // Progress bar logic moved from index.html - let progress: mdc.linearProgress.MDCLinearProgress; - const progressDiv = document.querySelector('.mdc-linear-progress')!; + let progress: mdc.linearProgress.MDCLinearProgress | undefined; + const progressDiv = document.querySelector('.mdc-linear-progress'); if (progressDiv) { // Assuming 'mdc' is globally available, potentially loaded via a script tag // If not, you might need to import it or add type definitions. @@ -76,8 +79,8 @@ async function init(): Promise { progress.open(); progress.determinate = false; progress.done = function () { - progress!.close(); - progressDiv.remove(); // Use optional chaining in case progressDiv is null + progress?.close(); + progressDiv.remove(); }; } @@ -107,7 +110,7 @@ async function init(): Promise { // Deck.gl Layer and Overlay geojsonLayer = new deck.GeoJsonLayer({ id: 'geojson-layer', - data: `https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/1.0_week_age.kml?t=${Date.now()}`, // Append timestamp to prevent caching + data: `https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/1.0_week_age.kml?t=${String(Date.now())}`, // Append timestamp to prevent caching loaders: [KMLLoader], pickable: true, stroked: true, // Set to true to add a border @@ -121,10 +124,10 @@ async function init(): Promise { pointRadiusMinPixels: 2, pointRadiusMaxPixels: 200, getRadius: () => 8000, - getFillColor: (f: any) => { + getFillColor: (f: KmlFeature) => { // Extract magnitude from the description string const description = f.properties.description; - const magnitudeMatch = description.match(/M (\d+\.?\d*)/); + const magnitudeMatch = /M (\d+\.?\d*)/.exec(description); let parsedMagnitude: number | null = null; if (magnitudeMatch?.[1]) { parsedMagnitude = parseFloat(magnitudeMatch[1]); @@ -139,8 +142,7 @@ async function init(): Promise { const maxMag = 7; // Use parsed magnitude for color calculation - const magnitudeForColor = - parsedMagnitude !== null ? parsedMagnitude : minMag; + const magnitudeForColor = parsedMagnitude ?? minMag; // Normalize magnitude const normalizedMagnitude = Math.max( @@ -176,14 +178,22 @@ async function init(): Promise { }, }, // Optional: Add onHover or onClick handlers for interactivity - onHover: ({ object, x, y }: { object: any; x: number; y: number }) => { + onHover: ({ + object, + x, + y, + }: { + object: KmlFeature | undefined; + x: number; + y: number; + }) => { const tooltip = document.getElementById('tooltip'); // Assuming a tooltip element exists if (tooltip && object) { let tooltipContent = `Earthquakes 1.0_week_age`; tooltipContent += `

${object.properties.description}

`; tooltip.innerHTML = tooltipContent; - tooltip.style.left = x + 'px'; - tooltip.style.top = y + 'px'; + tooltip.style.left = String(x) + 'px'; + tooltip.style.top = String(y) + 'px'; tooltip.style.display = 'block'; } else if (tooltip) { tooltip.style.display = 'none'; @@ -191,9 +201,7 @@ async function init(): Promise { }, onDataLoad: () => { console.log('KML data loaded'); - if (progress?.done) { - progress.done(); - } + progress?.done?.(); }, }); @@ -228,7 +236,7 @@ async function init(): Promise { minColor[1] + normalizedMagnitude * (maxColor[1] - minColor[1]); const b = minColor[2] + normalizedMagnitude * (maxColor[2] - minColor[2]); - const color = `rgb(${Math.round(r)}, ${Math.round(g)}, ${Math.round(b)})`; + const color = `rgb(${String(Math.round(r))}, ${String(Math.round(g))}, ${String(Math.round(b))})`; // Create legend item element const legendItem = document.createElement('div'); @@ -241,7 +249,7 @@ async function init(): Promise { // Create label const label = document.createElement('span'); - label.textContent = `${magnitude}`; // Adjust label format as needed + label.textContent = String(magnitude); // Adjust label format as needed // Append color swatch and label to legend item legendItem.appendChild(colorSwatch); diff --git a/samples/deckgl-kml/index.ts b/samples/deckgl-kml/index.ts index 1a40ddeca..2caa16215 100644 --- a/samples/deckgl-kml/index.ts +++ b/samples/deckgl-kml/index.ts @@ -5,20 +5,35 @@ */ /* eslint-disable @typescript-eslint/no-explicit-any */ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable @typescript-eslint/no-unsafe-call */ -/* eslint-disable @typescript-eslint/no-unsafe-argument */ -/* eslint-disable @typescript-eslint/no-unsafe-return */ /* [START maps_deckgl_kml] */ // Import necessary loader import { KMLLoader } from '@loaders.gl/kml'; +interface KmlFeatureProperties { + color?: string; + stroke?: string; + name?: string; + description?: string; + styleUrl?: string; + 'stroke-width'?: string; + fill?: string; + centroid?: [number, number]; + [key: string]: string | [number, number] | undefined; +} +interface KmlFeature { + properties: KmlFeatureProperties; + geometry?: { + type: string; + coordinates: number[][][]; + }; +} + // Declare global namespace for Deck.gl to satisfy TypeScript compiler declare namespace deck { class GeoJsonLayer { constructor(props: any); - props: any; + props: { data: string }; clone(props: any): GeoJsonLayer; pickable: boolean; getFillColor: (d: any) => number[]; @@ -67,8 +82,8 @@ let googleMapsOverlay: deck.GoogleMapsOverlay; async function init(): Promise { // Progress bar logic moved from index.html - let progress: mdc.linearProgress.MDCLinearProgress; - const progressDiv = document.querySelector('.mdc-linear-progress')!; + let progress: mdc.linearProgress.MDCLinearProgress | undefined; + const progressDiv = document.querySelector('.mdc-linear-progress'); if (progressDiv) { // Assuming 'mdc' is globally available, potentially loaded via a script tag // If not, you might need to import it or add type definitions. @@ -76,8 +91,8 @@ async function init(): Promise { progress.open(); progress.determinate = false; progress.done = function () { - progress.close(); - progressDiv?.remove(); // Use optional chaining in case progressDiv is null + progress?.close(); + progressDiv.remove(); }; } @@ -114,9 +129,9 @@ async function init(): Promise { extruded: false, // Set to false for lines lineWidthScale: 14, // Adjust line width scale lineWidthMinPixels: 4, - getLineColor: (d: any) => { + getLineColor: (d: KmlFeature) => { // Function to get color from properties - const color = d.properties.color || d.properties.stroke; // Try 'color' or 'stroke' property + const color = d.properties.color ?? d.properties.stroke; // Try 'color' or 'stroke' property if (color) { // Convert hex or AABBGGRR string to RGBA array const rgba = hexOrAabbggrrToRgba(color); @@ -137,10 +152,18 @@ async function init(): Promise { } }, // Optional: Add onHover or onClick handlers for interactivity - onHover: ({ object, x, y }: { object: any; x: number; y: number }) => { + onHover: ({ + object, + x, + y, + }: { + object: KmlFeature | undefined; + x: number; + y: number; + }) => { const tooltip = document.getElementById('tooltip'); // Assuming a tooltip element exists if (tooltip && object) { - let tooltipContent = `

${object.properties.name || 'GeoJSON Feature'}

`; + let tooltipContent = `

${object.properties.name ?? 'GeoJSON Feature'}

`; // Define a list of common KML properties to display const kmlProperties = [ 'description', @@ -152,15 +175,18 @@ async function init(): Promise { ]; for (const key of kmlProperties) { if ( - object.properties.hasOwnProperty(key) && + Object.prototype.hasOwnProperty.call( + object.properties, + key + ) && object.properties[key] !== undefined ) { - tooltipContent += `

${key}: ${object.properties[key]}

`; + tooltipContent += `

${key}: ${String(object.properties[key])}

`; } } tooltip.innerHTML = tooltipContent; - tooltip.style.left = x + 'px'; - tooltip.style.top = y + 'px'; + tooltip.style.left = String(x) + 'px'; + tooltip.style.top = String(y) + 'px'; tooltip.style.display = 'block'; } else if (tooltip) { tooltip.style.display = 'none'; @@ -171,7 +197,7 @@ async function init(): Promise { const textLayer = new deck.TextLayer({ id: 'text-layer', data: geojsonLayer.props.data, // Use the same data as the GeoJsonLayer - getPosition: (d: any) => { + getPosition: (d: KmlFeature) => { // Attempt to use a 'centroid' property if available, otherwise use the first coordinate let position = [0, 0]; // Default position if (d.properties.centroid) { @@ -182,13 +208,13 @@ async function init(): Promise { ) { // Assuming Polygon or MultiPolygon const coordinates = d.geometry.coordinates[0][0]; - if (coordinates && coordinates.length >= 2) { + if (coordinates.length >= 2) { position = [coordinates[0], coordinates[1]]; // [lng, lat] } } return position; }, - getText: (d: any) => d.properties.name || '', // Display the name property + getText: (d: KmlFeature) => d.properties.name ?? '', // Display the name property getColor: [0, 0, 0, 255], // Black text color for better visibility getSize: 16, // Increased text size getAngle: 0, diff --git a/samples/deckgl-points/index.ts b/samples/deckgl-points/index.ts index fd1759434..58b3efe75 100644 --- a/samples/deckgl-points/index.ts +++ b/samples/deckgl-points/index.ts @@ -27,8 +27,8 @@ function isEarthquake( return ( f.properties !== null && typeof f.properties === 'object' && - // eslint-disable-next-line @typescript-eslint/no-explicit-any - typeof (f.properties as any).mag === 'number' + 'mag' in f.properties && + typeof (f.properties as Record).mag === 'number' ); } diff --git a/samples/deckgl-polygon/index.ts b/samples/deckgl-polygon/index.ts index a83ddc536..7d5ccfdf6 100644 --- a/samples/deckgl-polygon/index.ts +++ b/samples/deckgl-polygon/index.ts @@ -3,9 +3,6 @@ * Copyright 2025 Google LLC. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ -/* eslint-disable @typescript-eslint/no-explicit-any */ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable @typescript-eslint/no-unsafe-return */ /* [START maps_deckgl_polygon] */ // Initialize and add the map @@ -26,18 +23,45 @@ declare namespace mdc { } } +interface ZipcodeData { + contour: [number, number][] | [number, number][][]; + population: number; + area: number; + zipcode: string; +} + +interface PolygonLayerProps { + id: string; + data: string; + getPolygon: (d: ZipcodeData) => [number, number][] | [number, number][][]; + getElevation: (d: ZipcodeData) => number; + getFillColor: (d: ZipcodeData) => number[]; + getLineColor?: number[]; + getLineWidth?: number; + lineWidthMinPixels?: number; + visible?: boolean; + opacity?: number; + pickable?: boolean; + onDataLoad?: () => void; + onHover?: (info: { + object: ZipcodeData | undefined; + x: number; + y: number; + }) => void; +} + // Declare global namespace for Deck.gl to satisfy TypeScript compiler declare namespace deck { class PolygonLayer { - constructor(props: any); - props: any; - clone(props: any): PolygonLayer; + constructor(props: PolygonLayerProps); + props: PolygonLayerProps; + clone(props: Partial): PolygonLayer; pickable: boolean; // Added pickable property } class GoogleMapsOverlay { - constructor(props: any); + constructor(props: { layers: PolygonLayer[] }); setMap(map: google.maps.Map | null): void; - setProps(props: any): void; + setProps(props: { layers: PolygonLayer[] }): void; } // Add other Deck.gl types used globally if needed } @@ -87,9 +111,9 @@ async function init(): Promise { id: 'PolygonLayer', data: 'https://raw.githubusercontent.com/visgl/deck.gl-data/master/website/sf-zipcodes.json', - getPolygon: (d: any) => d.contour, // Use 'any' for simplicity - getElevation: (d: any) => d.population / d.area / 10, // Use 'any' for simplicity - getFillColor: (d: any) => [d.population / d.area / 60, 140, 0], // Use 'any' for simplicity + getPolygon: (d: ZipcodeData) => d.contour, + getElevation: (d: ZipcodeData) => d.population / d.area / 10, + getFillColor: (d: ZipcodeData) => [d.population / d.area / 60, 140, 0], getLineColor: [255, 255, 255], getLineWidth: 20, lineWidthMinPixels: 1, @@ -101,32 +125,30 @@ async function init(): Promise { // Check if progress is defined // Add a small delay to ensure the progress bar is removed setTimeout(() => { - progress?.done?.(); // hides progress bar + progress.done?.(); // hides progress bar }, 100); // 100ms delay } }, - onHover: ({ object, x, y }: { object: any; x: number; y: number }) => { - // Use 'any' for object for simplicity + onHover: ({ + object, + x, + y, + }: { + object: ZipcodeData | undefined; + x: number; + y: number; + }) => { const tooltip = document.getElementById('tooltip'); if (tooltip) { if (object) { // Format data for tooltip (example: display specific properties) let tooltipContent = ''; // Updated title - const properties = object; // Correctly access the properties object - if (properties) { - if (properties.zipcode !== undefined) { - tooltipContent += `Zipcode: ${properties.zipcode}
`; - } - if (properties.population !== undefined) { - tooltipContent += `Population: ${properties.population}
`; - } - if (properties.area !== undefined) { - tooltipContent += `Area: ${properties.area}
`; - } - } + tooltipContent += `Zipcode: ${object.zipcode}
`; + tooltipContent += `Population: ${object.population}
`; + tooltipContent += `Area: ${object.area}
`; tooltip.innerHTML = tooltipContent; - tooltip.style.left = x + 'px'; - tooltip.style.top = y + 'px'; + tooltip.style.left = String(x) + 'px'; + tooltip.style.top = String(y) + 'px'; tooltip.style.display = 'block'; } else { tooltip.style.display = 'none'; @@ -147,7 +169,7 @@ async function init(): Promise { if (toggleButton) { // Check if toggleButton is found toggleButton.addEventListener('click', () => { - const currentVisible = polygonLayer.props.visible; + const currentVisible = polygonLayer.props.visible ?? true; // Create a new layer instance with toggled visibility const newPolygonLayer = polygonLayer.clone({ visible: !currentVisible, diff --git a/samples/event-poi/index.ts b/samples/event-poi/index.ts index 0dabe0828..a4d2645ab 100644 --- a/samples/event-poi/index.ts +++ b/samples/event-poi/index.ts @@ -55,7 +55,7 @@ async function showInfoWindow( const content = document.createElement('div'); const address = document.createElement('div'); const placeId = document.createElement('div'); - address.textContent = place.formattedAddress || ''; + address.textContent = place.formattedAddress ?? ''; placeId.textContent = place.id || ''; content.append(address, placeId); @@ -63,7 +63,7 @@ async function showInfoWindow( const name = document.createElement('div'); name.style.fontWeight = 'bold'; name.style.fontSize = 'medium'; - name.textContent = place.displayName || ''; + name.textContent = place.displayName ?? ''; // Update info window options. infoWindow.setOptions({ diff --git a/samples/event-properties/index.ts b/samples/event-properties/index.ts index 5d92767c0..ce22ca6a6 100644 --- a/samples/event-properties/index.ts +++ b/samples/event-properties/index.ts @@ -20,7 +20,7 @@ async function init() { infoWindow.open(innerMap); innerMap.addListener('zoom_changed', () => { - infoWindow.setContent('Zoom: ' + innerMap.getZoom()!); + infoWindow.setContent(`Zoom: ${String(innerMap.getZoom())}`); }); } diff --git a/samples/geocoding-region-es/index.ts b/samples/geocoding-region-es/index.ts index 5490b2e17..a89c3bf99 100644 --- a/samples/geocoding-region-es/index.ts +++ b/samples/geocoding-region-es/index.ts @@ -29,7 +29,7 @@ async function init(): Promise { }) .catch((e: unknown) => { window.alert( - 'Geocode was not successful for the following reason: ' + e + `Geocode was not successful for the following reason: ${String(e)}` ); }); } diff --git a/samples/geocoding-region-us/index.ts b/samples/geocoding-region-us/index.ts index 497b12158..47fdd751c 100644 --- a/samples/geocoding-region-us/index.ts +++ b/samples/geocoding-region-us/index.ts @@ -29,7 +29,7 @@ async function init(): Promise { }) .catch((e: unknown) => { window.alert( - 'Geocode was not successful for the following reason: ' + e + `Geocode was not successful for the following reason: ${String(e)}` ); }); } diff --git a/samples/geocoding-reverse/index.ts b/samples/geocoding-reverse/index.ts index 7875559e8..648848c53 100644 --- a/samples/geocoding-reverse/index.ts +++ b/samples/geocoding-reverse/index.ts @@ -52,7 +52,7 @@ async function init() { // Add a click event listener to the map. innerMap.addListener('click', (event: google.maps.MapMouseEvent) => { if (event.latLng) { - latLngQuery.value = `${event.latLng.lat()}, ${event.latLng.lng()}`; + latLngQuery.value = `${String(event.latLng.lat())}, ${String(event.latLng.lng())}`; void geocodeLatLng(geocoder, innerMap, infoWindow); } }); diff --git a/samples/map-projection-simple/index.ts b/samples/map-projection-simple/index.ts index 493f644f9..9c28026ba 100644 --- a/samples/map-projection-simple/index.ts +++ b/samples/map-projection-simple/index.ts @@ -30,12 +30,7 @@ async function init() { const coordsDiv = document.getElementById('coords')!; innerMap.addListener('mousemove', (event: google.maps.MapMouseEvent) => { - coordsDiv.textContent = - 'lat: ' + - Math.round(event.latLng!.lat()) + - ', ' + - 'lng: ' + - Math.round(event.latLng!.lng()); + coordsDiv.textContent = `lat: ${String(Math.round(event.latLng!.lat()))}, lng: ${String(Math.round(event.latLng!.lng()))}`; }); // Add some markers to the map. @@ -72,7 +67,7 @@ async function initGallPeters() { if (y < 0 || y >= scale) return ''; - return 'gall-peters_' + zoom + '_' + x + '_' + y + '.png'; + return `gall-peters_${zoom}_${x}_${y}.png`; }, tileSize: new Size(GALL_PETERS_RANGE_X, GALL_PETERS_RANGE_Y), minZoom: 0, diff --git a/samples/place-autocomplete-data-session/index.ts b/samples/place-autocomplete-data-session/index.ts index 78dfb3a2c..8606f32a6 100644 --- a/samples/place-autocomplete-data-session/index.ts +++ b/samples/place-autocomplete-data-session/index.ts @@ -7,7 +7,7 @@ // [START maps_place_autocomplete_data_session] const mapElement = document.querySelector('gmp-map')!; let innerMap: google.maps.Map; -let marker: google.maps.marker.AdvancedMarkerElement; +let marker: google.maps.marker.AdvancedMarkerElement | undefined; const titleElement = document.querySelector('.title')!; const resultsContainerElement = document.querySelector('.results')!; const inputElement = document.querySelector('input')!; @@ -50,7 +50,7 @@ async function makeAutocompleteRequest(inputEvent: Event) { const { AutocompleteSuggestion } = await google.maps.importLibrary('places'); - if (!(inputEvent.target as HTMLInputElement)?.value) { + if (!(inputEvent.target as HTMLInputElement).value) { titleElement.textContent = ''; resultsContainerElement.replaceChildren(); return; @@ -84,7 +84,6 @@ async function makeAutocompleteRequest(inputEvent: Event) { placeButton.addEventListener('click', () => { void onPlaceSelected(placePrediction.toPlace()); }); - // eslint-disable-next-line @typescript-eslint/no-base-to-string placeButton.textContent = placePrediction.text.toString(); placeButton.classList.add('place-button'); diff --git a/samples/place-autocomplete-data-simple/index.ts b/samples/place-autocomplete-data-simple/index.ts index c24ecba8d..cd7cc455d 100644 --- a/samples/place-autocomplete-data-simple/index.ts +++ b/samples/place-autocomplete-data-simple/index.ts @@ -55,7 +55,6 @@ async function init() { const listItem = document.createElement('li'); listItem.appendChild( - // eslint-disable-next-line @typescript-eslint/no-base-to-string document.createTextNode(placePrediction!.text.toString()) ); resultsElement.appendChild(listItem); diff --git a/samples/place-autocomplete-element/index.ts b/samples/place-autocomplete-element/index.ts index 521db7441..37b87ab29 100644 --- a/samples/place-autocomplete-element/index.ts +++ b/samples/place-autocomplete-element/index.ts @@ -4,21 +4,15 @@ * SPDX-License-Identifier: Apache-2.0 */ -// Remove these disables once the PlacesLibrary typing is fixed: - -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable @typescript-eslint/no-unsafe-call */ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ - // [START maps_place_autocomplete_element] async function init(): Promise { // [START maps_place_autocomplete_element_add] // Request needed libraries. - // @ts-expect-error - when this gets addressed also remove the global eslint-disables above const { PlaceAutocompleteElement } = await google.maps.importLibrary('places'); // Create the input HTML element, and append it. - const placeAutocomplete = new PlaceAutocompleteElement(); + const options: google.maps.places.PlaceAutocompleteElementOptions = {}; + const placeAutocomplete = new PlaceAutocompleteElement(options); document.body.appendChild(placeAutocomplete); // [END maps_place_autocomplete_element_add] diff --git a/samples/place-class/index.ts b/samples/place-class/index.ts index 12063162c..cad69a956 100644 --- a/samples/place-class/index.ts +++ b/samples/place-class/index.ts @@ -50,7 +50,7 @@ async function getPlaceDetails() { const content = document.createElement('div'); const address = document.createElement('div'); const placeId = document.createElement('div'); - address.textContent = place.formattedAddress || ''; + address.textContent = place.formattedAddress ?? ''; placeId.textContent = place.id; content.append(placeId, address); diff --git a/samples/place-nearby-search/index.ts b/samples/place-nearby-search/index.ts index 1c1283e8c..cc0c120f5 100644 --- a/samples/place-nearby-search/index.ts +++ b/samples/place-nearby-search/index.ts @@ -97,7 +97,7 @@ async function nearbySearch() { // Build the content of the InfoWindow safely using DOM elements. const content = document.createElement('div'); const address = document.createElement('div'); - address.textContent = place.formattedAddress || ''; + address.textContent = place.formattedAddress ?? ''; const placeId = document.createElement('div'); placeId.textContent = place.id; content.append(address, placeId); diff --git a/samples/place-photos/index.ts b/samples/place-photos/index.ts index eb293bdf4..a6b36dcf1 100644 --- a/samples/place-photos/index.ts +++ b/samples/place-photos/index.ts @@ -30,11 +30,11 @@ async function init() { // Add photos to the gallery. place.photos?.forEach((photo) => { - const altText = 'Photo of ' + place.displayName; + const altText = `Photo of ${place.displayName}`; const img = document.createElement('img'); const imgButton = document.createElement('button'); const expandedImage = document.createElement('img'); - img.src = photo?.getURI({ maxHeight: 380 }); + img.src = photo.getURI({ maxHeight: 380 }); img.alt = altText; imgButton.addEventListener('click', (event) => { centerSelectedThumbnail(imgButton); @@ -61,11 +61,11 @@ async function init() { if (place.photos && place.photos.length > 0) { const photo = place.photos[0]; const img = document.createElement('img'); - img.alt = 'Photo of ' + place.displayName; + img.alt = `Photo of ${place.displayName}`; img.src = photo.getURI(); expandedImageDiv.appendChild(img); - if (photo.authorAttributions && photo.authorAttributions.length > 0) { + if (photo.authorAttributions.length > 0) { expandedImageDiv.appendChild( createAttribution(photo.authorAttributions[0]) ); diff --git a/samples/place-reviews/index.ts b/samples/place-reviews/index.ts index 8e48cd105..da100929c 100644 --- a/samples/place-reviews/index.ts +++ b/samples/place-reviews/index.ts @@ -48,12 +48,12 @@ async function init() { const authorUri = place.reviews[0].authorAttribution!.uri; // Safely populate the HTML. - title.textContent = place.displayName || ''; - address.textContent = place.formattedAddress || ''; + title.textContent = place.displayName ?? ''; + address.textContent = place.formattedAddress ?? ''; rating.textContent = `Rating: ${reviewRating} stars`; - review.textContent = reviewText || ''; + review.textContent = reviewText ?? ''; authorLink.textContent = authorName; - authorLink.href = authorUri || ''; + authorLink.href = authorUri ?? ''; authorLink.target = '_blank'; content.appendChild(title); diff --git a/samples/places-autocomplete-addressform/index.ts b/samples/places-autocomplete-addressform/index.ts index 9ea715084..135a08b53 100644 --- a/samples/places-autocomplete-addressform/index.ts +++ b/samples/places-autocomplete-addressform/index.ts @@ -59,23 +59,23 @@ async function fillInAddress( // the specified type-value. for (const component of place.addressComponents) { if (component.types.includes('street_address')) { - address1 = `${component.longText} ${address1}`; + address1 = `${component.longText ?? ''} ${address1}`; } if (component.types.includes('street_number')) { - address1 = `${component.longText} ${address1}`; + address1 = `${component.longText ?? ''} ${address1}`; } if (component.types.includes('route')) { - address1 += component.shortText; + address1 += component.shortText ?? ''; } if (component.types.includes('postal_code')) { - postcode = `${component.longText}${postcode}`; + postcode = `${component.longText ?? ''}${postcode}`; } if (component.types.includes('postal_code_suffix')) { - postcode = `${postcode}-${component.longText}`; + postcode = `${postcode}-${component.longText ?? ''}`; } if (component.types.includes('locality')) { diff --git a/samples/polyline-complex/index.ts b/samples/polyline-complex/index.ts index 06b179388..f1776d97c 100644 --- a/samples/polyline-complex/index.ts +++ b/samples/polyline-complex/index.ts @@ -45,7 +45,7 @@ async function init() { // Add a new marker at the new plotted point on the polyline. new AdvancedMarkerElement({ position: latLng, - title: '#' + path.getLength(), + title: `#${String(path.getLength())}`, map: innerMap, }); }); diff --git a/samples/react-ui-kit-search-nearby/src/app.tsx b/samples/react-ui-kit-search-nearby/src/app.tsx index 1fadcbc91..507fad3df 100644 --- a/samples/react-ui-kit-search-nearby/src/app.tsx +++ b/samples/react-ui-kit-search-nearby/src/app.tsx @@ -182,7 +182,7 @@ const PlaceSearchController = ({ (nearbyRequest as any).includedTypes = [selectedType]; const handleLoad = () => { - const newPlaces = (placeSearch as any).places || []; + const newPlaces = (placeSearch as any).places ?? []; setPlaces(newPlaces); if (newPlaces.length > 0) { const newBounds = new coreLib.LatLngBounds(); @@ -226,8 +226,7 @@ const PlaceSearchController = ({ if (selectedPlace?.location) { placeRequestRef.current.place = selectedPlace; - if (placeDetailsRef.current) - placeDetailsRef.current.style.display = 'block'; + placeDetailsRef.current.style.display = 'block'; popupMarkerRef.current.position = selectedPlace.location; popupMarkerRef.current.map = map; @@ -250,8 +249,7 @@ const PlaceSearchController = ({ } } else { popupMarkerRef.current.map = null; - if (placeDetailsRef.current) - placeDetailsRef.current.style.display = 'none'; + placeDetailsRef.current.style.display = 'none'; } }, [selectedPlace, map]); diff --git a/samples/react-ui-kit-search-text/src/app.tsx b/samples/react-ui-kit-search-text/src/app.tsx index 1aac09100..a1dbb4a26 100644 --- a/samples/react-ui-kit-search-text/src/app.tsx +++ b/samples/react-ui-kit-search-text/src/app.tsx @@ -27,10 +27,6 @@ import './styles.css'; const API_KEY = 'AIzaSyA6myHzS10YXdcazAFalmXvDkrYCp5cLc8'; -if (!API_KEY) { - console.error('Missing Google Maps API key'); -} - const App = () => ( { - const newPlaces = (placeSearch as any).places || []; + const newPlaces = (placeSearch as any).places ?? []; setPlaces(newPlaces); if (newPlaces.length > 0) { const newBounds = new coreLib.LatLngBounds(); @@ -218,8 +214,7 @@ const PlaceSearchController = ({ if (selectedPlace?.location) { placeRequestRef.current.place = selectedPlace; - if (placeDetailsRef.current) - placeDetailsRef.current.style.display = 'block'; + placeDetailsRef.current.style.display = 'block'; popupMarkerRef.current.position = selectedPlace.location; popupMarkerRef.current.map = map; @@ -242,8 +237,7 @@ const PlaceSearchController = ({ } } else { popupMarkerRef.current.map = null; - if (placeDetailsRef.current) - placeDetailsRef.current.style.display = 'none'; + placeDetailsRef.current.style.display = 'none'; } }, [selectedPlace, map]); diff --git a/samples/rectangle-event/index.ts b/samples/rectangle-event/index.ts index 5736c8f84..9091afe16 100644 --- a/samples/rectangle-event/index.ts +++ b/samples/rectangle-event/index.ts @@ -49,16 +49,9 @@ function showNewRect() { const sw = rectangle.getBounds()!.getSouthWest(); const contentString = - 'Rectangle moved.
' + - 'New north-east corner: ' + - ne.lat() + - ', ' + - ne.lng() + - '
' + - 'New south-west corner: ' + - sw.lat() + - ', ' + - sw.lng(); + `Rectangle moved.
` + + `New north-east corner: ${String(ne.lat())}, ${String(ne.lng())}
` + + `New south-west corner: ${String(sw.lat())}, ${String(sw.lng())}`; // Set the info window's content and position. infoWindow.setContent(contentString); diff --git a/samples/routes-compute-routes/index.ts b/samples/routes-compute-routes/index.ts index 52e17d0b0..5175cdece 100644 --- a/samples/routes-compute-routes/index.ts +++ b/samples/routes-compute-routes/index.ts @@ -4,9 +4,6 @@ * SPDX-License-Identifier: Apache-2.0 */ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable @typescript-eslint/no-unsafe-call */ - // [START maps_routes_compute_routes] let markers: google.maps.marker.AdvancedMarkerElement[] = []; let polylines: google.maps.Polyline[] = []; @@ -29,7 +26,6 @@ async function init() { const [ { InfoWindow }, { AdvancedMarkerElement }, - // @ts-expect-error - currently missing. bug fix pending { PlaceAutocompleteElement }, { ComputeRoutesExtraComputation, ReferenceRoute, Route, RouteLabel }, ] = await Promise.all([ @@ -95,8 +91,9 @@ async function init() { formData: FormData ): google.maps.routes.ComputeRoutesRequest { const travelMode = - (formData.get('travel_mode') as google.maps.TravelModeString) || - undefined; + (formData.get( + 'travel_mode' + ) as google.maps.TravelModeString | null) ?? undefined; const extraComputations: google.maps.routes.ComputeRoutesExtraComputation[] = []; const requestedReferenceRoutes: google.maps.routes.ReferenceRoute[] = @@ -134,11 +131,13 @@ async function init() { routingPreference: (formData.get( 'routing_preference' - ) as google.maps.routes.RoutingPreferenceString) || undefined, + ) as google.maps.routes.RoutingPreferenceString | null) ?? + undefined, polylineQuality: (formData.get( 'polyline_quality' - ) as google.maps.routes.PolylineQualityString) || undefined, + ) as google.maps.routes.PolylineQualityString | null) ?? + undefined, computeAlternativeRoutes: formData.get('compute_alternative_routes') === 'on', routeModifiers: { @@ -259,7 +258,9 @@ async function init() { strokeOpacity: 0.5, strokeWeight: 8, }, - colorScheme: map.innerMap.get('colorScheme'), + colorScheme: map.innerMap.get( + 'colorScheme' + ) as google.maps.ColorScheme, }) ); @@ -313,8 +314,7 @@ async function init() { detailsDiv.appendChild(distanceP); const durationP = document.createElement('p'); - durationP.textContent = - `Duration: ${route.localizedValues.duration}`!; + durationP.textContent = `Duration: ${route.localizedValues.duration ?? ''}`; detailsDiv.appendChild(durationP); } @@ -352,10 +352,6 @@ async function init() { } function attachMapClickListener() { - if (!map?.innerMap) { - return; - } - let infoWindowAlert = document.getElementById('infowindow-alert'); if (!infoWindowAlert) { infoWindowAlert = document.createElement('div'); @@ -385,7 +381,7 @@ async function init() { }); await navigator.clipboard.writeText( - `${mapsMouseEvent.latLng.lat()},${mapsMouseEvent.latLng.lng()}` + `${String(mapsMouseEvent.latLng.lat())},${String(mapsMouseEvent.latLng.lng())}` ); infoWindow.open(map.innerMap); @@ -474,15 +470,17 @@ async function init() { function initializeLocationInputs() { const originAutocomplete = new PlaceAutocompleteElement({ name: 'origin_location', - }); + }) as HTMLElement; const destinationAutocomplete = new PlaceAutocompleteElement({ name: 'destination_location', - }); + }) as HTMLElement; - [ + const autocompletes: [HTMLElement, PlaceAutocompleteSelection][] = [ [originAutocomplete, originAutocompleteSelection], [destinationAutocomplete, destinationAutocompleteSelection], - ].forEach(([autocomplete, autocompleteData]) => { + ]; + + autocompletes.forEach(([autocomplete, autocompleteData]) => { autocomplete.addEventListener( 'gmp-select', async ( diff --git a/samples/routes-get-directions-panel/index.ts b/samples/routes-get-directions-panel/index.ts index 7c7499e2e..c73876064 100644 --- a/samples/routes-get-directions-panel/index.ts +++ b/samples/routes-get-directions-panel/index.ts @@ -62,7 +62,7 @@ async function init(): Promise { // Render navigation instructions const directionsPanel = document.getElementById('directions'); - if (!routes || routes.length === 0) { + if (routes.length === 0) { if (directionsPanel) { directionsPanel.textContent = 'No routes available.'; } @@ -88,7 +88,7 @@ async function init(): Promise { legTitleElement.textContent = `Leg ${index + 1} of ${route.legs!.length}`; legContainer.appendChild(legTitleElement); - if (leg.steps && leg.steps.length > 0) { + if (leg.steps.length > 0) { // Add steps to an ordered list const stepsList = document.createElement('ol'); stepsList.className = 'directions-steps'; @@ -111,7 +111,7 @@ async function init(): Promise { // Distance and Duration if (step.localizedValues) { const distanceNode = document.createElement('p'); - distanceNode.textContent = `${step.localizedValues.distance} (${step.localizedValues.staticDuration})`; + distanceNode.textContent = `${step.localizedValues.distance ?? ''} (${step.localizedValues.staticDuration ?? ''})`; distanceNode.className = 'distance'; directionWrapper.appendChild(distanceNode); } diff --git a/samples/routes-route-matrix/index.ts b/samples/routes-route-matrix/index.ts index 601e75dee..f12ad889b 100644 --- a/samples/routes-route-matrix/index.ts +++ b/samples/routes-route-matrix/index.ts @@ -95,7 +95,7 @@ async function init(): Promise { const marker = new AdvancedMarkerElement({ map, position: origin.location, - title: `Origin: ${origin.displayName}`, + title: `Origin: ${origin.displayName ?? 'Unknown'}`, }); marker.append(pin); markers.push(marker); diff --git a/samples/ui-kit-place-search-nearby/index.ts b/samples/ui-kit-place-search-nearby/index.ts index 61dc39066..c6a686c8f 100644 --- a/samples/ui-kit-place-search-nearby/index.ts +++ b/samples/ui-kit-place-search-nearby/index.ts @@ -14,7 +14,7 @@ const placeSearchQuery = document.querySelector( )!; const placeDetails = document.querySelector('gmp-place-details-compact')!; const placeRequest = document.querySelector('gmp-place-details-place-request')!; -const typeSelect = document.querySelector('.type-select') as HTMLSelectElement; +const typeSelect = document.querySelector('.type-select')!; /* [END maps_ui_kit_place_search_nearby_query_selectors] */ // Global variables for the map, markers, and info window. diff --git a/samples/ui-kit-place-search-text/index.ts b/samples/ui-kit-place-search-text/index.ts index 28325a12f..de8c1e6e4 100644 --- a/samples/ui-kit-place-search-text/index.ts +++ b/samples/ui-kit-place-search-text/index.ts @@ -14,7 +14,7 @@ const placeSearchQuery = document.querySelector( )!; const placeDetails = document.querySelector('gmp-place-details-compact')!; const placeRequest = document.querySelector('gmp-place-details-place-request')!; -const queryInput = document.querySelector('.query-input') as HTMLInputElement; +const queryInput = document.querySelector('.query-input')!; const searchButton = document.querySelector('.search-button')!; /* [END maps_ui_kit_place_search_text_query_selectors] */ diff --git a/samples/weather-api-current-compact/index.ts b/samples/weather-api-current-compact/index.ts index acbe4793f..90c59ebc6 100644 --- a/samples/weather-api-current-compact/index.ts +++ b/samples/weather-api-current-compact/index.ts @@ -3,7 +3,6 @@ * Copyright 2025 Google LLC. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ -/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ // [START maps_weather_api_compact] @@ -18,6 +17,10 @@ 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 +const markerTypes = new WeakMap< + google.maps.marker.AdvancedMarkerElement, + 'initial' | 'button' | 'dynamic' +>(); // To store marker types safely let markersLoaded = false; // Flag to track if button markers are loaded async function init(): Promise { @@ -50,7 +53,7 @@ async function init(): Promise { 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 Element; + let target = event.domEvent.target as Element | null; let isClickOnMarker = false; while (target) { if ( @@ -61,7 +64,7 @@ async function init(): Promise { isClickOnMarker = true; break; } - target = target.parentElement!; + target = target.parentElement; } if (!isClickOnMarker && event.latLng) { @@ -89,7 +92,7 @@ async function init(): Promise { // Remove the previous dynamic marker if it exists const currentDynamicMarkerIndex = allMarkers.findIndex( - (marker) => (marker as any).markerType === 'dynamic' + (marker) => markerTypes.get(marker) === 'dynamic' ); if (currentDynamicMarkerIndex !== -1) { allMarkers[currentDynamicMarkerIndex].map = null; @@ -141,7 +144,7 @@ async function createAndAddMarker( marker.append(weatherWidget); // Store the marker type - (marker as any).markerType = markerType; + markerTypes.set(marker, markerType); // Fetch and update weather data for this location void updateWeatherDisplayForMarker( @@ -248,7 +251,7 @@ async function toggleDarkMode() { 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 Element; + let target = event.domEvent.target as Element | null; let isClickOnMarker = false; while (target) { if ( @@ -259,7 +262,7 @@ async function toggleDarkMode() { isClickOnMarker = true; break; } - target = target.parentElement!; + target = target.parentElement; } if (!isClickOnMarker && event.latLng) { @@ -286,7 +289,7 @@ async function toggleDarkMode() { // Remove the previous dynamic marker if it exists const currentDynamicMarkerIndex = allMarkers.findIndex( - (marker) => (marker as any).markerType === 'dynamic' + (marker) => markerTypes.get(marker) === 'dynamic' ); if (currentDynamicMarkerIndex !== -1) { allMarkers[currentDynamicMarkerIndex].map = null; @@ -330,7 +333,7 @@ function removeButtonMarkers(): void { const buttonMarker = allMarkers.find( (marker) => marker.firstElementChild === activeWeatherWidget && - (marker as any).markerType === 'button' + markerTypes.get(marker) === 'button' ); if (buttonMarker) { const rainDetailsElement = @@ -348,7 +351,7 @@ function removeButtonMarkers(): void { // Remove button markers from the map and the allMarkers array const markersToRemove = allMarkers.filter( - (marker) => (marker as any).markerType === 'button' + (marker) => markerTypes.get(marker) === 'button' ); markersToRemove.forEach((marker) => { marker.map = null; @@ -359,6 +362,13 @@ function removeButtonMarkers(): void { }); } +interface WeatherApiError { + error?: { + status?: string; + message?: string; + }; +} + async function updateWeatherDisplayForMarker( marker: google.maps.marker.AdvancedMarkerElement, widget: SimpleWeatherWidget, @@ -373,12 +383,12 @@ async function updateWeatherDisplayForMarker( const response = await fetch(currentConditionsUrl); if (!response.ok) { - const errorBody = await response.json(); + const errorBody = (await response.json()) as WeatherApiError; console.error('API error response:', errorBody); if ( response.status === 404 && - errorBody?.error?.status === 'NOT_FOUND' + errorBody.error?.status === 'NOT_FOUND' ) { widget.data = { error: 'Location not supported' }; } else { diff --git a/samples/weather-api-current-compact/package.json b/samples/weather-api-current-compact/package.json index 20f065021..8c0e5fb8e 100644 --- a/samples/weather-api-current-compact/package.json +++ b/samples/weather-api-current-compact/package.json @@ -8,7 +8,5 @@ "build:vite": "vite build --base './'", "preview": "vite preview" }, - "dependencies": { - - } + "dependencies": {} } diff --git a/samples/weather-api-current-compact/simple-weather-widget.ts b/samples/weather-api-current-compact/simple-weather-widget.ts index dcb8b75f1..39c12778f 100644 --- a/samples/weather-api-current-compact/simple-weather-widget.ts +++ b/samples/weather-api-current-compact/simple-weather-widget.ts @@ -4,8 +4,41 @@ * SPDX-License-Identifier: Apache-2.0 */ -/* eslint-disable @typescript-eslint/no-explicit-any */ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +interface CombinedWeatherData { + error?: string | null; + temperature?: { + degrees?: number | null; + } | null; + weatherCondition?: { + iconBaseUri?: string | null; + } | null; + currentConditionsHistory?: { + qpf?: { + quantity?: number | null; + } | null; + } | null; + maxTemperature?: { + degrees?: number | null; + } | null; + daytimeForecast?: { + weatherCondition?: { + iconBaseUri?: string | null; + } | null; + } | null; + nighttimeForecast?: { + weatherCondition?: { + iconBaseUri?: string | null; + } | null; + } | null; + precipitation?: { + probability?: { + percent?: number | null; + } | null; + qpf?: { + quantity?: number | null; + } | null; + } | null; +} class SimpleWeatherWidget extends HTMLElement { constructor() { @@ -159,7 +192,7 @@ class SimpleWeatherWidget extends HTMLElement { `; } - set data(weatherData: any) { + set data(weatherData: CombinedWeatherData | null | undefined) { const iconElement = this.shadowRoot!.getElementById( 'condition-icon' ) as HTMLImageElement; @@ -185,20 +218,21 @@ class SimpleWeatherWidget extends HTMLElement { } // Check if the data is current conditions or forecast day structure - const isForecastDay = - weatherData.daytimeForecast || weatherData.nighttimeForecast; + const isForecastDay = !!( + weatherData.daytimeForecast ?? weatherData.nighttimeForecast + ); - let temperature: number | undefined; - let iconBaseUri: string | undefined; - let rainProbability: number | undefined; - let rainQpf: number | undefined; + let temperature: number | null | undefined; + let iconBaseUri: string | null | undefined; + let rainProbability: number | null | undefined; + let rainQpf: number | null | undefined; if (isForecastDay) { // Data is a forecast day object const conditions = weatherData; temperature = conditions.maxTemperature?.degrees; iconBaseUri = - conditions.daytimeForecast?.weatherCondition?.iconBaseUri || + conditions.daytimeForecast?.weatherCondition?.iconBaseUri ?? conditions.nighttimeForecast?.weatherCondition?.iconBaseUri; rainProbability = conditions.precipitation?.probability?.percent; rainQpf = conditions.precipitation?.qpf?.quantity; @@ -208,11 +242,9 @@ class SimpleWeatherWidget extends HTMLElement { 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; + conditions.currentConditionsHistory?.qpf?.quantity ?? + conditions.precipitation?.qpf?.quantity; } let iconSrc = ''; // Initialize iconSrc @@ -235,20 +267,11 @@ class SimpleWeatherWidget extends HTMLElement { }; iconElement.src = iconSrc; - temperatureElement.textContent = `${temperature !== undefined ? temperature.toFixed(0) : 'N/A'}°C`; // Rounded temperature + temperatureElement.textContent = `${typeof temperature === 'number' ? 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 - } + rainProbabilityElement.textContent = `${rainProbability ?? 0}%`; + rainQpfElement.textContent = `${(rainQpf ?? 0).toFixed(1)}mm`; } /**