diff --git a/docs/content/scripts/leaflet/1.guides/1.pickup-locator.md b/docs/content/scripts/leaflet/1.guides/1.pickup-locator.md new file mode 100644 index 000000000..c07fb655e --- /dev/null +++ b/docs/content/scripts/leaflet/1.guides/1.pickup-locator.md @@ -0,0 +1,149 @@ +--- +title: "Example: Pickup Locator" +--- + +A useful locator needs a location list as well as a map. The list keeps addresses and opening hours available when tiles fail, and it gives keyboard users a direct way to choose a location. + +```vue + + + +``` + +Use CSS Grid to place the list beside the map on wide screens and above it on small screens. Keep the list in the document even when the map is the main visual. + +GeoJSON coordinates use `[longitude, latitude]`; Leaflet marker positions use `[latitude, longitude]`. diff --git a/docs/content/scripts/leaflet/1.guides/2.tiles-and-attribution.md b/docs/content/scripts/leaflet/1.guides/2.tiles-and-attribution.md new file mode 100644 index 000000000..e2d3120b0 --- /dev/null +++ b/docs/content/scripts/leaflet/1.guides/2.tiles-and-attribution.md @@ -0,0 +1,80 @@ +--- +title: Tiles & Attribution +--- + +Leaflet renders and controls the map, but it does not supply map imagery. Add a `ScriptLeafletTileLayer` for each raster tile service you use. + +## Choose a Tile Service + +A tile URL usually contains `{z}`, `{x}`, and `{y}` placeholders: + +```vue + +``` + +For production, check the service's traffic limits, caching rules, allowed use cases, privacy policy, and availability commitments. Some providers require an API key or account. Others offer downloadable tiles for self-hosting. + +Keep the URL configurable so you can change providers without rewriting the map component: + +```ts [nuxt.config.ts] +export default defineNuxtConfig({ + runtimeConfig: { + public: { + mapTileUrl: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', + }, + }, +}) +``` + +```vue + + + +``` + +## OpenStreetMap's Standard Tiles + +OpenStreetMap data is open, but the standard tile servers are a donation-funded public service. They have no SLA and may block clients that break the [tile usage policy](https://operations.osmfoundation.org/policies/tiles/). + +When using `tile.openstreetmap.org`: + +- Keep `© OpenStreetMap contributors` visible on the map. +- Use the official HTTPS tile URL and allow the browser to send its normal referrer. +- Respect HTTP caching headers. Do not force tiles to bypass the browser cache. +- Do not bulk download, prefetch areas, or add offline download features. + +Normal browser-based map viewing follows the technical caching and identification requirements. A proxy, native client, or offline feature needs additional work and may need a different provider. + +## Attribution + +Pass the provider's required attribution through the tile layer `options`. Leaflet combines attribution from active layers in its attribution control. + +Do not cover the control with page UI or remove it. If your map uses several data or imagery sources, include the attribution required by each source. + +## Failure Fallback + +Tile services can reject requests or become unavailable while the rest of the page still works. Keep essential locations in HTML and provide a useful error state: + +```vue + + + +``` diff --git a/docs/content/scripts/leaflet/1.guides/3.performance-and-accessibility.md b/docs/content/scripts/leaflet/1.guides/3.performance-and-accessibility.md new file mode 100644 index 000000000..bc2d0bc91 --- /dev/null +++ b/docs/content/scripts/leaflet/1.guides/3.performance-and-accessibility.md @@ -0,0 +1,93 @@ +--- +title: Performance & Accessibility +--- + +`ScriptLeafletMap` loads near the viewport by default. It also reserves the configured dimensions during SSR, which prevents the page from shifting when Leaflet starts. + +## Loading Strategies + +### Visible by Default + +The default `visible` trigger delays the SDK, stylesheet, and tile requests until the map approaches the viewport: + +```vue + +``` + +### Immediate Loading + +Load immediately when the map is the page's primary content: + +```vue + +``` + +You can also use an [element event trigger](/docs/guides/script-triggers#element-event-triggers) such as `mousedown` when the map should load only after explicit interaction. + +## Stable Layout and Loading States + +Always give the map a height. A number is treated as pixels; a CSS length works well for responsive layouts: + +```vue + +``` + +Use the `placeholder`, `loading`, and `error` slots when the default states do not fit your page. Put addresses, opening hours, and other essential details outside the map so they remain available before loading and after an error. + +## Interactive Maps + +Give the map a specific `aria-label` and each marker a unique `alt`. Leaflet's keyboard controls remain available after the map loads. + +```vue + + + +``` + +A text list should duplicate any location a user must discover or select. A visual marker alone is not enough for store selection, delivery status, or directions. + +## Decorative Maps + +Set `:interactive="false"` when the map is only decoration. The component disables map input, hides it from assistive technology, and makes child controls inert. + +```vue + +``` + +## Custom Styles + +Nuxt Scripts injects its embedded Leaflet stylesheet when the SDK starts loading. If your app already provides Leaflet CSS, disable the embedded copy: + +```ts [nuxt.config.ts] +export default defineNuxtConfig({ + css: ['leaflet/dist/leaflet.css'], +}) +``` + +```vue + +``` + +Use the same approach when an inline-style Content Security Policy prevents the default stylesheet injection. diff --git a/docs/content/scripts/leaflet/2.api/1.script-leaflet-map.md b/docs/content/scripts/leaflet/2.api/1.script-leaflet-map.md new file mode 100644 index 000000000..b8de67a63 --- /dev/null +++ b/docs/content/scripts/leaflet/2.api/1.script-leaflet-map.md @@ -0,0 +1,22 @@ +--- +title: +--- + +The map facade reserves layout space during SSR, loads Leaflet when triggered, creates the `L.Map`, and provides it to child components. + +::script-types{script-key="leaflet" filter="ScriptLeafletMap"} +:: + +`center` and `zoom` are reactive. Use `v-model:center` or `v-model:zoom` when you also want to receive user pan and zoom changes. + +```vue + +``` + +The `placeholder`, `awaitingLoad`, `loading`, and `error` slots customize each loading state. The default error state is visible and announced with `role="alert"`. diff --git a/docs/content/scripts/leaflet/2.api/2.tile-layer.md b/docs/content/scripts/leaflet/2.api/2.tile-layer.md new file mode 100644 index 000000000..5a5530ee6 --- /dev/null +++ b/docs/content/scripts/leaflet/2.api/2.tile-layer.md @@ -0,0 +1,10 @@ +--- +title: +--- + +Adds an `L.TileLayer` to the nearest parent map. Nuxt Scripts does not choose a tile provider for you. + +::script-types{script-key="leaflet" filter="ScriptLeafletTileLayer"} +:: + +Always follow the selected provider's terms and pass its required attribution through `options.attribution`. diff --git a/docs/content/scripts/leaflet/2.api/3.marker.md b/docs/content/scripts/leaflet/2.api/3.marker.md new file mode 100644 index 000000000..9af678abc --- /dev/null +++ b/docs/content/scripts/leaflet/2.api/3.marker.md @@ -0,0 +1,10 @@ +--- +title: +--- + +Adds a reactive `L.Marker`. Supply a unique `alt` so the generated marker image has a useful accessible name. + +::script-types{script-key="leaflet" filter="ScriptLeafletMarker"} +:: + +Nest a [``{lang="html"}](/scripts/leaflet/api/popup) in the default slot to bind it to the marker. diff --git a/docs/content/scripts/leaflet/2.api/4.popup.md b/docs/content/scripts/leaflet/2.api/4.popup.md new file mode 100644 index 000000000..4f26a8c5e --- /dev/null +++ b/docs/content/scripts/leaflet/2.api/4.popup.md @@ -0,0 +1,8 @@ +--- +title: +--- + +Renders slotted HTML in an `L.Popup`. Nest it inside a marker, or pass `position` for a standalone popup. Use the reactive `open` prop to control visibility. + +::script-types{script-key="leaflet" filter="ScriptLeafletPopup"} +:: diff --git a/docs/content/scripts/leaflet/2.api/5.geojson.md b/docs/content/scripts/leaflet/2.api/5.geojson.md new file mode 100644 index 000000000..0f2e37562 --- /dev/null +++ b/docs/content/scripts/leaflet/2.api/5.geojson.md @@ -0,0 +1,10 @@ +--- +title: +--- + +Adds inline GeoJSON through `L.geoJSON`. Replacing `data` clears and repopulates the existing layer; changing `options.style` updates its path style. + +::script-types{script-key="leaflet" filter="ScriptLeafletGeoJson"} +:: + +Remote fetching is intentionally outside the component. Fetch and validate data with `useFetch`, then pass the resulting object to `data` so loading and failure states remain explicit. diff --git a/docs/content/scripts/leaflet/2.api/6.use-script-leaflet.md b/docs/content/scripts/leaflet/2.api/6.use-script-leaflet.md new file mode 100644 index 000000000..012f62e04 --- /dev/null +++ b/docs/content/scripts/leaflet/2.api/6.use-script-leaflet.md @@ -0,0 +1,17 @@ +--- +title: useScriptLeaflet +--- + +Use `useScriptLeaflet()`{lang="ts"} when you need direct access to the Leaflet namespace or are building your own map component. + +::script-types{script-key="leaflet" filter="useScriptLeaflet"} +:: + +```ts +const { onLoaded } = useScriptLeaflet() + +onLoaded(({ L }) => { + const bounds = L.latLngBounds(points) + // Use the Leaflet API directly. +}) +``` diff --git a/docs/content/scripts/leaflet/index.md b/docs/content/scripts/leaflet/index.md new file mode 100644 index 000000000..c50f979ca --- /dev/null +++ b/docs/content/scripts/leaflet/index.md @@ -0,0 +1,97 @@ +--- +title: Leaflet +description: Add lightweight, provider-agnostic interactive maps to Nuxt with lazy loading. +links: + - label: useScriptLeaflet + icon: i-simple-icons-github + to: https://github.com/nuxt/scripts/blob/main/packages/script/src/runtime/registry/leaflet.ts + size: xs + - label: "" + icon: i-simple-icons-github + to: https://github.com/nuxt/scripts/blob/main/packages/script/src/runtime/components/Leaflet/ScriptLeafletMap.vue + size: xs +--- + +[Leaflet](https://leafletjs.com/) is an open-source JavaScript library for interactive maps. It provides the map renderer and interaction model; you choose the tile provider, attribution, geocoding, and routing services separately. + +Nuxt Scripts supports Leaflet 1.9.4 through the [`useScriptLeaflet()`{lang="ts"}](/scripts/leaflet/api/use-script-leaflet) composable and a small set of declarative components. First-party mode bundles the SDK by default, while Nuxt Scripts embeds the styles and marker images locally. + +::script-types{exclude-components} +:: + +## Setup + +Install Leaflet's types, then enable the registry entry: + +```bash +pnpm add -D @types/leaflet +``` + +```ts [nuxt.config.ts] +export default defineNuxtConfig({ + scripts: { + registry: { + leaflet: { trigger: false }, + }, + }, +}) +``` + +## Quick Start + +Leaflet does not include map imagery. This example explicitly chooses OpenStreetMap's standard tile service and keeps its required visible attribution. + +```vue + + + +``` + +The default `visible` trigger defers the Leaflet SDK and all tile requests until the map approaches the viewport. The component reserves its dimensions during SSR to avoid layout shift. + +::callout{color="amber"} +OpenStreetMap's public tile service has usage limits and no SLA. Read [Tiles & Attribution](/scripts/leaflet/guides/tiles-and-attribution) before choosing it for production. +:: + +See [Pickup Locator](/scripts/leaflet/guides/pickup-locator) for a complete store locator with a keyboard-accessible location list, popups, and a GeoJSON delivery zone. + +## Components + +- [``{lang="html"}](/scripts/leaflet/api/script-leaflet-map) creates the map and controls lazy loading. +- [``{lang="html"}](/scripts/leaflet/api/tile-layer) connects an explicit tile provider. +- [``{lang="html"}](/scripts/leaflet/api/marker) adds accessible, reactive markers. +- [``{lang="html"}](/scripts/leaflet/api/popup) binds slotted HTML to a marker or coordinate. +- [``{lang="html"}](/scripts/leaflet/api/geojson) renders inline GeoJSON. + +## Guides + +- [Pickup Locator](/scripts/leaflet/guides/pickup-locator) builds a practical locator from markers, popups, and GeoJSON. +- [Tiles & Attribution](/scripts/leaflet/guides/tiles-and-attribution) explains where map imagery comes from and what production deployments need to consider. +- [Performance & Accessibility](/scripts/leaflet/guides/performance-and-accessibility) covers loading triggers, SSR sizing, fallback content, and decorative maps. diff --git a/docs/content/scripts/maplibre/1.guides/1.delivery-tracker.md b/docs/content/scripts/maplibre/1.guides/1.delivery-tracker.md new file mode 100644 index 000000000..ac8e3f51c --- /dev/null +++ b/docs/content/scripts/maplibre/1.guides/1.delivery-tracker.md @@ -0,0 +1,155 @@ +--- +title: "Example: Delivery Tracker" +--- + +This tracker combines a GeoJSON route with a reactive courier marker. Update the marker from a timer or [WebSocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) handler in production; MapLibre moves it without recreating it. + +```vue + + + +``` + +Keep order status, checkpoint names, and estimated times in normal HTML outside the canvas. The `description` slot connects the route summary to the map for assistive technology. + +MapLibre coordinates use `[longitude, latitude]` order. diff --git a/docs/content/scripts/maplibre/1.guides/2.styles-and-providers.md b/docs/content/scripts/maplibre/1.guides/2.styles-and-providers.md new file mode 100644 index 000000000..d3c2e67a4 --- /dev/null +++ b/docs/content/scripts/maplibre/1.guides/2.styles-and-providers.md @@ -0,0 +1,70 @@ +--- +title: Styles & Providers +--- + +MapLibre GL JS renders the map in the browser. It does not include a basemap or a hosted tile service. + +| Part | What it does | +|---|---| +| MapLibre GL JS | Renders vector or raster sources with WebGL and handles map interaction. | +| Style JSON | Defines sources, layers, fonts, icons, colors, and labels. | +| Tile service | Hosts the vector or raster tiles referenced by the style. | +| Geographic data | Supplies roads, places, boundaries, and other mapped features. | + +The style URL passed to `map-style` selects the rest of the stack: + +```vue + +``` + +## OpenFreeMap + +OpenFreeMap provides public MapLibre-compatible styles without an API key. Its styles include OpenStreetMap attribution, and its [terms](https://openfreemap.org/tos/) describe the public service as provided as-is and subject to change or discontinuation. + +The public instance works well for evaluation, prototypes, and projects that can tolerate that availability model. Choose a provider with contractual availability or self-host the [OpenFreeMap stack](https://github.com/hyperknot/openfreemap) when the map is operationally critical. + +## OpenMapTiles and OpenStreetMap + +These names refer to different parts of the stack: + +- **OpenMapTiles** is an open vector-tile schema, toolchain, and set of styles. Use it through a hosted provider or serve compatible styles and tiles yourself. +- **OpenStreetMap** supplies open geographic data. A provider can turn that data into vector tiles consumed by a MapLibre style. +- **OpenStreetMap's standard tile service** serves raster images under a separate usage policy. It is not the vector style used in the example above. + +MapLibre can load any URL or inline object that follows the MapLibre Style Specification. Check that every source, sprite, and font URL in a self-hosted style is reachable from the browser. + +## Credentials and Attribution + +Hosted providers may require an API key in the style URL or one of its source URLs. Treat browser map credentials as public and restrict them by origin, API scope, and quota in the provider dashboard. + +MapLibre displays attribution declared by the active sources. Verify it in the rendered map, especially after editing a style or replacing its sources. Do not cover or remove required attribution. + +## Keep the Provider Replaceable + +Store the style URL in runtime config when environments use different services: + +```ts [nuxt.config.ts] +export default defineNuxtConfig({ + runtimeConfig: { + public: { + mapStyleUrl: 'https://tiles.openfreemap.org/styles/liberty', + }, + }, +}) +``` + +```vue + + + +``` diff --git a/docs/content/scripts/maplibre/1.guides/3.performance-csp-and-accessibility.md b/docs/content/scripts/maplibre/1.guides/3.performance-csp-and-accessibility.md new file mode 100644 index 000000000..966c0e7b1 --- /dev/null +++ b/docs/content/scripts/maplibre/1.guides/3.performance-csp-and-accessibility.md @@ -0,0 +1,120 @@ +--- +title: Performance, CSP & Accessibility +--- + +`ScriptMapLibreMap` loads near the viewport by default and reserves its configured dimensions during SSR. The SDK, stylesheet, Web Worker, style, and tiles stay off the network until the trigger runs. + +## Loading Strategies + +Use the default `visible` trigger for maps below the fold: + +```vue + +``` + +Load immediately when the map is the page's primary content: + +```vue + +``` + +Always provide a height so SSR and the first client render use the same layout: + +```vue + +``` + +## WebGL and Loading Failures + +MapLibre needs WebGL and several resources referenced by the style. Use the `error` slot for browsers without WebGL and for network or initialization failures: + +```vue + + + +``` + +Keep essential locations, route status, and actions in normal HTML rather than relying on the canvas. + +## Accessibility + +Give each interactive map a useful `aria-label`. Use the `description` slot for the locations, routes, or links a screen-reader user needs, and give each marker a unique `aria-label`. + +```vue + + + +``` + +For a decorative map, set `:interactive="false"`. The component disables input, removes the map from the accessibility tree, and makes its descendants inert. + +## Stylesheet Loading + +Nuxt Scripts injects its version-pinned MapLibre stylesheet when the SDK starts loading. To bundle the stylesheet with your app instead: + +```ts [nuxt.config.ts] +export default defineNuxtConfig({ + css: ['maplibre-gl/dist/maplibre-gl.css'], +}) +``` + +```vue + +``` + +## Content Security Policy + +The standard MapLibre build creates a Blob worker. Its documented CSP includes `worker-src blob:`, `child-src blob:`, and `img-src data: blob:` alongside the origins used by your style. + +For a policy that does not allow Blob workers, self-host MapLibre's CSP build and worker: + +```ts [nuxt.config.ts] +export default defineNuxtConfig({ + scripts: { + registry: { + maplibre: { + scriptInput: { src: '/maplibre-gl-csp.js' }, + }, + }, + }, +}) +``` + +```vue + +``` + +Add the style, tile, sprite, glyph, and worker origins to the matching directives in your policy. See MapLibre's [CSP directives](https://maplibre.org/maplibre-gl-js/docs/#csp-directives) for the complete requirements. diff --git a/docs/content/scripts/maplibre/2.api/1.script-maplibre-map.md b/docs/content/scripts/maplibre/2.api/1.script-maplibre-map.md new file mode 100644 index 000000000..c2ddb383f --- /dev/null +++ b/docs/content/scripts/maplibre/2.api/1.script-maplibre-map.md @@ -0,0 +1,28 @@ +--- +title: +--- + +The map facade reserves layout space during SSR, loads MapLibre when triggered, creates the `Map`, and provides it to child components. + +::script-types{script-key="maplibre" filter="ScriptMapLibreMap"} +:: + +`mapStyle`, `center`, `zoom`, `bearing`, and `pitch` are reactive. Use their `v-model` bindings when you also want to receive camera changes made by the user. + +```vue + + + +``` + +The `placeholder`, `awaitingLoad`, `loading`, and `error` slots customize each loading state. The default error state is visible and announced with `role="alert"`. diff --git a/docs/content/scripts/maplibre/2.api/2.marker.md b/docs/content/scripts/maplibre/2.api/2.marker.md new file mode 100644 index 000000000..0741f0b98 --- /dev/null +++ b/docs/content/scripts/maplibre/2.api/2.marker.md @@ -0,0 +1,12 @@ +--- +title: +--- + +Adds a reactive MapLibre `Marker`. Supply a unique `aria-label` so its generated DOM element has a useful accessible name. + +::script-types{script-key="maplibre" filter="ScriptMapLibreMarker"} +:: + +Nest a [``{lang="html"}](/scripts/maplibre/api/popup) in the default slot to bind it to the marker. Use `options.draggable` and the drag events for an editable marker. + +Marker options with public setters update reactively, including class names, dragging, rotation and pitch alignment, offset, opacity, and subpixel positioning. MapLibre reads constructor-only options such as `element`, `anchor`, `color`, and `scale` when it creates the marker. diff --git a/docs/content/scripts/maplibre/2.api/3.popup.md b/docs/content/scripts/maplibre/2.api/3.popup.md new file mode 100644 index 000000000..25b36171e --- /dev/null +++ b/docs/content/scripts/maplibre/2.api/3.popup.md @@ -0,0 +1,10 @@ +--- +title: +--- + +Renders slotted HTML in a MapLibre `Popup`. Nest it inside a marker, or pass `position` for a standalone popup. Use the reactive `open` prop to control visibility. + +`new maplibregl.Popup()`{lang="ts"} reads the `options` object at creation time. Options backed by MapLibre public setters also update reactively: `className`, `maxWidth`, `offset`, `padding`, and `subpixelPositioning`. Other options are initialization-only. + +::script-types{script-key="maplibre" filter="ScriptMapLibrePopup"} +:: diff --git a/docs/content/scripts/maplibre/2.api/4.navigation-control.md b/docs/content/scripts/maplibre/2.api/4.navigation-control.md new file mode 100644 index 000000000..89f80e740 --- /dev/null +++ b/docs/content/scripts/maplibre/2.api/4.navigation-control.md @@ -0,0 +1,8 @@ +--- +title: +--- + +Adds MapLibre's navigation control to the nearest parent map. Use `options` to choose whether zoom, compass, and pitch visualization controls are shown. + +::script-types{script-key="maplibre" filter="ScriptMapLibreNavigationControl"} +:: diff --git a/docs/content/scripts/maplibre/2.api/5.geojson.md b/docs/content/scripts/maplibre/2.api/5.geojson.md new file mode 100644 index 000000000..109b19018 --- /dev/null +++ b/docs/content/scripts/maplibre/2.api/5.geojson.md @@ -0,0 +1,14 @@ +--- +title: +--- + +Adds a GeoJSON source and one or more MapLibre style layers. Replacing `data` updates the existing source; changing layer or source options rebuilds resources in a controlled order. + +Changing `source-id` removes the previously owned source and layers before recreating them under the new ID. + +::script-types{script-key="maplibre" filter="ScriptMapLibreGeoJson"} +:: + +Each layer gets the component's `source-id` unless it explicitly supplies another source. The component also restores its source and layers after the map style changes. + +Remote fetching is intentionally outside the component. Fetch and validate data with `useFetch`, then pass the resulting object to `data` so loading and failure states remain explicit. diff --git a/docs/content/scripts/maplibre/2.api/6.use-script-maplibre.md b/docs/content/scripts/maplibre/2.api/6.use-script-maplibre.md new file mode 100644 index 000000000..b91878280 --- /dev/null +++ b/docs/content/scripts/maplibre/2.api/6.use-script-maplibre.md @@ -0,0 +1,17 @@ +--- +title: useScriptMapLibre +--- + +Use `useScriptMapLibre()`{lang="ts"} when you need direct access to the MapLibre namespace or are building your own map component. + +::script-types{script-key="maplibre" filter="useScriptMapLibre"} +:: + +```ts +const { onLoaded } = useScriptMapLibre() + +onLoaded(({ maplibregl }) => { + const bounds = new maplibregl.LngLatBounds(points) + // Use the MapLibre API directly. +}) +``` diff --git a/docs/content/scripts/maplibre/index.md b/docs/content/scripts/maplibre/index.md new file mode 100644 index 000000000..af6dd2ff3 --- /dev/null +++ b/docs/content/scripts/maplibre/index.md @@ -0,0 +1,100 @@ +--- +title: MapLibre GL JS +description: Add lazy-loaded, provider-agnostic vector maps to Nuxt with MapLibre GL JS. +links: + - label: useScriptMapLibre + icon: i-simple-icons-github + to: https://github.com/nuxt/scripts/blob/main/packages/script/src/runtime/registry/maplibre.ts + size: xs + - label: "" + icon: i-simple-icons-github + to: https://github.com/nuxt/scripts/blob/main/packages/script/src/runtime/components/MapLibre/ScriptMapLibreMap.vue + size: xs +--- + +[MapLibre GL JS](https://maplibre.org/maplibre-gl-js/docs/) is an open-source WebGL renderer for interactive vector maps. It renders the map and handles interaction; you choose the style, tile source, attribution, geocoding, and routing services separately. + +Nuxt Scripts supports MapLibre GL JS 5.24 through the [`useScriptMapLibre()`{lang="ts"}](/scripts/maplibre/api/use-script-maplibre) composable and declarative components for common map resources. First-party mode can bundle the JavaScript SDK. Nuxt Scripts loads the required MapLibre stylesheet separately by default. + +::script-types{exclude-components} +:: + +## Setup + +Install MapLibre for its TypeScript definitions, then enable the registry entry: + +```bash +pnpm add -D maplibre-gl +``` + +```ts [nuxt.config.ts] +export default defineNuxtConfig({ + scripts: { + registry: { + maplibre: { trigger: false }, + }, + }, +}) +``` + +## Quick Start + +Pass a MapLibre style URL or an inline style specification. This example uses OpenFreeMap's keyless Liberty style. + +```vue + + + +``` + +MapLibre coordinates use `[longitude, latitude]` order. This is the reverse of the `[latitude, longitude]` convention used by Leaflet. + +The default `visible` trigger defers the SDK, style, and tile requests until the map approaches the viewport. The component reserves its dimensions during SSR to avoid layout shift. + +::callout{color="amber"} +OpenFreeMap's public instance needs no API key, but it has no SLA. Read [Styles & Providers](/scripts/maplibre/guides/styles-and-providers) before choosing a production style service. +:: + +See [Delivery Tracker](/scripts/maplibre/guides/delivery-tracker) for a complete route tracker with reactive progress, selectable checkpoints, and an accessible text alternative. + +## Components + +- [``{lang="html"}](/scripts/maplibre/api/script-maplibre-map) creates the map and controls lazy loading. +- [``{lang="html"}](/scripts/maplibre/api/marker) adds an accessible, reactive marker. +- [``{lang="html"}](/scripts/maplibre/api/popup) binds slotted HTML to a marker or coordinate. +- [``{lang="html"}](/scripts/maplibre/api/navigation-control) adds zoom, compass, and pitch controls. +- [``{lang="html"}](/scripts/maplibre/api/geojson) manages a GeoJSON source and its style layers. + +## Guides + +- [Delivery Tracker](/scripts/maplibre/guides/delivery-tracker) builds a practical route tracker from reactive markers and styled GeoJSON. +- [Styles & Providers](/scripts/maplibre/guides/styles-and-providers) separates the renderer, style, tile service, and map data choices. +- [Performance, CSP & Accessibility](/scripts/maplibre/guides/performance-csp-and-accessibility) covers loading triggers, WebGL fallbacks, workers, and decorative maps. diff --git a/package.json b/package.json index 9446be21a..53eeb4936 100644 --- a/package.json +++ b/package.json @@ -37,8 +37,10 @@ "@nuxt/test-utils": "catalog:", "@nuxtjs/partytown": "catalog:", "@paypal/paypal-js": "catalog:", + "@types/geojson": "catalog:", "@types/google.maps": "catalog:", "@types/jest-image-snapshot": "catalog:", + "@types/leaflet": "catalog:", "@types/node": "catalog:", "@types/youtube": "catalog:", "@vue/test-utils": "catalog:", @@ -49,6 +51,7 @@ "h3": "catalog:", "happy-dom": "catalog:", "jest-image-snapshot": "catalog:", + "maplibre-gl": "catalog:", "nuxt": "catalog:", "ofetch": "catalog:", "ohash": "catalog:", diff --git a/packages/script/THIRD_PARTY_LICENSES.md b/packages/script/THIRD_PARTY_LICENSES.md new file mode 100644 index 000000000..078d0ad66 --- /dev/null +++ b/packages/script/THIRD_PARTY_LICENSES.md @@ -0,0 +1,124 @@ +# Third-party licenses + +## Leaflet 1.9.4 + +Copyright (c) 2010-2023, Vladimir Agafonkin +Copyright (c) 2010-2011, CloudMade + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +## MapLibre GL JS 5.24.0 + +Copyright (c) 2023, MapLibre contributors + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +- Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +- Neither the name of MapLibre GL JS nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +### Contains code from mapbox-gl-js v1.13 and earlier + +Version v1.13 of mapbox-gl-js and earlier are licensed under a BSD-3-Clause license. + +Copyright (c) 2020, Mapbox + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +- Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +- Neither the name of Mapbox GL JS nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +### Contains code from glfx.js + +Copyright (C) 2011 by Evan Wallace + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +### Contains a portion of d3-color + +Copyright 2010-2016 Mike Bostock + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +- Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +- Neither the name of the author nor the names of contributors may be used to + endorse or promote products derived from this software without specific prior + written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/packages/script/package.json b/packages/script/package.json index e823ef881..d004b6c28 100644 --- a/packages/script/package.json +++ b/packages/script/package.json @@ -49,6 +49,7 @@ "nuxt-scripts": "./bin/cli.mjs" }, "files": [ + "THIRD_PARTY_LICENSES.md", "bin", "dist" ], @@ -77,10 +78,13 @@ "@paypal/paypal-js": "^8.1.2 || ^9.0.0", "@speedcurve/lux": "^4.4.3", "@stripe/stripe-js": "^7.0.0 || ^8.0.0 || ^9.0.0", + "@types/geojson": "^7946.0.0", "@types/google.maps": "^3.58.1", + "@types/leaflet": "^1.9.0", "@types/vimeo__player": "^2.18.3", "@types/youtube": "^0.1.0", "@unhead/vue": "^2.0.3 || ^3.0.0", + "maplibre-gl": "^5.24.0", "posthog-js": "^1.0.0" }, "peerDependenciesMeta": { @@ -96,9 +100,15 @@ "@stripe/stripe-js": { "optional": true }, + "@types/geojson": { + "optional": true + }, "@types/google.maps": { "optional": true }, + "@types/leaflet": { + "optional": true + }, "@types/vimeo__player": { "optional": true }, @@ -108,6 +118,9 @@ "@unhead/vue": { "optional": true }, + "maplibre-gl": { + "optional": true + }, "posthog-js": { "optional": true } diff --git a/packages/script/src/registry-logos.ts b/packages/script/src/registry-logos.ts index 780d4d93f..af67135e6 100644 --- a/packages/script/src/registry-logos.ts +++ b/packages/script/src/registry-logos.ts @@ -43,6 +43,8 @@ export const LOGOS = { vimeoPlayer: ``, youtubePlayer: ``, googleMaps: ``, + leaflet: ``, + maplibre: ``, blueskyEmbed: ``, instagramEmbed: ``, xEmbed: { diff --git a/packages/script/src/registry-types.json b/packages/script/src/registry-types.json index 7a9da743a..a80b376b5 100644 --- a/packages/script/src/registry-types.json +++ b/packages/script/src/registry-types.json @@ -358,7 +358,17 @@ { "name": "ScriptGoogleMapsProps", "kind": "interface", - "code": "interface ScriptGoogleMapsProps ScriptGoogleMapsProps" + "code": "interface ScriptGoogleMapsProps {\n /**\n * Defines the trigger event to load the script.\n * @default ['mouseenter', 'mouseover', 'mousedown']\n */\n trigger?: ElementScriptTrigger\n /**\n * Defines the Google Maps API key. Must have access to the Static Maps API as well.\n */\n apiKey?: string\n /**\n * A latitude / longitude of where to focus the map.\n *\n * @deprecated Pass `center` via `mapOptions` instead. The top-level `center`\n * prop will be removed in a future major version. When both are set,\n * `mapOptions.center` wins.\n * @see https://scripts.nuxt.com/docs/migration-guide/v0-to-v1\n */\n center?: google.maps.LatLng | google.maps.LatLngLiteral | `${string},${string}`\n /**\n * Zoom level for the map (0-21). Reactive: changing this will update the map.\n *\n * @deprecated Pass `zoom` via `mapOptions` instead. The top-level `zoom`\n * prop will be removed in a future major version. When both are set,\n * `mapOptions.zoom` wins.\n * @see https://scripts.nuxt.com/docs/migration-guide/v0-to-v1\n */\n zoom?: number\n /**\n * Options for the map.\n */\n mapOptions?: google.maps.MapOptions\n /**\n * Defines the region of the map.\n */\n region?: string\n /**\n * Defines the language of the map.\n */\n language?: string\n /**\n * Defines the version of google maps js API.\n */\n version?: string\n /**\n * Defines the width of the map.\n * @default 640\n */\n width?: number | string\n /**\n * Defines the height of the map.\n * @default 400\n */\n height?: number | string\n /**\n * Customize the root element attributes.\n */\n rootAttrs?: HTMLAttributes & ReservedProps & Record\n /**\n * Map IDs for light and dark color modes.\n * When provided, the map will automatically switch styles based on color mode.\n * Requires @nuxtjs/color-mode or manual colorMode prop.\n */\n mapIds?: { light?: string, dark?: string }\n /**\n * Manual color mode control. When provided, overrides auto-detection from @nuxtjs/color-mode.\n * Accepts 'light' or 'dark'.\n */\n colorMode?: 'light' | 'dark'\n}" + }, + { + "name": "ScriptGoogleMapsEvents", + "kind": "interface", + "code": "interface ScriptGoogleMapsEvents {\n ready: ScriptGoogleMapsExpose\n error: -\n}" + }, + { + "name": "ScriptGoogleMapsSlots", + "kind": "interface", + "code": "interface ScriptGoogleMapsSlots {\n default?: () => any\n loading?: () => any\n awaitingLoad?: () => any\n error?: () => any\n placeholder?: () => any\n}" }, { "name": "ScriptGoogleMapsCircleProps", @@ -433,7 +443,12 @@ { "name": "ScriptGoogleMapsOverlayViewProps", "kind": "interface", - "code": "interface ScriptGoogleMapsOverlayViewProps ScriptGoogleMapsOverlayViewProps" + "code": "interface ScriptGoogleMapsOverlayViewProps {\n /**\n * Geographic position for the overlay. Falls back to parent marker position if omitted.\n *\n * Accepts either a plain `LatLngLiteral` (`{ lat, lng }`) or a\n * `google.maps.LatLng` instance.\n * @see https://developers.google.com/maps/documentation/javascript/reference/overlay-view#OverlayView\n */\n position?: google.maps.LatLng | google.maps.LatLngLiteral\n /**\n * Initial open state for the uncontrolled mode (when `v-model:open` is not\n * bound). When omitted, the overlay opens on mount, matching v0 behaviour.\n *\n * Has no effect when `v-model:open` is used; pass an initial value to the\n * bound ref instead.\n * @default true\n */\n defaultOpen?: boolean\n /**\n * Anchor point of the overlay relative to its position.\n * @default 'bottom-center'\n */\n anchor?: ScriptGoogleMapsOverlayAnchor\n /**\n * Pixel offset from the anchor position.\n */\n offset?: { x: number, y: number }\n /**\n * The map pane on which to render the overlay.\n * @default 'floatPane'\n * @see https://developers.google.com/maps/documentation/javascript/reference/overlay-view#MapPanes\n */\n pane?: ScriptGoogleMapsOverlayPane\n /**\n * CSS z-index for the overlay element.\n */\n zIndex?: number\n /**\n * Whether to block map click and gesture events from passing through the overlay.\n * @default true\n */\n blockMapInteraction?: boolean\n /**\n * Pan the map so the overlay is fully visible when opened, similar to InfoWindow behavior.\n * Set to `true` for default 40px padding, or a number for custom padding.\n * @default true\n */\n panOnOpen?: boolean | number\n /**\n * Automatically hide the overlay when its parent marker joins a cluster (on zoom out).\n * Only applies when nested inside a ScriptGoogleMapsMarkerClusterer.\n * @default true\n */\n hideWhenClustered?: boolean\n}" + }, + { + "name": "ScriptGoogleMapsOverlayViewSlots", + "kind": "interface", + "code": "interface ScriptGoogleMapsOverlayViewSlots {\n default?: () => any\n}" }, { "name": "ScriptGoogleMapsPolygonProps", @@ -650,6 +665,83 @@ "code": "interface ScriptIntercomSlots {\n default?: (props: { ready: boolean }) => any\n awaitingLoad?: () => any\n loading?: () => any\n error?: () => any\n}" } ], + "leaflet": [ + { + "name": "LeafletOptions", + "kind": "const", + "code": "export const LeafletOptions = object({\n /**\n * Inject the Leaflet 1.9.4 styles and default marker images when the script\n * begins loading. Disable this when supplying your own Leaflet stylesheet.\n * @default true\n */\n injectStyles: optional(boolean()),\n})" + }, + { + "name": "LeafletApi", + "kind": "interface", + "code": "export interface LeafletApi {\n L: typeof Leaflet\n}" + }, + { + "name": "ScriptLeafletGeoJsonProps", + "kind": "interface", + "code": "interface ScriptLeafletGeoJsonProps {\n /** GeoJSON object, feature, or feature collection. Replace it to update the layer. */\n data: GeoJsonObject | GeoJsonObject[]\n /** Options passed to `L.geoJSON`. */\n options?: Leaflet.GeoJSONOptions\n}" + }, + { + "name": "ScriptLeafletGeoJsonEvents", + "kind": "interface", + "code": "interface ScriptLeafletGeoJsonEvents {\n click: Leaflet.LeafletMouseEvent\n dblclick: Leaflet.LeafletMouseEvent\n mousedown: Leaflet.LeafletMouseEvent\n mouseover: Leaflet.LeafletMouseEvent\n mouseout: Leaflet.LeafletMouseEvent\n contextmenu: Leaflet.LeafletMouseEvent\n layeradd: Leaflet.LayerEvent\n layerremove: Leaflet.LayerEvent\n}" + }, + { + "name": "ScriptLeafletMapProps", + "kind": "interface", + "code": "interface ScriptLeafletMapProps {\n /**\n * Defines when the Leaflet script loads.\n * @default 'visible'\n */\n trigger?: ElementScriptTrigger\n /** Initial and reactively controlled map center. */\n center: Leaflet.LatLngExpression\n /** Initial and reactively controlled zoom level. @default 13 */\n zoom?: number\n /** Options passed to `L.map`. `center` and `zoom` use their dedicated props. */\n options?: Leaflet.MapOptions\n /** Inject Leaflet's styles and embedded default marker images. @default true */\n injectStyles?: boolean\n /** Width reserved before the map loads. @default 640 */\n width?: number | string\n /** Height reserved before the map loads. @default 400 */\n height?: number | string\n /** Accessible name for an interactive map. @default 'Interactive map' */\n ariaLabel?: string\n /** Disable map input and remove it from the accessibility tree when decorative. @default true */\n interactive?: boolean\n /** Attributes applied to the outer layout container. */\n rootAttrs?: HTMLAttributes & ReservedProps & Record\n}" + }, + { + "name": "ScriptLeafletMapEvents", + "kind": "interface", + "code": "interface ScriptLeafletMapEvents {\n ready: ScriptLeafletMapExpose\n error: Error\n click: Leaflet.LeafletMouseEvent\n move: Leaflet.LeafletEvent\n moveend: Leaflet.LeafletEvent\n zoom: Leaflet.LeafletEvent\n zoomend: Leaflet.LeafletEvent\n update:center: Leaflet.LatLng\n update:zoom: number\n}" + }, + { + "name": "ScriptLeafletMapSlots", + "kind": "interface", + "code": "interface ScriptLeafletMapSlots {\n default?: () => any\n loading?: () => any\n awaitingLoad?: () => any\n error?: (props: { error: Error }) => any\n placeholder?: () => any\n}" + }, + { + "name": "ScriptLeafletMarkerProps", + "kind": "interface", + "code": "interface ScriptLeafletMarkerProps {\n /** Reactive marker position. */\n position: Leaflet.LatLngExpression\n /** Unique text alternative for the marker icon. */\n alt?: string\n /** Tooltip text for the marker icon. */\n title?: string\n /** Options passed to `L.marker`. */\n options?: Leaflet.MarkerOptions\n}" + }, + { + "name": "ScriptLeafletMarkerEvents", + "kind": "interface", + "code": "interface ScriptLeafletMarkerEvents {\n click: Leaflet.LeafletMouseEvent\n dblclick: Leaflet.LeafletMouseEvent\n mousedown: Leaflet.LeafletMouseEvent\n mouseover: Leaflet.LeafletMouseEvent\n mouseout: Leaflet.LeafletMouseEvent\n contextmenu: Leaflet.LeafletMouseEvent\n dragstart: Leaflet.LeafletEvent\n drag: Leaflet.LeafletEvent\n dragend: Leaflet.DragEndEvent\n move: Leaflet.LeafletEvent\n}" + }, + { + "name": "ScriptLeafletMarkerSlots", + "kind": "interface", + "code": "interface ScriptLeafletMarkerSlots {\n default?: () => any\n}" + }, + { + "name": "ScriptLeafletPopupProps", + "kind": "interface", + "code": "interface ScriptLeafletPopupProps {\n /** Position for a standalone popup. Omit when nested inside a marker. */\n position?: Leaflet.LatLngExpression\n /** Whether the popup is open. @default false */\n open?: boolean\n /** Options passed to `L.popup`. */\n options?: Leaflet.PopupOptions\n}" + }, + { + "name": "ScriptLeafletPopupEvents", + "kind": "interface", + "code": "interface ScriptLeafletPopupEvents {\n open: Leaflet.LeafletEvent\n close: Leaflet.LeafletEvent\n}" + }, + { + "name": "ScriptLeafletPopupSlots", + "kind": "interface", + "code": "interface ScriptLeafletPopupSlots {\n default?: () => any\n}" + }, + { + "name": "ScriptLeafletTileLayerProps", + "kind": "interface", + "code": "interface ScriptLeafletTileLayerProps {\n /** Tile URL template, for example `https://tile.openstreetmap.org/{z}/{x}/{y}.png`. */\n url: string\n /** Tile layer options. Include the provider's required `attribution`. */\n options?: Leaflet.TileLayerOptions\n}" + }, + { + "name": "ScriptLeafletTileLayerEvents", + "kind": "interface", + "code": "interface ScriptLeafletTileLayerEvents {\n loading: Leaflet.LeafletEvent\n load: Leaflet.LeafletEvent\n tileloadstart: Leaflet.TileEvent\n tileload: Leaflet.TileEvent\n tileabort: Leaflet.TileEvent\n tileerror: Leaflet.TileErrorEvent\n}" + } + ], "lemon-squeezy": [ { "name": "LemonSqueezyEventPayload", @@ -704,6 +796,73 @@ "code": "export interface LinkedInInsightApi {\n lintrk: LintrkFns & { q?: unknown[] }\n}" } ], + "maplibre": [ + { + "name": "MapLibreOptions", + "kind": "const", + "code": "export const MapLibreOptions = object({\n /**\n * Inject the MapLibre GL JS 5.24.0 stylesheet when the script begins\n * loading. Disable this when supplying the stylesheet through Nuxt.\n * @default true\n */\n injectStyles: optional(boolean()),\n /**\n * Stylesheet URL used when `injectStyles` is enabled.\n * @default 'https://unpkg.com/maplibre-gl@5.24.0/dist/maplibre-gl.css'\n */\n stylesheetUrl: optional(string()),\n /**\n * Worker URL for the CSP-compatible MapLibre build. Pair this with a custom\n * `scriptInput.src` that loads `maplibre-gl-csp.js`.\n */\n workerUrl: optional(string()),\n})" + }, + { + "name": "MapLibreApi", + "kind": "interface", + "code": "export interface MapLibreApi {\n maplibregl: typeof MapLibre\n}" + }, + { + "name": "ScriptMapLibreGeoJsonProps", + "kind": "interface", + "code": "interface ScriptMapLibreGeoJsonProps {\n /** MapLibre source ID. Changing it rebuilds the owned source and layers. */\n sourceId: string\n /** Inline GeoJSON data or a URL returning GeoJSON. */\n data: GeoJSON | string\n /** GeoJSON source options. `type` and `data` are supplied by the component. */\n sourceOptions?: Omit\n /** Style layers backed by this source. */\n layers: ScriptMapLibreGeoJsonLayer[]\n /** Existing layer ID before which the layers are inserted. */\n beforeId?: string\n}" + }, + { + "name": "ScriptMapLibreMapProps", + "kind": "interface", + "code": "interface ScriptMapLibreMapProps {\n /**\n * Defines when the MapLibre script loads.\n * @default 'visible'\n */\n trigger?: ElementScriptTrigger\n /** MapLibre style URL or inline style specification. */\n mapStyle: string | MapLibre.StyleSpecification\n /** Initial and reactively controlled map center. */\n center: MapLibre.LngLatLike\n /** Initial and reactively controlled zoom level. @default 12 */\n zoom?: number\n /** Initial and reactively controlled bearing in degrees. @default 0 */\n bearing?: number\n /** Initial and reactively controlled pitch in degrees. @default 0 */\n pitch?: number\n /** Options passed to `new maplibregl.Map()`. Dedicated props take precedence. */\n options?: Omit\n /** Inject MapLibre's stylesheet when the script begins loading. @default true */\n injectStyles?: boolean\n /** Custom MapLibre stylesheet URL. */\n stylesheetUrl?: string\n /** Worker URL used with MapLibre's CSP-compatible build. */\n workerUrl?: string\n /** Width reserved before the map loads. @default 640 */\n width?: number | string\n /** Height reserved before the map loads. @default 400 */\n height?: number | string\n /** Accessible name for an interactive map. @default 'Interactive map' */\n ariaLabel?: string\n /** Disable map input and remove it from the accessibility tree when decorative. @default true */\n interactive?: boolean\n /** Attributes applied to the outer layout container. */\n rootAttrs?: HTMLAttributes & ReservedProps & Record\n}" + }, + { + "name": "ScriptMapLibreMapEvents", + "kind": "interface", + "code": "interface ScriptMapLibreMapEvents {\n ready: ScriptMapLibreMapExpose\n error: Error\n click: MapLibre.MapEventType['click']\n move: MapLibre.MapEventType['move']\n moveend: MapLibre.MapEventType['moveend']\n zoom: MapLibre.MapEventType['zoom']\n zoomend: MapLibre.MapEventType['zoomend']\n rotate: MapLibre.MapEventType['rotate']\n rotateend: MapLibre.MapEventType['rotateend']\n pitch: MapLibre.MapEventType['pitch']\n pitchend: MapLibre.MapEventType['pitchend']\n update:center: MapLibre.LngLat\n update:zoom: number\n update:bearing: number\n update:pitch: number\n}" + }, + { + "name": "ScriptMapLibreMapSlots", + "kind": "interface", + "code": "interface ScriptMapLibreMapSlots {\n default?: () => any\n loading?: () => any\n awaitingLoad?: () => any\n error?: (props: { error: Error }) => any\n placeholder?: () => any\n description?: () => any\n}" + }, + { + "name": "ScriptMapLibreMarkerProps", + "kind": "interface", + "code": "interface ScriptMapLibreMarkerProps {\n /** Reactive marker position in `[longitude, latitude]` order. */\n position: MapLibre.LngLatLike\n /** Accessible name for the marker element. */\n ariaLabel?: string\n /** Tooltip text for the marker element. */\n title?: string\n /** Options passed to `new maplibregl.Marker()`. Options with public setters also update reactively. */\n options?: MapLibre.MarkerOptions\n}" + }, + { + "name": "ScriptMapLibreMarkerEvents", + "kind": "interface", + "code": "interface ScriptMapLibreMarkerEvents {\n click: MouseEvent\n dragstart: MapLibre.Event\n drag: MapLibre.Event\n dragend: MapLibre.Event\n}" + }, + { + "name": "ScriptMapLibreMarkerSlots", + "kind": "interface", + "code": "interface ScriptMapLibreMarkerSlots {\n default?: () => any\n}" + }, + { + "name": "ScriptMapLibreNavigationControlProps", + "kind": "interface", + "code": "interface ScriptMapLibreNavigationControlProps {\n /** Position of the navigation control. */\n position?: MapLibre.ControlPosition\n /** Options passed to `new maplibregl.NavigationControl()`. */\n options?: MapLibre.NavigationControlOptions\n}" + }, + { + "name": "ScriptMapLibrePopupProps", + "kind": "interface", + "code": "interface ScriptMapLibrePopupProps {\n /** Position for a standalone popup. Omit when nested inside a marker. */\n position?: MapLibre.LngLatLike\n /** Whether the popup is open. @default false */\n open?: boolean\n /** Options passed to `new maplibregl.Popup()`. Options with public setters also update reactively. */\n options?: MapLibre.PopupOptions\n}" + }, + { + "name": "ScriptMapLibrePopupEvents", + "kind": "interface", + "code": "interface ScriptMapLibrePopupEvents {\n open: MapLibre.Event\n close: MapLibre.Event\n}" + }, + { + "name": "ScriptMapLibrePopupSlots", + "kind": "interface", + "code": "interface ScriptMapLibrePopupSlots {\n default?: () => any\n}" + } + ], "matomo-analytics": [ { "name": "MatomoAnalyticsOptions", @@ -1292,6 +1451,37 @@ "required": false } ], + "LeafletOptions": [ + { + "name": "injectStyles", + "type": "boolean", + "required": false, + "description": "Inject the Leaflet 1.9.4 styles and default marker images when the script begins loading. Disable this when supplying your own Leaflet stylesheet.", + "defaultValue": "true" + } + ], + "MapLibreOptions": [ + { + "name": "injectStyles", + "type": "boolean", + "required": false, + "description": "Inject the MapLibre GL JS 5.24.0 stylesheet when the script begins loading. Disable this when supplying the stylesheet through Nuxt.", + "defaultValue": "true" + }, + { + "name": "stylesheetUrl", + "type": "string", + "required": false, + "description": "Stylesheet URL used when `injectStyles` is enabled.", + "defaultValue": "'https://unpkg.com/maplibre-gl@5.24.0/dist/maplibre-gl.css'" + }, + { + "name": "workerUrl", + "type": "string", + "required": false, + "description": "Worker URL for the CSP-compatible MapLibre build. Pair this with a custom `scriptInput.src` that loads `maplibre-gl-csp.js`." + } + ], "AhrefsAnalyticsOptions": [ { "name": "key", @@ -2669,6 +2859,135 @@ "defaultValue": "'g'" } ], + "ScriptGoogleMapsProps": [ + { + "name": "trigger", + "type": "ElementScriptTrigger", + "required": false, + "description": "Defines the trigger event to load the script.", + "defaultValue": "['mouseenter', 'mouseover', 'mousedown']" + }, + { + "name": "apiKey", + "type": "string", + "required": false, + "description": "Defines the Google Maps API key. Must have access to the Static Maps API as well." + }, + { + "name": "center", + "type": "google.maps.LatLng | google.maps.LatLngLiteral | `${string},${string}`", + "required": false, + "description": "A latitude / longitude of where to focus the map. prop will be removed in a future major version. When both are set, `mapOptions.center` wins." + }, + { + "name": "zoom", + "type": "number", + "required": false, + "description": "Zoom level for the map (0-21). Reactive: changing this will update the map. prop will be removed in a future major version. When both are set, `mapOptions.zoom` wins." + }, + { + "name": "mapOptions", + "type": "google.maps.MapOptions", + "required": false, + "description": "Options for the map." + }, + { + "name": "region", + "type": "string", + "required": false, + "description": "Defines the region of the map." + }, + { + "name": "language", + "type": "string", + "required": false, + "description": "Defines the language of the map." + }, + { + "name": "version", + "type": "string", + "required": false, + "description": "Defines the version of google maps js API." + }, + { + "name": "width", + "type": "number | string", + "required": false, + "description": "Defines the width of the map.", + "defaultValue": "640" + }, + { + "name": "height", + "type": "number | string", + "required": false, + "description": "Defines the height of the map.", + "defaultValue": "400" + }, + { + "name": "rootAttrs", + "type": "HTMLAttributes & ReservedProps & Record", + "required": false, + "description": "Customize the root element attributes." + }, + { + "name": "mapIds", + "type": "{ light?: string, dark?: string }", + "required": false, + "description": "Map IDs for light and dark color modes. When provided, the map will automatically switch styles based on color mode. Requires @nuxtjs/color-mode or manual colorMode prop." + }, + { + "name": "colorMode", + "type": "'light' | 'dark'", + "required": false, + "description": "Manual color mode control. When provided, overrides auto-detection from @nuxtjs/color-mode. Accepts 'light' or 'dark'." + } + ], + "ScriptGoogleMapsEvents": [ + { + "name": "ready", + "type": "ScriptGoogleMapsExpose", + "required": false, + "description": "Fired when the Google Maps instance is fully loaded and ready to use. Provides access to the maps API." + }, + { + "name": "error", + "type": "-", + "required": false, + "description": "Fired when the Google Maps script fails to load." + } + ], + "ScriptGoogleMapsSlots": [ + { + "name": "default", + "type": "-", + "required": false, + "description": "Default slot for rendering child components (e.g. markers, info windows) that depend on the map being ready." + }, + { + "name": "loading", + "type": "-", + "required": false, + "description": "Slot displayed while the map is loading. Can be used to show a custom loading indicator." + }, + { + "name": "awaitingLoad", + "type": "-", + "required": false, + "description": "Slot displayed when the script is awaiting user interaction to load (based on the `trigger` prop)." + }, + { + "name": "error", + "type": "-", + "required": false, + "description": "Slot displayed if the script fails to load." + }, + { + "name": "placeholder", + "type": "-", + "required": false, + "description": "Slot displayed as a placeholder before the map is ready. Useful for showing a static map or skeleton." + } + ], "ScriptGoogleMapsCircleProps": [ { "name": "options", @@ -3023,12 +3342,79 @@ } ], "ScriptGoogleMapsOverlayViewProps": [ + { + "name": "position", + "type": "google.maps.LatLng | google.maps.LatLngLiteral", + "required": false, + "description": "Geographic position for the overlay. Falls back to parent marker position if omitted. Accepts either a plain `LatLngLiteral` (`{ lat, lng }`) or a `google.maps.LatLng` instance." + }, + { + "name": "defaultOpen", + "type": "boolean", + "required": false, + "description": "Initial open state for the uncontrolled mode (when `v-model:open` is not bound). When omitted, the overlay opens on mount, matching v0 behaviour. Has no effect when `v-model:open` is used; pass an initial value to the bound ref instead.", + "defaultValue": "true" + }, + { + "name": "anchor", + "type": "ScriptGoogleMapsOverlayAnchor", + "required": false, + "description": "Anchor point of the overlay relative to its position.", + "defaultValue": "'bottom-center'" + }, + { + "name": "offset", + "type": "{ x: number, y: number }", + "required": false, + "description": "Pixel offset from the anchor position." + }, + { + "name": "pane", + "type": "ScriptGoogleMapsOverlayPane", + "required": false, + "description": "The map pane on which to render the overlay.", + "defaultValue": "'floatPane'" + }, + { + "name": "zIndex", + "type": "number", + "required": false, + "description": "CSS z-index for the overlay element." + }, + { + "name": "blockMapInteraction", + "type": "boolean", + "required": false, + "description": "Whether to block map click and gesture events from passing through the overlay.", + "defaultValue": "true" + }, + { + "name": "panOnOpen", + "type": "boolean | number", + "required": false, + "description": "Pan the map so the overlay is fully visible when opened, similar to InfoWindow behavior. Set to `true` for default 40px padding, or a number for custom padding.", + "defaultValue": "true" + }, + { + "name": "hideWhenClustered", + "type": "boolean", + "required": false, + "description": "Automatically hide the overlay when its parent marker joins a cluster (on zoom out). Only applies when nested inside a ScriptGoogleMapsMarkerClusterer.", + "defaultValue": "true" + }, { "name": "v-model:open", "type": "boolean", "required": false } ], + "ScriptGoogleMapsOverlayViewSlots": [ + { + "name": "default", + "type": "-", + "required": false + } + ], "ScriptGoogleMapsPolygonProps": [ { "name": "options", @@ -3263,6 +3649,663 @@ "description": "Fired when the DOM mouseup event is fired on the rectangle." } ], + "ScriptLeafletGeoJsonProps": [ + { + "name": "data", + "type": "GeoJsonObject | GeoJsonObject[]", + "required": true + }, + { + "name": "options", + "type": "Leaflet.GeoJSONOptions", + "required": false + } + ], + "ScriptLeafletGeoJsonEvents": [ + { + "name": "click", + "type": "Leaflet.LeafletMouseEvent", + "required": false + }, + { + "name": "dblclick", + "type": "Leaflet.LeafletMouseEvent", + "required": false + }, + { + "name": "mousedown", + "type": "Leaflet.LeafletMouseEvent", + "required": false + }, + { + "name": "mouseover", + "type": "Leaflet.LeafletMouseEvent", + "required": false + }, + { + "name": "mouseout", + "type": "Leaflet.LeafletMouseEvent", + "required": false + }, + { + "name": "contextmenu", + "type": "Leaflet.LeafletMouseEvent", + "required": false + }, + { + "name": "layeradd", + "type": "Leaflet.LayerEvent", + "required": false + }, + { + "name": "layerremove", + "type": "Leaflet.LayerEvent", + "required": false + } + ], + "ScriptLeafletMapProps": [ + { + "name": "trigger", + "type": "ElementScriptTrigger", + "required": false, + "description": "Defines when the Leaflet script loads.", + "defaultValue": "'visible'" + }, + { + "name": "center", + "type": "Leaflet.LatLngExpression", + "required": true + }, + { + "name": "zoom", + "type": "number", + "required": false + }, + { + "name": "options", + "type": "Leaflet.MapOptions", + "required": false + }, + { + "name": "injectStyles", + "type": "boolean", + "required": false + }, + { + "name": "width", + "type": "number | string", + "required": false + }, + { + "name": "height", + "type": "number | string", + "required": false + }, + { + "name": "ariaLabel", + "type": "string", + "required": false + }, + { + "name": "interactive", + "type": "boolean", + "required": false + }, + { + "name": "rootAttrs", + "type": "HTMLAttributes & ReservedProps & Record", + "required": false + } + ], + "ScriptLeafletMapEvents": [ + { + "name": "ready", + "type": "ScriptLeafletMapExpose", + "required": false + }, + { + "name": "error", + "type": "Error", + "required": false + }, + { + "name": "click", + "type": "Leaflet.LeafletMouseEvent", + "required": false + }, + { + "name": "move", + "type": "Leaflet.LeafletEvent", + "required": false + }, + { + "name": "moveend", + "type": "Leaflet.LeafletEvent", + "required": false + }, + { + "name": "zoom", + "type": "Leaflet.LeafletEvent", + "required": false + }, + { + "name": "zoomend", + "type": "Leaflet.LeafletEvent", + "required": false + }, + { + "name": "update:center", + "type": "Leaflet.LatLng", + "required": false + }, + { + "name": "update:zoom", + "type": "number", + "required": false + } + ], + "ScriptLeafletMapSlots": [ + { + "name": "default", + "type": "-", + "required": false + }, + { + "name": "loading", + "type": "-", + "required": false + }, + { + "name": "awaitingLoad", + "type": "-", + "required": false + }, + { + "name": "error", + "type": "{ error: Error }", + "required": false + }, + { + "name": "placeholder", + "type": "-", + "required": false + } + ], + "ScriptLeafletMarkerProps": [ + { + "name": "position", + "type": "Leaflet.LatLngExpression", + "required": true + }, + { + "name": "alt", + "type": "string", + "required": false + }, + { + "name": "title", + "type": "string", + "required": false + }, + { + "name": "options", + "type": "Leaflet.MarkerOptions", + "required": false + } + ], + "ScriptLeafletMarkerEvents": [ + { + "name": "click", + "type": "Leaflet.LeafletMouseEvent", + "required": false + }, + { + "name": "dblclick", + "type": "Leaflet.LeafletMouseEvent", + "required": false + }, + { + "name": "mousedown", + "type": "Leaflet.LeafletMouseEvent", + "required": false + }, + { + "name": "mouseover", + "type": "Leaflet.LeafletMouseEvent", + "required": false + }, + { + "name": "mouseout", + "type": "Leaflet.LeafletMouseEvent", + "required": false + }, + { + "name": "contextmenu", + "type": "Leaflet.LeafletMouseEvent", + "required": false + }, + { + "name": "dragstart", + "type": "Leaflet.LeafletEvent", + "required": false + }, + { + "name": "drag", + "type": "Leaflet.LeafletEvent", + "required": false + }, + { + "name": "dragend", + "type": "Leaflet.DragEndEvent", + "required": false + }, + { + "name": "move", + "type": "Leaflet.LeafletEvent", + "required": false + } + ], + "ScriptLeafletMarkerSlots": [ + { + "name": "default", + "type": "-", + "required": false + } + ], + "ScriptLeafletPopupProps": [ + { + "name": "position", + "type": "Leaflet.LatLngExpression", + "required": false + }, + { + "name": "open", + "type": "boolean", + "required": false + }, + { + "name": "options", + "type": "Leaflet.PopupOptions", + "required": false + } + ], + "ScriptLeafletPopupEvents": [ + { + "name": "open", + "type": "Leaflet.LeafletEvent", + "required": false + }, + { + "name": "close", + "type": "Leaflet.LeafletEvent", + "required": false + } + ], + "ScriptLeafletPopupSlots": [ + { + "name": "default", + "type": "-", + "required": false + } + ], + "ScriptLeafletTileLayerProps": [ + { + "name": "url", + "type": "string", + "required": true + }, + { + "name": "options", + "type": "Leaflet.TileLayerOptions", + "required": false + } + ], + "ScriptLeafletTileLayerEvents": [ + { + "name": "loading", + "type": "Leaflet.LeafletEvent", + "required": false + }, + { + "name": "load", + "type": "Leaflet.LeafletEvent", + "required": false + }, + { + "name": "tileloadstart", + "type": "Leaflet.TileEvent", + "required": false + }, + { + "name": "tileload", + "type": "Leaflet.TileEvent", + "required": false + }, + { + "name": "tileabort", + "type": "Leaflet.TileEvent", + "required": false + }, + { + "name": "tileerror", + "type": "Leaflet.TileErrorEvent", + "required": false + } + ], + "ScriptMapLibreGeoJsonProps": [ + { + "name": "sourceId", + "type": "string", + "required": true + }, + { + "name": "data", + "type": "GeoJSON | string", + "required": true + }, + { + "name": "sourceOptions", + "type": "Omit", + "required": false + }, + { + "name": "layers", + "type": "ScriptMapLibreGeoJsonLayer[]", + "required": true + }, + { + "name": "beforeId", + "type": "string", + "required": false + } + ], + "ScriptMapLibreMapProps": [ + { + "name": "trigger", + "type": "ElementScriptTrigger", + "required": false, + "description": "Defines when the MapLibre script loads.", + "defaultValue": "'visible'" + }, + { + "name": "mapStyle", + "type": "string | MapLibre.StyleSpecification", + "required": true + }, + { + "name": "center", + "type": "MapLibre.LngLatLike", + "required": true + }, + { + "name": "zoom", + "type": "number", + "required": false + }, + { + "name": "bearing", + "type": "number", + "required": false + }, + { + "name": "pitch", + "type": "number", + "required": false + }, + { + "name": "options", + "type": "Omit", + "required": false + }, + { + "name": "injectStyles", + "type": "boolean", + "required": false + }, + { + "name": "stylesheetUrl", + "type": "string", + "required": false + }, + { + "name": "workerUrl", + "type": "string", + "required": false + }, + { + "name": "width", + "type": "number | string", + "required": false + }, + { + "name": "height", + "type": "number | string", + "required": false + }, + { + "name": "ariaLabel", + "type": "string", + "required": false + }, + { + "name": "interactive", + "type": "boolean", + "required": false + }, + { + "name": "rootAttrs", + "type": "HTMLAttributes & ReservedProps & Record", + "required": false + } + ], + "ScriptMapLibreMapEvents": [ + { + "name": "ready", + "type": "ScriptMapLibreMapExpose", + "required": false + }, + { + "name": "error", + "type": "Error", + "required": false + }, + { + "name": "click", + "type": "MapLibre.MapEventType['click']", + "required": false + }, + { + "name": "move", + "type": "MapLibre.MapEventType['move']", + "required": false + }, + { + "name": "moveend", + "type": "MapLibre.MapEventType['moveend']", + "required": false + }, + { + "name": "zoom", + "type": "MapLibre.MapEventType['zoom']", + "required": false + }, + { + "name": "zoomend", + "type": "MapLibre.MapEventType['zoomend']", + "required": false + }, + { + "name": "rotate", + "type": "MapLibre.MapEventType['rotate']", + "required": false + }, + { + "name": "rotateend", + "type": "MapLibre.MapEventType['rotateend']", + "required": false + }, + { + "name": "pitch", + "type": "MapLibre.MapEventType['pitch']", + "required": false + }, + { + "name": "pitchend", + "type": "MapLibre.MapEventType['pitchend']", + "required": false + }, + { + "name": "update:center", + "type": "MapLibre.LngLat", + "required": false + }, + { + "name": "update:zoom", + "type": "number", + "required": false + }, + { + "name": "update:bearing", + "type": "number", + "required": false + }, + { + "name": "update:pitch", + "type": "number", + "required": false + } + ], + "ScriptMapLibreMapSlots": [ + { + "name": "default", + "type": "-", + "required": false + }, + { + "name": "loading", + "type": "-", + "required": false + }, + { + "name": "awaitingLoad", + "type": "-", + "required": false + }, + { + "name": "error", + "type": "{ error: Error }", + "required": false + }, + { + "name": "placeholder", + "type": "-", + "required": false + }, + { + "name": "description", + "type": "-", + "required": false + } + ], + "ScriptMapLibreMarkerProps": [ + { + "name": "position", + "type": "MapLibre.LngLatLike", + "required": true + }, + { + "name": "ariaLabel", + "type": "string", + "required": false + }, + { + "name": "title", + "type": "string", + "required": false + }, + { + "name": "options", + "type": "MapLibre.MarkerOptions", + "required": false + } + ], + "ScriptMapLibreMarkerEvents": [ + { + "name": "click", + "type": "MouseEvent", + "required": false + }, + { + "name": "dragstart", + "type": "MapLibre.Event", + "required": false + }, + { + "name": "drag", + "type": "MapLibre.Event", + "required": false + }, + { + "name": "dragend", + "type": "MapLibre.Event", + "required": false + } + ], + "ScriptMapLibreMarkerSlots": [ + { + "name": "default", + "type": "-", + "required": false + } + ], + "ScriptMapLibreNavigationControlProps": [ + { + "name": "position", + "type": "MapLibre.ControlPosition", + "required": false + }, + { + "name": "options", + "type": "MapLibre.NavigationControlOptions", + "required": false + } + ], + "ScriptMapLibrePopupProps": [ + { + "name": "position", + "type": "MapLibre.LngLatLike", + "required": false + }, + { + "name": "open", + "type": "boolean", + "required": false + }, + { + "name": "options", + "type": "MapLibre.PopupOptions", + "required": false + } + ], + "ScriptMapLibrePopupEvents": [ + { + "name": "open", + "type": "MapLibre.Event", + "required": false + }, + { + "name": "close", + "type": "MapLibre.Event", + "required": false + } + ], + "ScriptMapLibrePopupSlots": [ + { + "name": "default", + "type": "-", + "required": false + } + ], "ScriptBlueskyEmbedProps": [ { "name": "postUrl", diff --git a/packages/script/src/registry.ts b/packages/script/src/registry.ts index 591d7889b..d7e6734fc 100644 --- a/packages/script/src/registry.ts +++ b/packages/script/src/registry.ts @@ -32,7 +32,9 @@ import { HotjarOptions, InstagramEmbedOptions, IntercomOptions, + LeafletOptions, LinkedInInsightOptions, + MapLibreOptions, MatomoAnalyticsOptions, MetaPixelOptions, MixpanelAnalyticsOptions, @@ -158,6 +160,8 @@ export const registryMeta: RegistryScriptMeta[] = [ m('vimeoPlayer', 'Vimeo Player', 'video', 'useScriptVimeoPlayer', { bundle: true, proxy: true }, PRIVACY_IP_ONLY), // content m('googleMaps', 'Google Maps', 'content', 'useScriptGoogleMaps', {}, null), + m('leaflet', 'Leaflet', 'content', 'useScriptLeaflet', { bundle: true }, null), + m('maplibre', 'MapLibre GL JS', 'content', 'useScriptMapLibre', { bundle: true }, null), m('instagramEmbed', 'Instagram Embed', 'content', false, {}, null), m('xEmbed', 'X Embed', 'content', false, {}, null), m('blueskyEmbed', 'Bluesky Embed', 'content', false, {}, null), @@ -695,6 +699,21 @@ export async function registry(resolve?: (path: string) => Promise): Pro { route: '/_scripts/proxy/google-maps-geocode', handler: './runtime/server/google-maps-geocode-proxy', requiresSigning: true }, ], }), + def('leaflet', { + schema: LeafletOptions, + label: 'Leaflet', + src: 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.js', + category: 'content', + bundle: true, + }), + def('maplibre', { + composableName: 'useScriptMapLibre', + schema: MapLibreOptions, + label: 'MapLibre GL JS', + src: 'https://unpkg.com/maplibre-gl@5.24.0/dist/maplibre-gl.js', + category: 'content', + bundle: true, + }), def('blueskyEmbed', { composableName: false, schema: BlueskyEmbedOptions, diff --git a/packages/script/src/runtime/components/Leaflet/ScriptLeafletGeoJson.vue b/packages/script/src/runtime/components/Leaflet/ScriptLeafletGeoJson.vue new file mode 100644 index 000000000..013e8e29c --- /dev/null +++ b/packages/script/src/runtime/components/Leaflet/ScriptLeafletGeoJson.vue @@ -0,0 +1,65 @@ + + + diff --git a/packages/script/src/runtime/components/Leaflet/ScriptLeafletMap.vue b/packages/script/src/runtime/components/Leaflet/ScriptLeafletMap.vue new file mode 100644 index 000000000..252eb0546 --- /dev/null +++ b/packages/script/src/runtime/components/Leaflet/ScriptLeafletMap.vue @@ -0,0 +1,245 @@ + + + + + diff --git a/packages/script/src/runtime/components/Leaflet/ScriptLeafletMarker.vue b/packages/script/src/runtime/components/Leaflet/ScriptLeafletMarker.vue new file mode 100644 index 000000000..bcb183b55 --- /dev/null +++ b/packages/script/src/runtime/components/Leaflet/ScriptLeafletMarker.vue @@ -0,0 +1,99 @@ + + + diff --git a/packages/script/src/runtime/components/Leaflet/ScriptLeafletPopup.vue b/packages/script/src/runtime/components/Leaflet/ScriptLeafletPopup.vue new file mode 100644 index 000000000..7fd71714a --- /dev/null +++ b/packages/script/src/runtime/components/Leaflet/ScriptLeafletPopup.vue @@ -0,0 +1,108 @@ + + + + + diff --git a/packages/script/src/runtime/components/Leaflet/ScriptLeafletTileLayer.vue b/packages/script/src/runtime/components/Leaflet/ScriptLeafletTileLayer.vue new file mode 100644 index 000000000..2e6039e69 --- /dev/null +++ b/packages/script/src/runtime/components/Leaflet/ScriptLeafletTileLayer.vue @@ -0,0 +1,56 @@ + + + diff --git a/packages/script/src/runtime/components/Leaflet/useLeafletResource.ts b/packages/script/src/runtime/components/Leaflet/useLeafletResource.ts new file mode 100644 index 000000000..77dac44b4 --- /dev/null +++ b/packages/script/src/runtime/components/Leaflet/useLeafletResource.ts @@ -0,0 +1,93 @@ +import type * as Leaflet from 'leaflet' +import type { InjectionKey, ShallowRef } from 'vue' +import { inject, onUnmounted, shallowRef, watch } from 'vue' + +export type LeafletNamespace = typeof Leaflet + +export interface LeafletMapContext { + map: ShallowRef + leaflet: ShallowRef +} + +export const LEAFLET_MAP_INJECTION_KEY = Symbol('leaflet-map') as InjectionKey + +export const LEAFLET_MARKER_INJECTION_KEY = Symbol('leaflet-marker') as InjectionKey<{ + marker: ShallowRef +}> + +export interface LeafletResourceContext { + map: Leaflet.Map + leaflet: LeafletNamespace +} + +type UnionToIntersection = (T extends unknown ? (value: T) => void : never) extends (value: infer I) => void ? I : never + +type LeafletEmit = UnionToIntersection<{ + [TEvent in keyof TEvents]: TEvents[TEvent] extends unknown[] + ? (event: TEvent, ...args: TEvents[TEvent]) => void + : never +}[keyof TEvents]> + +export function bindLeafletEvents( + target: Leaflet.Evented, + emit: LeafletEmit, + events: readonly (keyof TEvents & string)[], +): void { + const emitEvent = emit as (event: keyof TEvents & string, payload: Leaflet.LeafletEvent) => void + for (const event of events) + target.on(event, payload => emitEvent(event, payload)) +} + +/** + * Creates a Leaflet resource once its parent map and any component-specific + * DOM refs are ready, then removes it synchronously during unmount. + */ +export function useLeafletResource({ + ready, + create, + cleanup, +}: { + ready?: () => boolean + create: (context: LeafletResourceContext) => T + cleanup: (resource: T, context: LeafletResourceContext) => void +}): ShallowRef { + const mapContext = inject(LEAFLET_MAP_INJECTION_KEY, undefined) + const resource = shallowRef() as ShallowRef + let creating = false + let resourceContext: LeafletResourceContext | undefined + + if (import.meta.dev && !mapContext) { + console.warn('[nuxt-scripts] Leaflet child components must be placed inside .') + } + + const stop = watch( + () => [mapContext?.map.value, mapContext?.leaflet.value, ready?.() ?? true] as const, + ([map, leaflet, isReady]) => { + if (!map || !leaflet || !isReady || resource.value || creating) + return + + creating = true + try { + resourceContext = { map, leaflet } + resource.value = create(resourceContext) + } + catch (error) { + console.error('[nuxt-scripts] Leaflet resource creation failed:', error) + } + finally { + creating = false + } + }, + { immediate: true }, + ) + + onUnmounted(() => { + stop() + if (resource.value && resourceContext) + cleanup(resource.value, resourceContext) + resource.value = undefined + resourceContext = undefined + }) + + return resource +} diff --git a/packages/script/src/runtime/components/MapLibre/ScriptMapLibreGeoJson.vue b/packages/script/src/runtime/components/MapLibre/ScriptMapLibreGeoJson.vue new file mode 100644 index 000000000..5f4c1fe2c --- /dev/null +++ b/packages/script/src/runtime/components/MapLibre/ScriptMapLibreGeoJson.vue @@ -0,0 +1,106 @@ + + + + + diff --git a/packages/script/src/runtime/components/MapLibre/ScriptMapLibreMap.vue b/packages/script/src/runtime/components/MapLibre/ScriptMapLibreMap.vue new file mode 100644 index 000000000..0642dc8f8 --- /dev/null +++ b/packages/script/src/runtime/components/MapLibre/ScriptMapLibreMap.vue @@ -0,0 +1,316 @@ + + + + + + + diff --git a/packages/script/src/runtime/components/MapLibre/ScriptMapLibreMarker.vue b/packages/script/src/runtime/components/MapLibre/ScriptMapLibreMarker.vue new file mode 100644 index 000000000..0211b67a7 --- /dev/null +++ b/packages/script/src/runtime/components/MapLibre/ScriptMapLibreMarker.vue @@ -0,0 +1,119 @@ + + + diff --git a/packages/script/src/runtime/components/MapLibre/ScriptMapLibreNavigationControl.vue b/packages/script/src/runtime/components/MapLibre/ScriptMapLibreNavigationControl.vue new file mode 100644 index 000000000..93bbe3623 --- /dev/null +++ b/packages/script/src/runtime/components/MapLibre/ScriptMapLibreNavigationControl.vue @@ -0,0 +1,29 @@ + + + diff --git a/packages/script/src/runtime/components/MapLibre/ScriptMapLibrePopup.vue b/packages/script/src/runtime/components/MapLibre/ScriptMapLibrePopup.vue new file mode 100644 index 000000000..732ee1ccd --- /dev/null +++ b/packages/script/src/runtime/components/MapLibre/ScriptMapLibrePopup.vue @@ -0,0 +1,125 @@ + + + + + diff --git a/packages/script/src/runtime/components/MapLibre/useMapLibreResource.ts b/packages/script/src/runtime/components/MapLibre/useMapLibreResource.ts new file mode 100644 index 000000000..55dafe083 --- /dev/null +++ b/packages/script/src/runtime/components/MapLibre/useMapLibreResource.ts @@ -0,0 +1,73 @@ +import type * as MapLibre from 'maplibre-gl' +import type { InjectionKey, ShallowRef } from 'vue' +import { inject, onUnmounted, shallowRef, watch } from 'vue' + +export interface MapLibreMapContext { + map: ShallowRef + maplibre: ShallowRef +} + +export const MAPLIBRE_MAP_INJECTION_KEY = Symbol('maplibre-map') as InjectionKey + +export const MAPLIBRE_MARKER_INJECTION_KEY = Symbol('maplibre-marker') as InjectionKey<{ + marker: ShallowRef +}> + +export interface MapLibreResourceContext { + map: MapLibre.Map + maplibre: typeof MapLibre +} + +/** + * Creates a MapLibre resource after its parent map and any component-specific + * DOM refs are ready, then removes it synchronously during unmount. + */ +export function useMapLibreResource({ + ready, + create, + cleanup, +}: { + ready?: () => boolean + create: (context: MapLibreResourceContext) => T + cleanup: (resource: T, context: MapLibreResourceContext) => void +}): ShallowRef { + const mapContext = inject(MAPLIBRE_MAP_INJECTION_KEY, undefined) + const resource = shallowRef() as ShallowRef + let creating = false + let resourceContext: MapLibreResourceContext | undefined + + if (import.meta.dev && !mapContext) { + console.warn('[nuxt-scripts] MapLibre child components must be placed inside .') + } + + const stop = watch( + () => [mapContext?.map.value, mapContext?.maplibre.value, ready?.() ?? true] as const, + ([map, maplibre, isReady]) => { + if (!map || !maplibre || !isReady || resource.value || creating) + return + + creating = true + try { + resourceContext = { map, maplibre } + resource.value = create(resourceContext) + } + catch (error) { + console.error('[nuxt-scripts] MapLibre resource creation failed:', error) + } + finally { + creating = false + } + }, + { immediate: true }, + ) + + onUnmounted(() => { + stop() + if (resource.value && resourceContext) + cleanup(resource.value, resourceContext) + resource.value = undefined + resourceContext = undefined + }) + + return resource +} diff --git a/packages/script/src/runtime/leaflet-styles.ts b/packages/script/src/runtime/leaflet-styles.ts new file mode 100644 index 000000000..e0a52c8ca --- /dev/null +++ b/packages/script/src/runtime/leaflet-styles.ts @@ -0,0 +1,37 @@ +import type * as Leaflet from 'leaflet' + +const LEAFLET_STYLE_ID = 'nuxt-scripts-leaflet-styles' + +/** + * Leaflet 1.9.4 styles with its referenced images embedded as data URIs. + * Leaflet is Copyright (c) 2010-2023 Vladimir Agafonkin and + * Copyright (c) 2010-2011 CloudMade, licensed under BSD-2-Clause. + * The complete notice is retained in packages/script/THIRD_PARTY_LICENSES.md. + */ +export const LEAFLET_CSS = '/* required styles */\r\n\r\n.leaflet-pane,\r\n.leaflet-tile,\r\n.leaflet-marker-icon,\r\n.leaflet-marker-shadow,\r\n.leaflet-tile-container,\r\n.leaflet-pane > svg,\r\n.leaflet-pane > canvas,\r\n.leaflet-zoom-box,\r\n.leaflet-image-layer,\r\n.leaflet-layer {\r\n\tposition: absolute;\r\n\tleft: 0;\r\n\ttop: 0;\r\n\t}\r\n.leaflet-container {\r\n\toverflow: hidden;\r\n\t}\r\n.leaflet-tile,\r\n.leaflet-marker-icon,\r\n.leaflet-marker-shadow {\r\n\t-webkit-user-select: none;\r\n\t -moz-user-select: none;\r\n\t user-select: none;\r\n\t -webkit-user-drag: none;\r\n\t}\r\n/* Prevents IE11 from highlighting tiles in blue */\r\n.leaflet-tile::selection {\r\n\tbackground: transparent;\r\n}\r\n/* Safari renders non-retina tile on retina better with this, but Chrome is worse */\r\n.leaflet-safari .leaflet-tile {\r\n\timage-rendering: -webkit-optimize-contrast;\r\n\t}\r\n/* hack that prevents hw layers "stretching" when loading new tiles */\r\n.leaflet-safari .leaflet-tile-container {\r\n\twidth: 1600px;\r\n\theight: 1600px;\r\n\t-webkit-transform-origin: 0 0;\r\n\t}\r\n.leaflet-marker-icon,\r\n.leaflet-marker-shadow {\r\n\tdisplay: block;\r\n\t}\r\n/* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */\r\n/* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */\r\n.leaflet-container .leaflet-overlay-pane svg {\r\n\tmax-width: none !important;\r\n\tmax-height: none !important;\r\n\t}\r\n.leaflet-container .leaflet-marker-pane img,\r\n.leaflet-container .leaflet-shadow-pane img,\r\n.leaflet-container .leaflet-tile-pane img,\r\n.leaflet-container img.leaflet-image-layer,\r\n.leaflet-container .leaflet-tile {\r\n\tmax-width: none !important;\r\n\tmax-height: none !important;\r\n\twidth: auto;\r\n\tpadding: 0;\r\n\t}\r\n\r\n.leaflet-container img.leaflet-tile {\r\n\t/* See: https://bugs.chromium.org/p/chromium/issues/detail?id=600120 */\r\n\tmix-blend-mode: plus-lighter;\r\n}\r\n\r\n.leaflet-container.leaflet-touch-zoom {\r\n\t-ms-touch-action: pan-x pan-y;\r\n\ttouch-action: pan-x pan-y;\r\n\t}\r\n.leaflet-container.leaflet-touch-drag {\r\n\t-ms-touch-action: pinch-zoom;\r\n\t/* Fallback for FF which doesn\'t support pinch-zoom */\r\n\ttouch-action: none;\r\n\ttouch-action: pinch-zoom;\r\n}\r\n.leaflet-container.leaflet-touch-drag.leaflet-touch-zoom {\r\n\t-ms-touch-action: none;\r\n\ttouch-action: none;\r\n}\r\n.leaflet-container {\r\n\t-webkit-tap-highlight-color: transparent;\r\n}\r\n.leaflet-container a {\r\n\t-webkit-tap-highlight-color: rgba(51, 181, 229, 0.4);\r\n}\r\n.leaflet-tile {\r\n\tfilter: inherit;\r\n\tvisibility: hidden;\r\n\t}\r\n.leaflet-tile-loaded {\r\n\tvisibility: inherit;\r\n\t}\r\n.leaflet-zoom-box {\r\n\twidth: 0;\r\n\theight: 0;\r\n\t-moz-box-sizing: border-box;\r\n\t box-sizing: border-box;\r\n\tz-index: 800;\r\n\t}\r\n/* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */\r\n.leaflet-overlay-pane svg {\r\n\t-moz-user-select: none;\r\n\t}\r\n\r\n.leaflet-pane { z-index: 400; }\r\n\r\n.leaflet-tile-pane { z-index: 200; }\r\n.leaflet-overlay-pane { z-index: 400; }\r\n.leaflet-shadow-pane { z-index: 500; }\r\n.leaflet-marker-pane { z-index: 600; }\r\n.leaflet-tooltip-pane { z-index: 650; }\r\n.leaflet-popup-pane { z-index: 700; }\r\n\r\n.leaflet-map-pane canvas { z-index: 100; }\r\n.leaflet-map-pane svg { z-index: 200; }\r\n\r\n.leaflet-vml-shape {\r\n\twidth: 1px;\r\n\theight: 1px;\r\n\t}\r\n.lvml {\r\n\tbehavior: url(#default#VML);\r\n\tdisplay: inline-block;\r\n\tposition: absolute;\r\n\t}\r\n\r\n\r\n/* control positioning */\r\n\r\n.leaflet-control {\r\n\tposition: relative;\r\n\tz-index: 800;\r\n\tpointer-events: visiblePainted; /* IE 9-10 doesn\'t have auto */\r\n\tpointer-events: auto;\r\n\t}\r\n.leaflet-top,\r\n.leaflet-bottom {\r\n\tposition: absolute;\r\n\tz-index: 1000;\r\n\tpointer-events: none;\r\n\t}\r\n.leaflet-top {\r\n\ttop: 0;\r\n\t}\r\n.leaflet-right {\r\n\tright: 0;\r\n\t}\r\n.leaflet-bottom {\r\n\tbottom: 0;\r\n\t}\r\n.leaflet-left {\r\n\tleft: 0;\r\n\t}\r\n.leaflet-control {\r\n\tfloat: left;\r\n\tclear: both;\r\n\t}\r\n.leaflet-right .leaflet-control {\r\n\tfloat: right;\r\n\t}\r\n.leaflet-top .leaflet-control {\r\n\tmargin-top: 10px;\r\n\t}\r\n.leaflet-bottom .leaflet-control {\r\n\tmargin-bottom: 10px;\r\n\t}\r\n.leaflet-left .leaflet-control {\r\n\tmargin-left: 10px;\r\n\t}\r\n.leaflet-right .leaflet-control {\r\n\tmargin-right: 10px;\r\n\t}\r\n\r\n\r\n/* zoom and fade animations */\r\n\r\n.leaflet-fade-anim .leaflet-popup {\r\n\topacity: 0;\r\n\t-webkit-transition: opacity 0.2s linear;\r\n\t -moz-transition: opacity 0.2s linear;\r\n\t transition: opacity 0.2s linear;\r\n\t}\r\n.leaflet-fade-anim .leaflet-map-pane .leaflet-popup {\r\n\topacity: 1;\r\n\t}\r\n.leaflet-zoom-animated {\r\n\t-webkit-transform-origin: 0 0;\r\n\t -ms-transform-origin: 0 0;\r\n\t transform-origin: 0 0;\r\n\t}\r\nsvg.leaflet-zoom-animated {\r\n\twill-change: transform;\r\n}\r\n\r\n.leaflet-zoom-anim .leaflet-zoom-animated {\r\n\t-webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1);\r\n\t -moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1);\r\n\t transition: transform 0.25s cubic-bezier(0,0,0.25,1);\r\n\t}\r\n.leaflet-zoom-anim .leaflet-tile,\r\n.leaflet-pan-anim .leaflet-tile {\r\n\t-webkit-transition: none;\r\n\t -moz-transition: none;\r\n\t transition: none;\r\n\t}\r\n\r\n.leaflet-zoom-anim .leaflet-zoom-hide {\r\n\tvisibility: hidden;\r\n\t}\r\n\r\n\r\n/* cursors */\r\n\r\n.leaflet-interactive {\r\n\tcursor: pointer;\r\n\t}\r\n.leaflet-grab {\r\n\tcursor: -webkit-grab;\r\n\tcursor: -moz-grab;\r\n\tcursor: grab;\r\n\t}\r\n.leaflet-crosshair,\r\n.leaflet-crosshair .leaflet-interactive {\r\n\tcursor: crosshair;\r\n\t}\r\n.leaflet-popup-pane,\r\n.leaflet-control {\r\n\tcursor: auto;\r\n\t}\r\n.leaflet-dragging .leaflet-grab,\r\n.leaflet-dragging .leaflet-grab .leaflet-interactive,\r\n.leaflet-dragging .leaflet-marker-draggable {\r\n\tcursor: move;\r\n\tcursor: -webkit-grabbing;\r\n\tcursor: -moz-grabbing;\r\n\tcursor: grabbing;\r\n\t}\r\n\r\n/* marker & overlays interactivity */\r\n.leaflet-marker-icon,\r\n.leaflet-marker-shadow,\r\n.leaflet-image-layer,\r\n.leaflet-pane > svg path,\r\n.leaflet-tile-container {\r\n\tpointer-events: none;\r\n\t}\r\n\r\n.leaflet-marker-icon.leaflet-interactive,\r\n.leaflet-image-layer.leaflet-interactive,\r\n.leaflet-pane > svg path.leaflet-interactive,\r\nsvg.leaflet-image-layer.leaflet-interactive path {\r\n\tpointer-events: visiblePainted; /* IE 9-10 doesn\'t have auto */\r\n\tpointer-events: auto;\r\n\t}\r\n\r\n/* visual tweaks */\r\n\r\n.leaflet-container {\r\n\tbackground: #ddd;\r\n\toutline-offset: 1px;\r\n\t}\r\n.leaflet-container a {\r\n\tcolor: #0078A8;\r\n\t}\r\n.leaflet-zoom-box {\r\n\tborder: 2px dotted #38f;\r\n\tbackground: rgba(255,255,255,0.5);\r\n\t}\r\n\r\n\r\n/* general typography */\r\n.leaflet-container {\r\n\tfont-family: "Helvetica Neue", Arial, Helvetica, sans-serif;\r\n\tfont-size: 12px;\r\n\tfont-size: 0.75rem;\r\n\tline-height: 1.5;\r\n\t}\r\n\r\n\r\n/* general toolbar styles */\r\n\r\n.leaflet-bar {\r\n\tbox-shadow: 0 1px 5px rgba(0,0,0,0.65);\r\n\tborder-radius: 4px;\r\n\t}\r\n.leaflet-bar a {\r\n\tbackground-color: #fff;\r\n\tborder-bottom: 1px solid #ccc;\r\n\twidth: 26px;\r\n\theight: 26px;\r\n\tline-height: 26px;\r\n\tdisplay: block;\r\n\ttext-align: center;\r\n\ttext-decoration: none;\r\n\tcolor: black;\r\n\t}\r\n.leaflet-bar a,\r\n.leaflet-control-layers-toggle {\r\n\tbackground-position: 50% 50%;\r\n\tbackground-repeat: no-repeat;\r\n\tdisplay: block;\r\n\t}\r\n.leaflet-bar a:hover,\r\n.leaflet-bar a:focus {\r\n\tbackground-color: #f4f4f4;\r\n\t}\r\n.leaflet-bar a:first-child {\r\n\tborder-top-left-radius: 4px;\r\n\tborder-top-right-radius: 4px;\r\n\t}\r\n.leaflet-bar a:last-child {\r\n\tborder-bottom-left-radius: 4px;\r\n\tborder-bottom-right-radius: 4px;\r\n\tborder-bottom: none;\r\n\t}\r\n.leaflet-bar a.leaflet-disabled {\r\n\tcursor: default;\r\n\tbackground-color: #f4f4f4;\r\n\tcolor: #bbb;\r\n\t}\r\n\r\n.leaflet-touch .leaflet-bar a {\r\n\twidth: 30px;\r\n\theight: 30px;\r\n\tline-height: 30px;\r\n\t}\r\n.leaflet-touch .leaflet-bar a:first-child {\r\n\tborder-top-left-radius: 2px;\r\n\tborder-top-right-radius: 2px;\r\n\t}\r\n.leaflet-touch .leaflet-bar a:last-child {\r\n\tborder-bottom-left-radius: 2px;\r\n\tborder-bottom-right-radius: 2px;\r\n\t}\r\n\r\n/* zoom control */\r\n\r\n.leaflet-control-zoom-in,\r\n.leaflet-control-zoom-out {\r\n\tfont: bold 18px \'Lucida Console\', Monaco, monospace;\r\n\ttext-indent: 1px;\r\n\t}\r\n\r\n.leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out {\r\n\tfont-size: 22px;\r\n\t}\r\n\r\n\r\n/* layers control */\r\n\r\n.leaflet-control-layers {\r\n\tbox-shadow: 0 1px 5px rgba(0,0,0,0.4);\r\n\tbackground: #fff;\r\n\tborder-radius: 5px;\r\n\t}\r\n.leaflet-control-layers-toggle {\r\n\tbackground-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAaCAQAAAADQ4RFAAACf0lEQVR4AY1UM3gkARTePdvdoTxXKc+qTl3aU5U6b2Kbkz3Gtq3Zw6ziLGNPzrYx7946Tr6/ee/XeCQ4D3ykPtL5tHno4n0d/h3+xfuWHGLX81cn7r0iTNzjr7LrlxCqPtkbTQEHeqOrTy4Yyt3VCi/IOB0v7rVC7q45Q3Gr5K6jt+3Gl5nCoDD4MtO+j96Wu8atmhGqcNGHObuf8OM/x3AMx38+4Z2sPqzCxRFK2aF2e5Jol56XTLyggAMTL56XOMoS1W4pOyjUcGGQdZxU6qRh7B9Zp+PfpOFlqt0zyDZckPi1ttmIp03jX8gyJ8a/PG2yutpS/Vol7peZIbZcKBAEEheEIAgFbDkz5H6Zrkm2hVWGiXKiF4Ycw0RWKdtC16Q7qe3X4iOMxruonzegJzWaXFrU9utOSsLUmrc0YjeWYjCW4PDMADElpJSSQ0vQvA1Tm6/JlKnqFs1EGyZiFCqnRZTEJJJiKRYzVYzJck2Rm6P4iH+cmSY0YzimYa8l0EtTODFWhcMIMVqdsI2uiTvKmTisIDHJ3od5GILVhBCarCfVRmo4uTjkhrhzkiBV7SsaqS+TzrzM1qpGGUFt28pIySQHR6h7F6KSwGWm97ay+Z+ZqMcEjEWebE7wxCSQwpkhJqoZA5ivCdZDjJepuJ9IQjGGUmuXJdBFUygxVqVsxFsLMbDe8ZbDYVCGKxs+W080max1hFCarCfV+C1KATwcnvE9gRRuMP2prdbWGowm1KB1y+zwMMENkM755cJ2yPDtqhTI6ED1M/82yIDtC/4j4BijjeObflpO9I9MwXTCsSX8jWAFeHr05WoLTJ5G8IQVS/7vwR6ohirYM7f6HzYpogfS3R2OAAAAAElFTkSuQmCC);\r\n\twidth: 36px;\r\n\theight: 36px;\r\n\t}\r\n.leaflet-retina .leaflet-control-layers-toggle {\r\n\tbackground-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADQAAAA0CAQAAABvcdNgAAAEsklEQVR4AWL4TydIhpZK1kpWOlg0w3ZXP6D2soBtG42jeI6ZmQTHzAxiTbSJsYLjO9HhP+WOmcuhciVnmHVQcJnp7DFvScowZorad/+V/fVzMdMT2g9Cv9guXGv/7pYOrXh2U+RRR3dSd9JRx6bIFc/ekqHI29JC6pJ5ZEh1yWkhkbcFeSjxgx3L2m1cb1C7bceyxA+CNjT/Ifff+/kDk2u/w/33/IeCMOSaWZ4glosqT3DNnNZQ7Cs58/3Ce5HL78iZH/vKVIaYlqzfdLu8Vi7dnvUbEza5Idt36tquZFldl6N5Z/POLof0XLK61mZCmJSWjVF9tEjUluu74IUXvgttuVIHE7YxSkaYhJZam7yiM9Pv82JYfl9nptxZaxMJE4YSPty+vF0+Y2up9d3wwijfjZbabqm/3bZ9ecKHsiGmRflnn1MW4pjHf9oLufyn2z3y1D6n8g8TZhxyzipLNPnAUpsOiuWimg52psrTZYnOWYNDTMuWBWa0tJb4rgq1UvmutpaYEbZlwU3CLJm/ayYjHW5/h7xWLn9Hh1vepDkyf7dE7MtT5LR4e7yYpHrkhOUpEfssBLq2pPhAqoSWKUkk7EDqkmK6RrCEzqDjhNDWNE+XSMvkJRDWlZTmCW0l0PHQGRZY5t1L83kT0Y3l2SItk5JAWHl2dCOBm+fPu3fo5/3v61RMCO9Jx2EEYYhb0rmNQMX/vm7gqOEJLcXTGw3CAuRNeyaPWwjR8PRqKQ1PDA/dpv+on9Shox52WFnx0KY8onHayrJzm87i5h9xGw/tfkev0jGsQizqezUKjk12hBMKJ4kbCqGPVNXudyyrShovGw5CgxsRICxF6aRmSjlBnHRzg7Gx8fKqEubI2rahQYdR1YgDIRQO7JvQyD52hoIQx0mxa0ODtW2Iozn1le2iIRdzwWewedyZzewidueOGqlsn1MvcnQpuVwLGG3/IR1hIKxCjelIDZ8ldqWz25jWAsnldEnK0Zxro19TGVb2ffIZEsIO89EIEDvKMPrzmBOQcKQ+rroye6NgRRxqR4U8EAkz0CL6uSGOm6KQCdWjvjRiSP1BPalCRS5iQYiEIvxuBMJEWgzSoHADcVMuN7IuqqTeyUPq22qFimFtxDyBBJEwNyt6TM88blFHao/6tWWhuuOM4SAK4EI4QmFHA+SEyWlp4EQoJ13cYGzMu7yszEIBOm2rVmHUNqwAIQabISNMRstmdhNWcFLsSm+0tjJH1MdRxO5Nx0WDMhCtgD6OKgZeljJqJKc9po8juskR9XN0Y1lZ3mWjLR9JCO1jRDMd0fpYC2VnvjBSEFg7wBENc0R9HFlb0xvF1+TBEpF68d+DHR6IOWVv2BECtxo46hOFUBd/APU57WIoEwJhIi2CdpyZX0m93BZicktMj1AS9dClteUFAUNUIEygRZCtik5zSxI9MubTBH1GOiHsiLJ3OCoSZkILa9PxiN0EbvhsAo8tdAf9Seepd36lGWHmtNANTv5Jd0z4QYyeo/UEJqxKRpg5LZx6btLPsOaEmdMyxYdlc8LMaJnikDlhclqmPiQnTEpLUIZEwkRagjYkEibQErwhkTAKCLQEbUgkzJQWc/0PstHHcfEdQ+UAAAAASUVORK5CYII=);\r\n\tbackground-size: 26px 26px;\r\n\t}\r\n.leaflet-touch .leaflet-control-layers-toggle {\r\n\twidth: 44px;\r\n\theight: 44px;\r\n\t}\r\n.leaflet-control-layers .leaflet-control-layers-list,\r\n.leaflet-control-layers-expanded .leaflet-control-layers-toggle {\r\n\tdisplay: none;\r\n\t}\r\n.leaflet-control-layers-expanded .leaflet-control-layers-list {\r\n\tdisplay: block;\r\n\tposition: relative;\r\n\t}\r\n.leaflet-control-layers-expanded {\r\n\tpadding: 6px 10px 6px 6px;\r\n\tcolor: #333;\r\n\tbackground: #fff;\r\n\t}\r\n.leaflet-control-layers-scrollbar {\r\n\toverflow-y: scroll;\r\n\toverflow-x: hidden;\r\n\tpadding-right: 5px;\r\n\t}\r\n.leaflet-control-layers-selector {\r\n\tmargin-top: 2px;\r\n\tposition: relative;\r\n\ttop: 1px;\r\n\t}\r\n.leaflet-control-layers label {\r\n\tdisplay: block;\r\n\tfont-size: 13px;\r\n\tfont-size: 1.08333em;\r\n\t}\r\n.leaflet-control-layers-separator {\r\n\theight: 0;\r\n\tborder-top: 1px solid #ddd;\r\n\tmargin: 5px -10px 5px -6px;\r\n\t}\r\n\r\n/* Default icon URLs */\r\n.leaflet-default-icon-path { /* used only in path-guessing heuristic, see L.Icon.Default */\r\n\tbackground-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAApCAYAAADAk4LOAAAFgUlEQVR4Aa1XA5BjWRTN2oW17d3YaZtr2962HUzbDNpjszW24mRt28p47v7zq/bXZtrp/lWnXr337j3nPCe85NcypgSFdugCpW5YoDAMRaIMqRi6aKq5E3YqDQO3qAwjVWrD8Ncq/RBpykd8oZUb/kaJutow8r1aP9II0WmLKLIsJyv1w/kqw9Ch2MYdB++12Onxee/QMwvf4/Dk/Lfp/i4nxTXtOoQ4pW5Aj7wpici1A9erdAN2OH64x8OSP9j3Ft3b7aWkTg/Fm91siTra0f9on5sQr9INejH6CUUUpavjFNq1B+Oadhxmnfa8RfEmN8VNAsQhPqF55xHkMzz3jSmChWU6f7/XZKNH+9+hBLOHYozuKQPxyMPUKkrX/K0uWnfFaJGS1QPRtZsOPtr3NsW0uyh6NNCOkU3Yz+bXbT3I8G3xE5EXLXtCXbbqwCO9zPQYPRTZ5vIDXD7U+w7rFDEoUUf7ibHIR4y6bLVPXrz8JVZEql13trxwue/uDivd3fkWRbS6/IA2bID4uk0UpF1N8qLlbBlXs4Ee7HLTfV1j54APvODnSfOWBqtKVvjgLKzF5YdEk5ewRkGlK0i33Eofffc7HT56jD7/6U+qH3Cx7SBLNntH5YIPvODnyfIXZYRVDPqgHtLs5ABHD3YzLuespb7t79FY34DjMwrVrcTuwlT55YMPvOBnRrJ4VXTdNnYug5ucHLBjEpt30701A3Ts+HEa73u6dT3FNWwflY86eMHPk+Yu+i6pzUpRrW7SNDg5JHR4KapmM5Wv2E8Tfcb1HoqqHMHU+uWDD7zg54mz5/2BSnizi9T1Dg4QQXLToGNCkb6tb1NU+QAlGr1++eADrzhn/u8Q2YZhQVlZ5+CAOtqfbhmaUCS1ezNFVm2imDbPmPng5wmz+gwh+oHDce0eUtQ6OGDIyR0uUhUsoO3vfDmmgOezH0mZN59x7MBi++WDL1g/eEiU3avlidO671bkLfwbw5XV2P8Pzo0ydy4t2/0eu33xYSOMOD8hTf4CrBtGMSoXfPLchX+J0ruSePw3LZeK0juPJbYzrhkH0io7B3k164hiGvawhOKMLkrQLyVpZg8rHFW7E2uHOL888IBPlNZ1FPzstSJM694fWr6RwpvcJK60+0HCILTBzZLFNdtAzJaohze60T8qBzyh5ZuOg5e7uwQppofEmf2++DYvmySqGBuKaicF1blQjhuHdvCIMvp8whTTfZzI7RldpwtSzL+F1+wkdZ2TBOW2gIF88PBTzD/gpeREAMEbxnJcaJHNHrpzji0gQCS6hdkEeYt9DF/2qPcEC8RM28Hwmr3sdNyht00byAut2k3gufWNtgtOEOFGUwcXWNDbdNbpgBGxEvKkOQsxivJx33iow0Vw5S6SVTrpVq11ysA2Rp7gTfPfktc6zhtXBBC+adRLshf6sG2RfHPZ5EAc4sVZ83yCN00Fk/4kggu40ZTvIEm5g24qtU4KjBrx/BTTH8ifVASAG7gKrnWxJDcU7x8X6Ecczhm3o6YicvsLXWfh3Ch1W0k8x0nXF+0fFxgt4phz8QvypiwCCFKMqXCnqXExjq10beH+UUA7+nG6mdG/Pu0f3LgFcGrl2s0kNNjpmoJ9o4B29CMO8dMT4Q5ox8uitF6fqsrJOr8qnwNbRzv6hSnG5wP+64C7h9lp30hKNtKdWjtdkbuPA19nJ7Tz3zR/ibgARbhb4AlhavcBebmTHcFl2fvYEnW0ox9xMxKBS8btJ+KiEbq9zA4RthQXDhPa0T9TEe69gWupwc6uBUphquXgf+/FrIjweHQS4/pduMe5ERUMHUd9xv8ZR98CxkS4F2n3EUrUZ10EYNw7BWm9x1GiPssi3GgiGRDKWRYZfXlON+dfNbM+GgIwYdwAAAAASUVORK5CYII=);\r\n\t}\r\n\r\n\r\n/* attribution and scale controls */\r\n\r\n.leaflet-container .leaflet-control-attribution {\r\n\tbackground: #fff;\r\n\tbackground: rgba(255, 255, 255, 0.8);\r\n\tmargin: 0;\r\n\t}\r\n.leaflet-control-attribution,\r\n.leaflet-control-scale-line {\r\n\tpadding: 0 5px;\r\n\tcolor: #333;\r\n\tline-height: 1.4;\r\n\t}\r\n.leaflet-control-attribution a {\r\n\ttext-decoration: none;\r\n\t}\r\n.leaflet-control-attribution a:hover,\r\n.leaflet-control-attribution a:focus {\r\n\ttext-decoration: underline;\r\n\t}\r\n.leaflet-attribution-flag {\r\n\tdisplay: inline !important;\r\n\tvertical-align: baseline !important;\r\n\twidth: 1em;\r\n\theight: 0.6669em;\r\n\t}\r\n.leaflet-left .leaflet-control-scale {\r\n\tmargin-left: 5px;\r\n\t}\r\n.leaflet-bottom .leaflet-control-scale {\r\n\tmargin-bottom: 5px;\r\n\t}\r\n.leaflet-control-scale-line {\r\n\tborder: 2px solid #777;\r\n\tborder-top: none;\r\n\tline-height: 1.1;\r\n\tpadding: 2px 5px 1px;\r\n\twhite-space: nowrap;\r\n\t-moz-box-sizing: border-box;\r\n\t box-sizing: border-box;\r\n\tbackground: rgba(255, 255, 255, 0.8);\r\n\ttext-shadow: 1px 1px #fff;\r\n\t}\r\n.leaflet-control-scale-line:not(:first-child) {\r\n\tborder-top: 2px solid #777;\r\n\tborder-bottom: none;\r\n\tmargin-top: -2px;\r\n\t}\r\n.leaflet-control-scale-line:not(:first-child):not(:last-child) {\r\n\tborder-bottom: 2px solid #777;\r\n\t}\r\n\r\n.leaflet-touch .leaflet-control-attribution,\r\n.leaflet-touch .leaflet-control-layers,\r\n.leaflet-touch .leaflet-bar {\r\n\tbox-shadow: none;\r\n\t}\r\n.leaflet-touch .leaflet-control-layers,\r\n.leaflet-touch .leaflet-bar {\r\n\tborder: 2px solid rgba(0,0,0,0.2);\r\n\tbackground-clip: padding-box;\r\n\t}\r\n\r\n\r\n/* popup */\r\n\r\n.leaflet-popup {\r\n\tposition: absolute;\r\n\ttext-align: center;\r\n\tmargin-bottom: 20px;\r\n\t}\r\n.leaflet-popup-content-wrapper {\r\n\tpadding: 1px;\r\n\ttext-align: left;\r\n\tborder-radius: 12px;\r\n\t}\r\n.leaflet-popup-content {\r\n\tmargin: 13px 24px 13px 20px;\r\n\tline-height: 1.3;\r\n\tfont-size: 13px;\r\n\tfont-size: 1.08333em;\r\n\tmin-height: 1px;\r\n\t}\r\n.leaflet-popup-content p {\r\n\tmargin: 17px 0;\r\n\tmargin: 1.3em 0;\r\n\t}\r\n.leaflet-popup-tip-container {\r\n\twidth: 40px;\r\n\theight: 20px;\r\n\tposition: absolute;\r\n\tleft: 50%;\r\n\tmargin-top: -1px;\r\n\tmargin-left: -20px;\r\n\toverflow: hidden;\r\n\tpointer-events: none;\r\n\t}\r\n.leaflet-popup-tip {\r\n\twidth: 17px;\r\n\theight: 17px;\r\n\tpadding: 1px;\r\n\r\n\tmargin: -10px auto 0;\r\n\tpointer-events: auto;\r\n\r\n\t-webkit-transform: rotate(45deg);\r\n\t -moz-transform: rotate(45deg);\r\n\t -ms-transform: rotate(45deg);\r\n\t transform: rotate(45deg);\r\n\t}\r\n.leaflet-popup-content-wrapper,\r\n.leaflet-popup-tip {\r\n\tbackground: white;\r\n\tcolor: #333;\r\n\tbox-shadow: 0 3px 14px rgba(0,0,0,0.4);\r\n\t}\r\n.leaflet-container a.leaflet-popup-close-button {\r\n\tposition: absolute;\r\n\ttop: 0;\r\n\tright: 0;\r\n\tborder: none;\r\n\ttext-align: center;\r\n\twidth: 24px;\r\n\theight: 24px;\r\n\tfont: 16px/24px Tahoma, Verdana, sans-serif;\r\n\tcolor: #757575;\r\n\ttext-decoration: none;\r\n\tbackground: transparent;\r\n\t}\r\n.leaflet-container a.leaflet-popup-close-button:hover,\r\n.leaflet-container a.leaflet-popup-close-button:focus {\r\n\tcolor: #585858;\r\n\t}\r\n.leaflet-popup-scrolled {\r\n\toverflow: auto;\r\n\t}\r\n\r\n.leaflet-oldie .leaflet-popup-content-wrapper {\r\n\t-ms-zoom: 1;\r\n\t}\r\n.leaflet-oldie .leaflet-popup-tip {\r\n\twidth: 24px;\r\n\tmargin: 0 auto;\r\n\r\n\t-ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";\r\n\tfilter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);\r\n\t}\r\n\r\n.leaflet-oldie .leaflet-control-zoom,\r\n.leaflet-oldie .leaflet-control-layers,\r\n.leaflet-oldie .leaflet-popup-content-wrapper,\r\n.leaflet-oldie .leaflet-popup-tip {\r\n\tborder: 1px solid #999;\r\n\t}\r\n\r\n\r\n/* div icon */\r\n\r\n.leaflet-div-icon {\r\n\tbackground: #fff;\r\n\tborder: 1px solid #666;\r\n\t}\r\n\r\n\r\n/* Tooltip */\r\n/* Base styles for the element that has a tooltip */\r\n.leaflet-tooltip {\r\n\tposition: absolute;\r\n\tpadding: 6px;\r\n\tbackground-color: #fff;\r\n\tborder: 1px solid #fff;\r\n\tborder-radius: 3px;\r\n\tcolor: #222;\r\n\twhite-space: nowrap;\r\n\t-webkit-user-select: none;\r\n\t-moz-user-select: none;\r\n\t-ms-user-select: none;\r\n\tuser-select: none;\r\n\tpointer-events: none;\r\n\tbox-shadow: 0 1px 3px rgba(0,0,0,0.4);\r\n\t}\r\n.leaflet-tooltip.leaflet-interactive {\r\n\tcursor: pointer;\r\n\tpointer-events: auto;\r\n\t}\r\n.leaflet-tooltip-top:before,\r\n.leaflet-tooltip-bottom:before,\r\n.leaflet-tooltip-left:before,\r\n.leaflet-tooltip-right:before {\r\n\tposition: absolute;\r\n\tpointer-events: none;\r\n\tborder: 6px solid transparent;\r\n\tbackground: transparent;\r\n\tcontent: "";\r\n\t}\r\n\r\n/* Directions */\r\n\r\n.leaflet-tooltip-bottom {\r\n\tmargin-top: 6px;\r\n}\r\n.leaflet-tooltip-top {\r\n\tmargin-top: -6px;\r\n}\r\n.leaflet-tooltip-bottom:before,\r\n.leaflet-tooltip-top:before {\r\n\tleft: 50%;\r\n\tmargin-left: -6px;\r\n\t}\r\n.leaflet-tooltip-top:before {\r\n\tbottom: 0;\r\n\tmargin-bottom: -12px;\r\n\tborder-top-color: #fff;\r\n\t}\r\n.leaflet-tooltip-bottom:before {\r\n\ttop: 0;\r\n\tmargin-top: -12px;\r\n\tmargin-left: -6px;\r\n\tborder-bottom-color: #fff;\r\n\t}\r\n.leaflet-tooltip-left {\r\n\tmargin-left: -6px;\r\n}\r\n.leaflet-tooltip-right {\r\n\tmargin-left: 6px;\r\n}\r\n.leaflet-tooltip-left:before,\r\n.leaflet-tooltip-right:before {\r\n\ttop: 50%;\r\n\tmargin-top: -6px;\r\n\t}\r\n.leaflet-tooltip-left:before {\r\n\tright: 0;\r\n\tmargin-right: -12px;\r\n\tborder-left-color: #fff;\r\n\t}\r\n.leaflet-tooltip-right:before {\r\n\tleft: 0;\r\n\tmargin-left: -12px;\r\n\tborder-right-color: #fff;\r\n\t}\r\n\r\n/* Printing */\r\n\r\n@media print {\r\n\t/* Prevent printers from removing background-images of controls. */\r\n\t.leaflet-control {\r\n\t\t-webkit-print-color-adjust: exact;\r\n\t\tprint-color-adjust: exact;\r\n\t\t}\r\n\t}\r\n' + +export const LEAFLET_DEFAULT_ICON_URL = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAApCAYAAADAk4LOAAAFgUlEQVR4Aa1XA5BjWRTN2oW17d3YaZtr2962HUzbDNpjszW24mRt28p47v7zq/bXZtrp/lWnXr337j3nPCe85NcypgSFdugCpW5YoDAMRaIMqRi6aKq5E3YqDQO3qAwjVWrD8Ncq/RBpykd8oZUb/kaJutow8r1aP9II0WmLKLIsJyv1w/kqw9Ch2MYdB++12Onxee/QMwvf4/Dk/Lfp/i4nxTXtOoQ4pW5Aj7wpici1A9erdAN2OH64x8OSP9j3Ft3b7aWkTg/Fm91siTra0f9on5sQr9INejH6CUUUpavjFNq1B+Oadhxmnfa8RfEmN8VNAsQhPqF55xHkMzz3jSmChWU6f7/XZKNH+9+hBLOHYozuKQPxyMPUKkrX/K0uWnfFaJGS1QPRtZsOPtr3NsW0uyh6NNCOkU3Yz+bXbT3I8G3xE5EXLXtCXbbqwCO9zPQYPRTZ5vIDXD7U+w7rFDEoUUf7ibHIR4y6bLVPXrz8JVZEql13trxwue/uDivd3fkWRbS6/IA2bID4uk0UpF1N8qLlbBlXs4Ee7HLTfV1j54APvODnSfOWBqtKVvjgLKzF5YdEk5ewRkGlK0i33Eofffc7HT56jD7/6U+qH3Cx7SBLNntH5YIPvODnyfIXZYRVDPqgHtLs5ABHD3YzLuespb7t79FY34DjMwrVrcTuwlT55YMPvOBnRrJ4VXTdNnYug5ucHLBjEpt30701A3Ts+HEa73u6dT3FNWwflY86eMHPk+Yu+i6pzUpRrW7SNDg5JHR4KapmM5Wv2E8Tfcb1HoqqHMHU+uWDD7zg54mz5/2BSnizi9T1Dg4QQXLToGNCkb6tb1NU+QAlGr1++eADrzhn/u8Q2YZhQVlZ5+CAOtqfbhmaUCS1ezNFVm2imDbPmPng5wmz+gwh+oHDce0eUtQ6OGDIyR0uUhUsoO3vfDmmgOezH0mZN59x7MBi++WDL1g/eEiU3avlidO671bkLfwbw5XV2P8Pzo0ydy4t2/0eu33xYSOMOD8hTf4CrBtGMSoXfPLchX+J0ruSePw3LZeK0juPJbYzrhkH0io7B3k164hiGvawhOKMLkrQLyVpZg8rHFW7E2uHOL888IBPlNZ1FPzstSJM694fWr6RwpvcJK60+0HCILTBzZLFNdtAzJaohze60T8qBzyh5ZuOg5e7uwQppofEmf2++DYvmySqGBuKaicF1blQjhuHdvCIMvp8whTTfZzI7RldpwtSzL+F1+wkdZ2TBOW2gIF88PBTzD/gpeREAMEbxnJcaJHNHrpzji0gQCS6hdkEeYt9DF/2qPcEC8RM28Hwmr3sdNyht00byAut2k3gufWNtgtOEOFGUwcXWNDbdNbpgBGxEvKkOQsxivJx33iow0Vw5S6SVTrpVq11ysA2Rp7gTfPfktc6zhtXBBC+adRLshf6sG2RfHPZ5EAc4sVZ83yCN00Fk/4kggu40ZTvIEm5g24qtU4KjBrx/BTTH8ifVASAG7gKrnWxJDcU7x8X6Ecczhm3o6YicvsLXWfh3Ch1W0k8x0nXF+0fFxgt4phz8QvypiwCCFKMqXCnqXExjq10beH+UUA7+nG6mdG/Pu0f3LgFcGrl2s0kNNjpmoJ9o4B29CMO8dMT4Q5ox8uitF6fqsrJOr8qnwNbRzv6hSnG5wP+64C7h9lp30hKNtKdWjtdkbuPA19nJ7Tz3zR/ibgARbhb4AlhavcBebmTHcFl2fvYEnW0ox9xMxKBS8btJ+KiEbq9zA4RthQXDhPa0T9TEe69gWupwc6uBUphquXgf+/FrIjweHQS4/pduMe5ERUMHUd9xv8ZR98CxkS4F2n3EUrUZ10EYNw7BWm9x1GiPssi3GgiGRDKWRYZfXlON+dfNbM+GgIwYdwAAAAASUVORK5CYII=' +export const LEAFLET_DEFAULT_ICON_RETINA_URL = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAABSCAMAAAAhFXfZAAAC91BMVEVMaXEzeak2f7I4g7g3g7cua5gzeKg8hJo3grY4g7c3grU0gLI2frE0daAubJc2gbQwd6QzeKk2gLMtd5sxdKIua5g1frA2f7IydaM0e6w2fq41fK01eqo3grgubJgta5cxdKI1f7AydaQydaMxc6EubJgvbJkwcZ4ubZkwcJwubZgubJcydqUydKIxapgubJctbJcubZcubJcvbJYubJcvbZkubJctbJctbZcubJg2f7AubJcrbZcubJcubJcua5g3grY0fq8ubJcubJdEkdEwhsw6i88vhswuhcsuhMtBjMgthMsrg8srgss6is8qgcs8i9A9iMYtg8spgcoogMo7hcMngMonf8olfso4gr8kfck5iM8jfMk4iM8he8k1fro7itAgesk2hs8eecgzfLcofssdeMg0hc4cd8g2hcsxeLQbdsgZdcgxeLImfcszhM0vda4xgckzhM4xg84wf8Yxgs4udKsvfcQucqhUndROmdM1fK0wcZ8vb5w0eqpQm9MzeKhXoNVcpdYydKNWn9VZotVKltJFjsIwcJ1Rms9OlslLmtH///8+kc9epdYzd6dbo9VHkMM2f7FHmNBClM8ydqVcpNY9hro3gLM9hLczealQmcw3fa46f7A8gLMxc6I3eagyc6FIldJMl9JSnNRSntNNl9JPnNJFi75UnM9ZodVKksg8kM45jc09e6ZHltFBk883gbRBh7pDk9EwcaBzn784g7dKkcY2i81Om9M7j85Llc81is09g7Q4grY/j9A0eqxKmdFFltBEjcXf6fFImdBCiLxJl9FGlNFBi78yiMxVndEvbpo6js74+vx+psPP3+o/ks5HkcpGmNCjwdZCkNDM3ehYoNJEls+lxNkxh8xHks0+jdC1zd5Lg6r+/v/H2ufz9/o3jM3t8/edvdM/k89Th61OiLBSjbZklbaTt9BfptdjmL1AicBHj8hGk9FAgK1dkLNTjLRekrdClc/k7fM0icy0y9tgp9c4jc2NtM9Dlc8zicxeXZn3AAAAQ3RSTlMAHDdTb4yPA+LtnEQmC4L2EmHqB7XA0d0sr478x4/Yd5i1zOfyPkf1sLVq4Nh3FvjxopQ2/STNuFzUwFIwxKaejILpIBEV9wAABhVJREFUeF6s1NdyFEcYBeBeoQIhRAkLlRDGrhIgY3BJL8CVeKzuyXFzzjkn5ZxzzuScg3PO8cKzu70JkO0LfxdTU//pM9vTu7Xgf6KqOVTb9X7toRrVEfBf1HTVjZccrT/2by1VV928Yty9ZbVuucdz90frG8DBjl9pVApbOstvmMuvVgaNXSfAAd6pGxpy6yxf5ph43pS/4f3uoaGm2rdu72S9xzOvMymkZFq/ptDrk90mhW7e4zl7HLzhxGWPR20xmSxJ/VqldG5m9XhaVOA1DadsNh3Pu5L2N6QtPO/32JpqQBVVk20oy/Pi2s23WEvyfHbe1thadVQttvm7Llf65gGmXK67XtupyoM7HQhmXdLS8oGWJNeOJ3C5fG5XCEJnkez3/oFdsvgJ4l2ANZwhrJKk/7OSXa+3Vw2WJMlKnGkobouYk6T0TyX30klOUnTD9HJ5qpckL3EW/w4XF3Xd0FGywXUrstrclVsqz5Pd/sXFYyDnPdrLcQODmGOK47IZb4CmibmMn+MYRzFZ5jg33ZL/EJrWcszHmANy3ARBK/IXtciJy8VsitPSdE3uuHxzougojcUdr8/32atnz/ev3f/K5wtpxUTpcaI45zusVDpYtZi+jg0oU9b3x74h7+n9ABvYEZeKaVq0sh0AtLKsFtqNBdeT0MrSzwwlq9+x6xAO4tgOtSzbCjrNQQiNvQUbUEubvzBUeGw26yDCsRHCoLkTHDa7IdOLIThs/gHvChszh2CimE8peRs47cxANI0lYNB5y1DljpOF0IhzBDPOZnDOqYYbeGKECbPzWnXludPphw5c2YBq5zlwXphIbO4VDCZ0gnPfUO1TwZoYwAs2ExPCedAu9DAjfQUjzITQb3jNj0KG2Sgt6BHaQUdYzWz+XmBktOHwanXjaSTcwwziBcuMOtwBmqPrTOxFQR/DRKKPqyur0aiW6cULYsx6tBm0jXpR/AUWR6HRq9WVW6MRhIq5jLyjbaCTDCijyYJNpCajdyobP/eTw0iexBAKkJ3gA5KcQb2zBXsIBckn+xVv8jkZSaEFHE+jFEleAEfayRU0MouNoBmB/L50Ai/HSLIHxcrpCvnhSQAuakKp2C/YbCylJjXRVy/z3+Kv/RrNcCo+WUzlVEhzKffnTQnxeN9fWF88fiNCUdSTsaufaChKWInHeysygfpIqagoakW+vV20J8uyl6TyNKEZWV4oRSPyCkWpgOLSbkCObT8o2r6tlG58HQquf6O0v50tB7JM7F4EORd2dx/K0w/KHsVkLPaoYrwgP/y7krr3SSMA4zj+OBgmjYkxcdIJQyQRKgg2viX9Hddi9UBb29LrKR7CVVEEEXWojUkXNyfTNDE14W9gbHJNuhjDettN3ZvbOvdOqCD3Jp/9l+/wJE+9PkYGjx/fqkys3S2rMozM/o2106rfMUINo6hVqz+eu/hd1c4xTg0TAfy5kV+4UG6+IthHTU9woWmxuKNbTfuCSfovBCxq7EtHqvYL4Sm6F8GVxsSXHMQ07TOi1DKtZxjWaaIyi4CXWjxPccUw8WVbMYY5wxC1mzEyXMJWkllpRloi+Kkoq69sxBTlElF6aAxYUbjXNlhlDZilDnM4U5SlN5biRsRHnbx3mbeWjEh4mEyiuJDl5XcWVmX5GvNkFgLWZM5qwsop4/AWfLhU1cR7k1VVvcYCWRkOI6Xy5gmnphCYIkvzuNYzHzosq2oNk2RtSs8khfUOfHIDgR6ysYBaMpl4uEgk2U/oJTs9AaTSwma7dT69geAE2ZpEjUsn2ieJNHeKfrI3EcAGJ2ZaNgVuC8EBctCLc57P5u5led6IOBkIYkuQMrmmjChs4VkfOerHqSBkPzZlhe06RslZ3zMjk2sscqKwY0RcjKK+LWbzd7KiHhkncs/siFJ+V5eXxD34B8nVuJEpGJNmxN2gH3vSvp7J70tF+D1Ej8qUJD1TkErAND2GZwTFg/LubvmgiBG3SOvdlsqFQrkEzJCL1rstlnVFROixZoDDSuXQFHESwVGlcuQcMb/b42NgjLowh5MTDFE3vNB5qStRIErdCQEh6pLPR92anSUb/wAIhldAaDMpGgAAAABJRU5ErkJggg==' +export const LEAFLET_DEFAULT_SHADOW_URL = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACkAAAApCAQAAAACach9AAACMUlEQVR4Ae3ShY7jQBAE0Aoz/f9/HTMzhg1zrdKUrJbdx+Kd2nD8VNudfsL/Th///dyQN2TH6f3y/BGpC379rV+S+qqetBOxImNQXL8JCAr2V4iMQXHGNJxeCfZXhSRBcQMfvkOWUdtfzlLgAENmZDcmo2TVmt8OSM2eXxBp3DjHSMFutqS7SbmemzBiR+xpKCNUIRkdkkYxhAkyGoBvyQFEJEefwSmmvBfJuJ6aKqKWnAkvGZOaZXTUgFqYULWNSHUckZuR1HIIimUExutRxwzOLROIG4vKmCKQt364mIlhSyzAf1m9lHZHJZrlAOMMztRRiKimp/rpdJDc9Awry5xTZCte7FHtuS8wJgeYGrex28xNTd086Dik7vUMscQOa8y4DoGtCCSkAKlNwpgNtphjrC6MIHUkR6YWxxs6Sc5xqn222mmCRFzIt8lEdKx+ikCtg91qS2WpwVfBelJCiQJwvzixfI9cxZQWgiSJelKnwBElKYtDOb2MFbhmUigbReQBV0Cg4+qMXSxXSyGUn4UbF8l+7qdSGnTC0XLCmahIgUHLhLOhpVCtw4CzYXvLQWQbJNmxoCsOKAxSgBJno75avolkRw8iIAFcsdc02e9iyCd8tHwmeSSoKTowIgvscSGZUOA7PuCN5b2BX9mQM7S0wYhMNU74zgsPBj3HU7wguAfnxxjFQGBE6pwN+GjME9zHY7zGp8wVxMShYX9NXvEWD3HbwJf4giO4CFIQxXScH1/TM+04kkBiAAAAAElFTkSuQmCC' + +export function ensureLeafletStyles(): void { + if (typeof document === 'undefined' || document.getElementById(LEAFLET_STYLE_ID)) + return + + const style = document.createElement('style') + style.id = LEAFLET_STYLE_ID + style.dataset.nuxtScriptsLeaflet = '1' + style.textContent = LEAFLET_CSS + document.head.append(style) +} + +export function configureLeafletDefaultIcons(leaflet: typeof Leaflet | undefined): void { + if (!leaflet) + return + + leaflet.Icon.Default.mergeOptions({ + iconUrl: LEAFLET_DEFAULT_ICON_URL, + iconRetinaUrl: LEAFLET_DEFAULT_ICON_RETINA_URL, + shadowUrl: LEAFLET_DEFAULT_SHADOW_URL, + }) +} diff --git a/packages/script/src/runtime/maplibre-styles.ts b/packages/script/src/runtime/maplibre-styles.ts new file mode 100644 index 000000000..a61c437dc --- /dev/null +++ b/packages/script/src/runtime/maplibre-styles.ts @@ -0,0 +1,36 @@ +import type * as MapLibre from 'maplibre-gl' + +export const MAPLIBRE_STYLESHEET_URL = 'https://unpkg.com/maplibre-gl@5.24.0/dist/maplibre-gl.css' +export const MAPLIBRE_STYLESHEET_INTEGRITY = 'sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK' + +const MAPLIBRE_STYLE_ID = 'nuxt-scripts-maplibre-styles' + +export function configureMapLibreWorker(maplibre: typeof MapLibre | undefined, workerUrl?: string): void { + if (maplibre && workerUrl) + maplibre.setWorkerUrl(workerUrl) +} + +/** Injects MapLibre's required control and marker stylesheet once. */ +export function ensureMapLibreStyles(stylesheetUrl = MAPLIBRE_STYLESHEET_URL): void { + if (typeof document === 'undefined') + return + + const link = document.createElement('link') + link.rel = 'stylesheet' + link.href = stylesheetUrl + + const stylesheets = document.querySelectorAll('link[rel~="stylesheet"][href]') + if ([...stylesheets].some(stylesheet => stylesheet.href === link.href)) + return + + const injectedStylesheets = document.querySelectorAll('[data-nuxt-scripts-maplibre]') + link.id = injectedStylesheets.length ? `${MAPLIBRE_STYLE_ID}-${injectedStylesheets.length + 1}` : MAPLIBRE_STYLE_ID + link.dataset.nuxtScriptsMaplibre = '1' + + if (stylesheetUrl === MAPLIBRE_STYLESHEET_URL) { + link.integrity = MAPLIBRE_STYLESHEET_INTEGRITY + link.crossOrigin = 'anonymous' + } + + document.head.append(link) +} diff --git a/packages/script/src/runtime/registry/leaflet.ts b/packages/script/src/runtime/registry/leaflet.ts new file mode 100644 index 000000000..6834d7510 --- /dev/null +++ b/packages/script/src/runtime/registry/leaflet.ts @@ -0,0 +1,45 @@ +import type * as Leaflet from 'leaflet' +import type { RegistryScriptInput } from '#nuxt-scripts/types' +import { configureLeafletDefaultIcons, ensureLeafletStyles } from '../leaflet-styles' +import { useRegistryScript } from '../utils' +import { LeafletOptions } from './schemas' + +export { LeafletOptions } + +export type LeafletInput = RegistryScriptInput + +export interface LeafletApi { + L: typeof Leaflet +} + +declare global { + interface Window extends LeafletApi {} +} + +export function useScriptLeaflet(_options?: LeafletInput) { + return useRegistryScript('leaflet', (options, context) => { + const injectStyles = options?.injectStyles !== false + const usesDefaultSource = !context.scriptInput?.src + return { + scriptInput: { + src: 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.js', + ...(usesDefaultSource + ? { + integrity: 'sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=', + crossorigin: 'anonymous', + } + : {}), + }, + clientInit: injectStyles ? ensureLeafletStyles : undefined, + schema: import.meta.dev ? LeafletOptions : undefined, + scriptOptions: { + use() { + const leaflet = window.L + if (injectStyles && leaflet) + configureLeafletDefaultIcons(leaflet) + return { L: leaflet } + }, + }, + } + }, _options) +} diff --git a/packages/script/src/runtime/registry/maplibre.ts b/packages/script/src/runtime/registry/maplibre.ts new file mode 100644 index 000000000..22196119d --- /dev/null +++ b/packages/script/src/runtime/registry/maplibre.ts @@ -0,0 +1,46 @@ +import type * as MapLibre from 'maplibre-gl' +import type { RegistryScriptInput } from '#nuxt-scripts/types' +import { configureMapLibreWorker, ensureMapLibreStyles, MAPLIBRE_STYLESHEET_URL } from '../maplibre-styles' +import { useRegistryScript } from '../utils' +import { MapLibreOptions } from './schemas' + +export { MapLibreOptions } + +export type MapLibreInput = RegistryScriptInput + +export interface MapLibreApi { + maplibregl: typeof MapLibre +} + +declare global { + interface Window extends MapLibreApi {} +} + +export function useScriptMapLibre(_options?: MapLibreInput) { + return useRegistryScript('maplibre', (options, context) => { + const injectStyles = options?.injectStyles !== false + const stylesheetUrl = options?.stylesheetUrl || MAPLIBRE_STYLESHEET_URL + const usesDefaultSource = !context.scriptInput?.src + + return { + scriptInput: { + src: 'https://unpkg.com/maplibre-gl@5.24.0/dist/maplibre-gl.js', + ...(usesDefaultSource + ? { + integrity: 'sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp', + crossorigin: 'anonymous', + } + : {}), + }, + clientInit: injectStyles ? () => ensureMapLibreStyles(stylesheetUrl) : undefined, + schema: import.meta.dev ? MapLibreOptions : undefined, + scriptOptions: { + use() { + const maplibregl = window.maplibregl + configureMapLibreWorker(maplibregl, options?.workerUrl) + return { maplibregl } + }, + }, + } + }, _options) +} diff --git a/packages/script/src/runtime/registry/schemas.ts b/packages/script/src/runtime/registry/schemas.ts index d5dd19ff2..ea7594bd4 100644 --- a/packages/script/src/runtime/registry/schemas.ts +++ b/packages/script/src/runtime/registry/schemas.ts @@ -19,6 +19,34 @@ export const gcmConsentState = object({ region: optional(array(string())), }) +export const LeafletOptions = object({ + /** + * Inject the Leaflet 1.9.4 styles and default marker images when the script + * begins loading. Disable this when supplying your own Leaflet stylesheet. + * @default true + */ + injectStyles: optional(boolean()), +}) + +export const MapLibreOptions = object({ + /** + * Inject the MapLibre GL JS 5.24.0 stylesheet when the script begins + * loading. Disable this when supplying the stylesheet through Nuxt. + * @default true + */ + injectStyles: optional(boolean()), + /** + * Stylesheet URL used when `injectStyles` is enabled. + * @default 'https://unpkg.com/maplibre-gl@5.24.0/dist/maplibre-gl.css' + */ + stylesheetUrl: optional(string()), + /** + * Worker URL for the CSP-compatible MapLibre build. Pair this with a custom + * `scriptInput.src` that loads `maplibre-gl-csp.js`. + */ + workerUrl: optional(string()), +}) + export const AhrefsAnalyticsOptions = object({ /** * Your Ahrefs Web Analytics project key. Set as the `data-key` attribute diff --git a/packages/script/src/runtime/types.ts b/packages/script/src/runtime/types.ts index 63293ed48..77ccb5934 100644 --- a/packages/script/src/runtime/types.ts +++ b/packages/script/src/runtime/types.ts @@ -24,8 +24,10 @@ import type { GravatarInput } from './registry/gravatar' import type { HotjarInput } from './registry/hotjar' import type { InstagramEmbedInput } from './registry/instagram-embed' import type { IntercomInput } from './registry/intercom' +import type { LeafletInput } from './registry/leaflet' import type { LemonSqueezyInput } from './registry/lemon-squeezy' import type { LinkedInInsightInput } from './registry/linkedin-insight' +import type { MapLibreInput } from './registry/maplibre' import type { MatomoAnalyticsInput } from './registry/matomo-analytics' import type { MetaPixelInput } from './registry/meta-pixel' import type { MixpanelAnalyticsInput } from './registry/mixpanel-analytics' @@ -262,6 +264,8 @@ export interface ScriptRegistry { googleAdsense?: GoogleAdsenseInput googleAnalytics?: GoogleAnalyticsInput googleMaps?: GoogleMapsInput + leaflet?: LeafletInput + maplibre?: MapLibreInput googleRecaptcha?: GoogleRecaptchaInput googleSignIn?: GoogleSignInInput lemonSqueezy?: LemonSqueezyInput @@ -299,7 +303,7 @@ export interface ScriptRegistry { export type BuiltInRegistryScriptKey = | 'ahrefsAnalytics' | 'bingUet' | 'blueskyEmbed' | 'calendly' | 'carbonAds' | 'crisp' | 'clarity' | 'cloudflareWebAnalytics' | 'databuddyAnalytics' | 'metaPixel' | 'fathomAnalytics' | 'instagramEmbed' - | 'plausibleAnalytics' | 'googleAdsense' | 'googleAnalytics' | 'googleMaps' + | 'plausibleAnalytics' | 'googleAdsense' | 'googleAnalytics' | 'googleMaps' | 'leaflet' | 'maplibre' | 'googleRecaptcha' | 'googleSignIn' | 'lemonSqueezy' | 'googleTagManager' | 'hotjar' | 'intercom' | 'linkedinInsight' | 'paypal' | 'posthog' | 'matomoAnalytics' | 'mixpanelAnalytics' | 'rybbitAnalytics' | 'redditPixel' | 'segment' | 'stripe' | 'tiktokPixel' diff --git a/packages/script/src/script-meta.ts b/packages/script/src/script-meta.ts index 5148ebe13..ddab98994 100644 --- a/packages/script/src/script-meta.ts +++ b/packages/script/src/script-meta.ts @@ -177,6 +177,14 @@ export const scriptMeta = { urls: [], // Dynamic URL with API key trackedData: [], }, + leaflet: { + urls: ['https://unpkg.com/leaflet@1.9.4/dist/leaflet.js'], + trackedData: [], + }, + maplibre: { + urls: ['https://unpkg.com/maplibre-gl@5.24.0/dist/maplibre-gl.js'], + trackedData: [], + }, // CDN npm: { diff --git a/playground/nuxt.config.ts b/playground/nuxt.config.ts index f65edd794..7939d1ede 100644 --- a/playground/nuxt.config.ts +++ b/playground/nuxt.config.ts @@ -114,6 +114,8 @@ export default defineNuxtConfig({ // Maps googleMaps: { trigger: 'manual' }, + leaflet: { trigger: false }, + maplibre: { trigger: false }, // Other gravatar: { trigger: 'manual' }, diff --git a/playground/pages/index.vue b/playground/pages/index.vue index 1cdcd0cd2..14268fdb2 100644 --- a/playground/pages/index.vue +++ b/playground/pages/index.vue @@ -45,6 +45,8 @@ function getPlaygroundPath(script: any): string | null { 'vimeo-player': '/third-parties/vimeo/nuxt-scripts', 'youtube-player': '/third-parties/youtube/nuxt-scripts', 'google-maps': '/third-parties/google-maps/nuxt-scripts', + 'leaflet': '/third-parties/leaflet', + 'maplibre-gl-js': '/third-parties/maplibre', 'google-recaptcha': '/third-parties/google-recaptcha/nuxt-scripts', 'calendly': '/third-parties/calendly/nuxt-scripts', 'usercentrics': '/third-parties/usercentrics/nuxt-scripts', diff --git a/playground/pages/third-parties/leaflet.vue b/playground/pages/third-parties/leaflet.vue new file mode 100644 index 000000000..b5d95324e --- /dev/null +++ b/playground/pages/third-parties/leaflet.vue @@ -0,0 +1,337 @@ + + + + + diff --git a/playground/pages/third-parties/maplibre.vue b/playground/pages/third-parties/maplibre.vue new file mode 100644 index 000000000..9cf8841e1 --- /dev/null +++ b/playground/pages/third-parties/maplibre.vue @@ -0,0 +1,475 @@ + + + + + diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5722ac124..ba659e405 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -45,12 +45,18 @@ catalogs: '@speedcurve/lux': specifier: ^4.4.3 version: 4.4.3 + '@types/geojson': + specifier: ^7946.0.16 + version: 7946.0.16 '@types/google.maps': specifier: ^3.65.2 version: 3.65.2 '@types/jest-image-snapshot': specifier: ^6.4.1 version: 6.4.1 + '@types/leaflet': + specifier: ^1.9.21 + version: 1.9.21 '@types/node': specifier: ^26.1.1 version: 26.1.1 @@ -93,6 +99,9 @@ catalogs: magic-string: specifier: ^1.0.0 version: 1.0.0 + maplibre-gl: + specifier: ^5.24.0 + version: 5.24.0 nuxt: specifier: ^4.5.0 version: 4.5.0 @@ -196,12 +205,18 @@ importers: '@paypal/paypal-js': specifier: 'catalog:' version: 10.0.3 + '@types/geojson': + specifier: 'catalog:' + version: 7946.0.16 '@types/google.maps': specifier: 'catalog:' version: 3.65.2 '@types/jest-image-snapshot': specifier: 'catalog:' version: 6.4.1 + '@types/leaflet': + specifier: 'catalog:' + version: 1.9.21 '@types/node': specifier: 'catalog:' version: 26.1.1 @@ -232,6 +247,9 @@ importers: jest-image-snapshot: specifier: 'catalog:' version: 6.5.2 + maplibre-gl: + specifier: 'catalog:' + version: 5.24.0 nuxt: specifier: 'catalog:' version: 4.5.0(@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7(supports-color@10.2.2)))(@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7(supports-color@10.2.2)))(@parcel/watcher@2.5.6)(@types/node@26.1.1)(@vue/compiler-sfc@3.5.40)(cac@6.7.14)(crossws@0.4.6(srvx@0.11.22))(db0@0.3.4)(esbuild@0.28.1)(eslint@10.7.0(jiti@2.7.0)(supports-color@10.2.2))(ioredis@5.11.1(supports-color@10.2.2))(lightningcss@1.32.0)(magicast@0.5.3)(optionator@0.9.4)(oxc-parser@0.140.0)(rollup-plugin-visualizer@7.0.1(rolldown@1.2.0)(rollup@4.62.2))(rollup@4.62.2)(srvx@0.11.22)(supports-color@10.2.2)(terser@5.48.0)(typescript@6.0.3)(vite@8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0))(vue-tsc@3.3.7(typescript@6.0.3))(yaml@2.9.0) @@ -328,9 +346,15 @@ importers: '@stripe/stripe-js': specifier: ^7.0.0 || ^8.0.0 || ^9.0.0 version: 8.11.0 + '@types/geojson': + specifier: ^7946.0.0 + version: 7946.0.16 '@types/google.maps': specifier: ^3.58.1 version: 3.65.2 + '@types/leaflet': + specifier: ^1.9.0 + version: 1.9.21 '@types/vimeo__player': specifier: ^2.18.3 version: 2.18.3 @@ -358,6 +382,9 @@ importers: magic-string: specifier: 'catalog:' version: 1.0.0 + maplibre-gl: + specifier: ^5.24.0 + version: 5.24.0 ofetch: specifier: 'catalog:' version: 1.5.1 @@ -1413,11 +1440,47 @@ packages: '@kwsites/promise-deferred@1.1.1': resolution: {integrity: sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==} + '@mapbox/jsonlint-lines-primitives@2.0.3': + resolution: {integrity: sha512-0SElaV0uMxEnxzBhhX9WTuPyUeMsAN/SS0i16tjuba4/mio63MG9khjC1a0JAiPGXAwvwm4UfHJURCN7nyudQg==} + engines: {node: '>= 22'} + '@mapbox/node-pre-gyp@2.0.3': resolution: {integrity: sha512-uwPAhccfFJlsfCxMYTwOdVfOz3xqyj8xYL3zJj8f0pb30tLohnnFPhLuqp4/qoEz8sNxe4SESZedcBojRefIzg==} engines: {node: '>=18'} hasBin: true + '@mapbox/point-geometry@1.1.0': + resolution: {integrity: sha512-YGcBz1cg4ATXDCM/71L9xveh4dynfGmcLDqufR+nQQy3fKwsAZsWd/x4621/6uJaeB9mwOHE6hPeDgXz9uViUQ==} + + '@mapbox/tiny-sdf@2.2.0': + resolution: {integrity: sha512-LVL4wgI9YAum5V+LNVQO6QgFBPw7/MIIY4XJPNsPDMrjEwcE+JfKk1LuIl8GnF197ejVdC9QdPaxrx5gfgdGXg==} + + '@mapbox/unitbezier@0.0.1': + resolution: {integrity: sha512-nMkuDXFv60aBr9soUG5q+GvZYL+2KZHVvsqFCzqnkGEf46U2fvmytHaEVc1/YZbiLn8X+eR3QzX1+dwDO1lxlw==} + + '@mapbox/unitbezier@1.0.0': + resolution: {integrity: sha512-fqd515fjBmANKGGsQ286E2Wvj/XvDFpGzwJxq4CI6jMQue6Oy04uCKp+JWKF00xRTmk6cEu1jPJ9p3xqH8YWqQ==} + + '@mapbox/vector-tile@2.0.5': + resolution: {integrity: sha512-pXj8m7KTsqZt+1jsE0xIpGvqTSbblfkuEJL/NJmNePMtEwxO8V3XMDo9WMSfDeqHvCtBI9Lmt4mGcGR10zecmw==} + + '@mapbox/whoots-js@3.1.0': + resolution: {integrity: sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q==} + engines: {node: '>=6.0.0'} + + '@maplibre/geojson-vt@6.1.1': + resolution: {integrity: sha512-FVMOcmSP/yqol45t7StApEyTL5/vmqBCuFhH9n+fFuINenhaX+YgHHIt1yJ86S8kln3uJLcMvmEU2cfn6E2eCQ==} + + '@maplibre/maplibre-gl-style-spec@24.10.0': + resolution: {integrity: sha512-lichxSiagMEBBrqHF0trtMQH9RKh+9jUlIJl0qW0QHvt2H/tbvUWdE+ZzI2Jd0/pT7j/iavLonlPu7EQ/ixTOw==} + hasBin: true + + '@maplibre/mlt@1.1.12': + resolution: {integrity: sha512-ZeK5w2TTeHOajcLaEQs1KZXw2V9wIKo1PmThlxlsHoXsQsYlBqLJzPOd6tJHRtGTChUY3DPPmjXRArYVvAbmZw==} + + '@maplibre/vt-pbf@4.3.2': + resolution: {integrity: sha512-j6p0AdjvAR19Z3XaCysle7A4ZSo08tYOzxD0Y9NQylwPAkwJJeYub5b2eVucdeDh7erhv69DahoLOevDRERRUw==} + '@napi-rs/wasm-runtime@1.1.6': resolution: {integrity: sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==} peerDependencies: @@ -3343,6 +3406,9 @@ packages: '@types/katex@0.16.8': resolution: {integrity: sha512-trgaNyfU+Xh2Tc+ABIb44a5AYUpicB3uwirOioeOkNPPbmgRNtcWyDeeFRzjPZENO9Vq8gvVqfhaaXWLlevVwg==} + '@types/leaflet@1.9.21': + resolution: {integrity: sha512-TbAd9DaPGSnzp6QvtYngntMZgcRk+igFELwR2N99XZn7RXUdKgsXMR+28bUO0rPsWp8MIu/f47luLIQuSLYv/w==} + '@types/mdast@4.0.4': resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} @@ -4593,6 +4659,9 @@ packages: duplexer@0.1.2: resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} + earcut@3.2.3: + resolution: {integrity: sha512-vnS4AVwp1KHAF13i1vp1/2D5evWy3k5u/iW/B81QVsUZtV8cv2tU0b2VNFlqvh4kYwrFMDdjPCfAmfyJW9y14Q==} + eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} @@ -5159,6 +5228,9 @@ packages: github-slugger@2.0.0: resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==} + gl-matrix@3.4.4: + resolution: {integrity: sha512-latSnyDNt/8zYUB6VIJ6PCh2jBjJX6gnDsoCZ7LyW7GkqrD51EWwa9qCoGixj8YqBtETQK/xY7OmpTF8xz1DdQ==} + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -5489,6 +5561,9 @@ packages: json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + json-stringify-pretty-compact@4.0.0: + resolution: {integrity: sha512-3CNZ2DnrpByG9Nqj6Xo8vqbjT4F6N+tb4Gb28ESAZjYZ5yqvmc56J+/kuIwkaAMOyblTQhUW7PxMkUb8Q36N3Q==} + json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} @@ -5678,6 +5753,10 @@ packages: magicast@0.5.3: resolution: {integrity: sha512-pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw==} + maplibre-gl@5.24.0: + resolution: {integrity: sha512-ALyFxgtd5R+65UqZ/++lOqwWcC0SNho9c27fYSyLmG7AfnAul2o46F05aDJGPbFU57wos9dgcIySHs0Xe6ia3A==} + engines: {node: '>=16.14.0', npm: '>=8.1.0'} + markdown-table@3.0.4: resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} @@ -5864,6 +5943,9 @@ packages: resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} engines: {node: '>=16 || 14 >=14.17'} + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + minipass@7.1.3: resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} engines: {node: '>=16 || 14 >=14.17'} @@ -5924,6 +6006,9 @@ packages: muggle-string@0.4.1: resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} + murmurhash-js@1.0.0: + resolution: {integrity: sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw==} + nanoid@3.3.15: resolution: {integrity: sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -6176,6 +6261,14 @@ packages: pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + pbf@4.0.2: + resolution: {integrity: sha512-J0ajxARhZfpUEebxYs1vhMGMuLSXtBe1e+fFPDrf2uA2hgo+UshKfNUWOz92HJNz6/NFEXseQPddnHkTreWRqg==} + hasBin: true + + pbf@5.1.2: + resolution: {integrity: sha512-mnvGdvOrIvJOBGUEdGkrVXjN8E/VkIJCkf2eS1DH2yv82ORUlLttmDt0rWY38yYZmVwciZwBUvHM20qxBZf40w==} + hasBin: true + perfect-debounce@2.1.0: resolution: {integrity: sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==} @@ -6569,6 +6662,9 @@ packages: posthog-js@1.404.1: resolution: {integrity: sha512-ck/HOMKuZ6yefUk5OX+h2s9mUWBsnu0mGDUAWjIwAmQQrloZPShzOhOWuEuZQuMnCKORBFRVGsBAuzsl66cBJA==} + potpack@2.1.0: + resolution: {integrity: sha512-pcaShQc1Shq0y+E7GqJqvZj8DTthWV1KeHGdi0Z6IAin2Oi3JnLCOfwnCo84qc+HAp52wT9nK9H7FAJp5a44GQ==} + powershell-utils@0.1.0: resolution: {integrity: sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==} engines: {node: '>=20'} @@ -6646,6 +6742,9 @@ packages: proto-list@1.2.4: resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} + protocol-buffers-schema@3.6.1: + resolution: {integrity: sha512-VG2K63Igkiv9p76tk1lilczEK1cT+kCjKtkdhw1dQZV3k3IXJbd3o6Ho8b9zJZaHSnT2hKe4I+ObmX9w6m5SmQ==} + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -6662,6 +6761,9 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + quickselect@3.0.0: + resolution: {integrity: sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g==} + radix3@1.1.2: resolution: {integrity: sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==} @@ -6741,6 +6843,9 @@ packages: resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + resolve-protobuf-schema@2.1.0: + resolution: {integrity: sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==} + resolve@1.22.11: resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} engines: {node: '>= 0.4'} @@ -7110,6 +7215,9 @@ packages: resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} engines: {node: '>=12.0.0'} + tinyqueue@3.0.0: + resolution: {integrity: sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g==} + tinyrainbow@3.1.0: resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} engines: {node: '>=14.0.0'} @@ -8698,6 +8806,8 @@ snapshots: '@kwsites/promise-deferred@1.1.1': {} + '@mapbox/jsonlint-lines-primitives@2.0.3': {} + '@mapbox/node-pre-gyp@2.0.3(supports-color@10.2.2)': dependencies: consola: 3.4.2 @@ -8711,6 +8821,45 @@ snapshots: - encoding - supports-color + '@mapbox/point-geometry@1.1.0': {} + + '@mapbox/tiny-sdf@2.2.0': {} + + '@mapbox/unitbezier@0.0.1': {} + + '@mapbox/unitbezier@1.0.0': {} + + '@mapbox/vector-tile@2.0.5': + dependencies: + '@mapbox/point-geometry': 1.1.0 + '@types/geojson': 7946.0.16 + pbf: 4.0.2 + + '@mapbox/whoots-js@3.1.0': {} + + '@maplibre/geojson-vt@6.1.1': + dependencies: + kdbush: 4.1.0 + + '@maplibre/maplibre-gl-style-spec@24.10.0': + dependencies: + '@mapbox/jsonlint-lines-primitives': 2.0.3 + '@mapbox/unitbezier': 1.0.0 + json-stringify-pretty-compact: 4.0.0 + minimist: 1.2.8 + quickselect: 3.0.0 + tinyqueue: 3.0.0 + + '@maplibre/mlt@1.1.12': + dependencies: + '@mapbox/point-geometry': 1.1.0 + + '@maplibre/vt-pbf@4.3.2': + dependencies: + '@mapbox/point-geometry': 1.1.0 + '@types/geojson': 7946.0.16 + pbf: 5.1.2 + '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': dependencies: '@emnapi/core': 1.10.0 @@ -10788,6 +10937,10 @@ snapshots: '@types/katex@0.16.8': {} + '@types/leaflet@1.9.21': + dependencies: + '@types/geojson': 7946.0.16 + '@types/mdast@4.0.4': dependencies: '@types/unist': 3.0.3 @@ -12417,6 +12570,8 @@ snapshots: duplexer@0.1.2: {} + earcut@3.2.3: {} + eastasianwidth@0.2.0: {} editorconfig@1.0.7: @@ -13105,6 +13260,8 @@ snapshots: github-slugger@2.0.0: {} + gl-matrix@3.4.4: {} + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -13450,6 +13607,8 @@ snapshots: json-stable-stringify-without-jsonify@1.0.1: {} + json-stringify-pretty-compact@4.0.0: {} + json5@2.2.3: {} jsonc-eslint-parser@3.1.0: @@ -13659,6 +13818,28 @@ snapshots: '@babel/types': 7.29.7 source-map-js: 1.2.1 + maplibre-gl@5.24.0: + dependencies: + '@mapbox/jsonlint-lines-primitives': 2.0.3 + '@mapbox/point-geometry': 1.1.0 + '@mapbox/tiny-sdf': 2.2.0 + '@mapbox/unitbezier': 0.0.1 + '@mapbox/vector-tile': 2.0.5 + '@mapbox/whoots-js': 3.1.0 + '@maplibre/geojson-vt': 6.1.1 + '@maplibre/maplibre-gl-style-spec': 24.10.0 + '@maplibre/mlt': 1.1.12 + '@maplibre/vt-pbf': 4.3.2 + '@types/geojson': 7946.0.16 + earcut: 3.2.3 + gl-matrix: 3.4.4 + kdbush: 4.1.0 + murmurhash-js: 1.0.0 + pbf: 4.0.2 + potpack: 2.1.0 + quickselect: 3.0.0 + tinyqueue: 3.0.0 + markdown-table@3.0.4: {} marked@17.0.6: {} @@ -14043,6 +14224,8 @@ snapshots: dependencies: brace-expansion: 2.1.1 + minimist@1.2.8: {} + minipass@7.1.3: {} minizlib@3.1.0: @@ -14106,6 +14289,8 @@ snapshots: muggle-string@0.4.1: {} + murmurhash-js@1.0.0: {} + nanoid@3.3.15: {} nanotar@0.3.0: {} @@ -14834,6 +15019,14 @@ snapshots: pathe@2.0.3: {} + pbf@4.0.2: + dependencies: + resolve-protobuf-schema: 2.1.0 + + pbf@5.1.2: + dependencies: + resolve-protobuf-schema: 2.1.0 + perfect-debounce@2.1.0: {} picocolors@1.1.1: {} @@ -15220,6 +15413,8 @@ snapshots: query-selector-shadow-dom: 1.0.1 web-vitals: 5.3.0 + potpack@2.1.0: {} + powershell-utils@0.1.0: {} preact@10.29.3: {} @@ -15325,6 +15520,8 @@ snapshots: proto-list@1.2.4: {} + protocol-buffers-schema@3.6.1: {} + punycode@2.3.1: {} quansync@0.2.11: {} @@ -15335,6 +15532,8 @@ snapshots: queue-microtask@1.2.3: {} + quickselect@3.0.0: {} + radix3@1.1.2: {} range-parser@1.3.0: {} @@ -15425,6 +15624,10 @@ snapshots: resolve-pkg-maps@1.0.0: {} + resolve-protobuf-schema@2.1.0: + dependencies: + protocol-buffers-schema: 3.6.1 + resolve@1.22.11: dependencies: is-core-module: 2.16.1 @@ -15853,6 +16056,8 @@ snapshots: fdir: 6.5.0(picomatch@4.0.4) picomatch: 4.0.4 + tinyqueue@3.0.0: {} + tinyrainbow@3.1.0: {} to-regex-range@5.0.1: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 62a3025b5..d94bf8a05 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -59,8 +59,10 @@ catalog: '@shikijs/langs': ^4.3.1 '@shikijs/themes': ^4.3.1 '@speedcurve/lux': ^4.4.3 + '@types/geojson': ^7946.0.16 '@types/google.maps': ^3.65.2 '@types/jest-image-snapshot': ^6.4.1 + '@types/leaflet': ^1.9.21 '@types/node': ^26.1.1 '@types/youtube': ^0.3.0 '@vue/test-utils': ^2.4.11 @@ -75,6 +77,7 @@ catalog: happy-dom: ^20.11.0 jest-image-snapshot: ^6.5.2 magic-string: ^1.0.0 + maplibre-gl: ^5.24.0 nuxt: ^4.5.0 ofetch: ^1.5.1 ohash: ^2.0.11 diff --git a/scripts/generate-registry-types.ts b/scripts/generate-registry-types.ts index 14ca0bdb3..f4e438624 100644 --- a/scripts/generate-registry-types.ts +++ b/scripts/generate-registry-types.ts @@ -252,11 +252,60 @@ function extractDeclarations(source: string, fileName: string): ExtractedDeclara // --- Component props & events extraction --- -const SCRIPT_SETUP_RE = /]*\bsetup\b[^>]*>([\s\S]*?)<\/script>/ +const SETUP_ATTRIBUTE_RE = /\bsetup\b/ -function extractScriptSetup(vueSource: string): string | null { - const match = vueSource.match(SCRIPT_SETUP_RE) - return match?.[1] ?? null +interface VueScriptBlock { + attributes: string + content: string +} + +function extractScriptBlocks(vueSource: string): VueScriptBlock[] { + const normalizedSource = vueSource.toLowerCase() + const blocks: VueScriptBlock[] = [] + let offset = 0 + + while (offset < normalizedSource.length) { + const openStart = normalizedSource.indexOf('', openStart + 7) + if (openEnd === -1) + break + const closeStart = normalizedSource.indexOf('', closeStart + 8) + if (closeEnd === -1) + break + + blocks.push({ + attributes: vueSource.slice(openStart + 7, openEnd), + content: vueSource.slice(openEnd + 1, closeStart), + }) + offset = closeEnd + 1 + } + + return blocks +} + +interface NamedTypeDeclaration { + node: any + source: string +} + +function extractNamedTypeDeclarations(source: string | null, fileName: string): Map { + const declarations = new Map() + if (!source) + return declarations + + const { program } = parseSync(fileName, source) + for (const programNode of program.body) { + const node = programNode.type === 'ExportNamedDeclaration' ? programNode.declaration : programNode + if (node?.type !== 'TSInterfaceDeclaration' && node?.type !== 'TSTypeAliasDeclaration') + continue + declarations.set(node.id.name, { node, source }) + } + return declarations } interface ComponentMeta { @@ -309,18 +358,21 @@ function resolveTSType(node: any, source: string): string { return source.slice(node.start, node.end) } -function extractPropsFields(typeNode: any, source: string, fullSource?: string): SchemaFieldMeta[] { - if (!typeNode || typeNode.type !== 'TSTypeLiteral') +function extractPropsFields(typeNode: any, source: string): SchemaFieldMeta[] { + const members = typeNode?.type === 'TSTypeLiteral' + ? typeNode.members + : typeNode?.type === 'TSInterfaceBody' + ? typeNode.body + : undefined + if (!members) return [] // Parse JSDoc comments from the type literal source text - const typeCode = fullSource - ? fullSource.slice(typeNode.start, typeNode.end) - : source.slice(typeNode.start, typeNode.end) + const typeCode = source.slice(typeNode.start, typeNode.end) const comments = parseSchemaComments(typeCode) const fields: SchemaFieldMeta[] = [] - for (const member of typeNode.members || []) { + for (const member of members) { if (member.type !== 'TSPropertySignature') continue const name = member.key?.name || member.key?.value @@ -338,13 +390,32 @@ function extractPropsFields(typeNode: any, source: string, fullSource?: string): return fields } -function extractComponentMeta(scriptSource: string, fileName: string): ComponentMeta | null { +function extractComponentMeta(scriptSource: string, fileName: string, namedTypes = new Map()): ComponentMeta | null { const { program } = parseSync(fileName, scriptSource) let propsResult: { code: string, defaults: Record, fields: SchemaFieldMeta[] } | null = null const events: SchemaFieldMeta[] = [] const models: SchemaFieldMeta[] = [] const slots: SchemaFieldMeta[] = [] const constArrays: Record = {} + const unresolvedTypeNames = new Set() + + function resolveTypeNode(typeNode: any): { node: any, source: string } { + if (typeNode?.type !== 'TSTypeReference' || typeNode.typeName?.type !== 'Identifier') + return { node: typeNode, source: scriptSource } + + const declaration = namedTypes.get(typeNode.typeName.name) + if (!declaration) { + if (!unresolvedTypeNames.has(typeNode.typeName.name)) { + unresolvedTypeNames.add(typeNode.typeName.name) + console.warn(`[generate-registry-types] Could not resolve referenced type "${typeNode.typeName.name}" in ${fileName}; declare it in the component's