Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 149 additions & 0 deletions docs/content/scripts/leaflet/1.guides/1.pickup-locator.md
Original file line number Diff line number Diff line change
@@ -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
<script setup lang="ts">
import type { Feature, Polygon } from 'geojson'
import type { LatLngExpression, LatLngTuple } from 'leaflet'

interface PickupLocation {
id: string
name: string
address: string
hours: string
position: LatLngTuple
}

const locations: PickupLocation[] = [
{
id: 'flinders-lane',
name: 'Flinders Lane',
address: 'Centre Place, Melbourne VIC',
hours: 'Open today until 6 pm',
position: [-37.8164, 144.9656],
},
{
id: 'queen-victoria-market',
name: 'Queen Victoria Market',
address: 'Queen Street, Melbourne VIC',
hours: 'Open today until 3 pm',
position: [-37.8076, 144.9568],
},
{
id: 'southbank',
name: 'Southbank',
address: 'Southbank Promenade, Southbank VIC',
hours: 'Open today until 8 pm',
position: [-37.8202, 144.9655],
},
]

const selectedId = ref(locations[0]!.id)
const center = ref<LatLngExpression>(locations[0]!.position)
const zoom = ref(14)

const deliveryZone: Feature<Polygon> = {
type: 'Feature',
properties: { name: 'Same-day delivery zone' },
geometry: {
type: 'Polygon',
coordinates: [[
[144.946, -37.802],
[144.982, -37.802],
[144.986, -37.828],
[144.951, -37.832],
[144.946, -37.802],
]],
},
}

function selectLocation(location: PickupLocation) {
selectedId.value = location.id
center.value = location.position
zoom.value = 16
}
</script>

<template>
<div class="pickup-locator">
<aside aria-labelledby="pickup-title">
<h2 id="pickup-title">
Pickup in Melbourne
</h2>

<ol>
<li v-for="location in locations" :key="location.id">
<button
type="button"
:aria-pressed="selectedId === location.id"
@click="selectLocation(location)"
>
<strong>{{ location.name }}</strong>
<span>{{ location.address }}</span>
<small>{{ location.hours }}</small>
</button>
</li>
</ol>
</aside>

<ScriptLeafletMap
v-model:center="center"
v-model:zoom="zoom"
width="100%"
:height="520"
aria-label="Pickup locations and delivery zone in central Melbourne"
>
<template #error>
<p role="alert">
The map is unavailable. Choose a pickup location from the list.
</p>
</template>

<ScriptLeafletTileLayer
url="https://tile.openstreetmap.org/{z}/{x}/{y}.png"
:options="{
maxZoom: 19,
attribution: '&copy; <a href=&quot;https://www.openstreetmap.org/copyright&quot;>OpenStreetMap contributors</a>',
}"
/>

<ScriptLeafletGeoJson
:data="deliveryZone"
:options="{
style: {
color: '#15803d',
fillColor: '#86efac',
fillOpacity: 0.18,
weight: 2,
},
}"
/>

<ScriptLeafletMarker
v-for="location in locations"
:key="location.id"
:position="location.position"
:alt="`${location.name} pickup location`"
:title="location.name"
@click="selectLocation(location)"
>
<ScriptLeafletPopup
:open="selectedId === location.id"
:options="{ minWidth: 180 }"
>
<strong>{{ location.name }}</strong><br>
{{ location.address }}<br>
{{ location.hours }}
</ScriptLeafletPopup>
</ScriptLeafletMarker>
</ScriptLeafletMap>
</div>
</template>
```

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]`.
80 changes: 80 additions & 0 deletions docs/content/scripts/leaflet/1.guides/2.tiles-and-attribution.md
Original file line number Diff line number Diff line change
@@ -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
<ScriptLeafletTileLayer
url="https://tile.openstreetmap.org/{z}/{x}/{y}.png"
:options="{
maxZoom: 19,
attribution: '&copy; <a href=&quot;https://www.openstreetmap.org/copyright&quot;>OpenStreetMap contributors</a>',
}"
/>
```

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
<script setup lang="ts">
const config = useRuntimeConfig()
const tileAttribution = '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap contributors</a>'
</script>

<template>
<ScriptLeafletTileLayer
:url="config.public.mapTileUrl"
:options="{ attribution: tileAttribution }"
/>
</template>
```

## 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
<ScriptLeafletMap :center="[-37.8136, 144.9631]" :zoom="13">
<template #error>
<p role="alert">
The map is unavailable. Use the location list to choose a store.
</p>
</template>
</ScriptLeafletMap>
```
Original file line number Diff line number Diff line change
@@ -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
<ScriptLeafletMap :center="[-37.8136, 144.9631]" :zoom="13" />
```

### Immediate Loading

Load immediately when the map is the page's primary content:

```vue
<ScriptLeafletMap
trigger="immediate"
:center="[-37.8136, 144.9631]"
:zoom="13"
/>
```

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
<ScriptLeafletMap
width="100%"
height="clamp(24rem, 60vw, 36rem)"
:center="[-37.8136, 144.9631]"
/>
```

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
<ScriptLeafletMap
:center="[-37.8136, 144.9631]"
:zoom="13"
aria-label="Pickup locations in central Melbourne"
>
<ScriptLeafletMarker
:position="[-37.8164, 144.9656]"
alt="Flinders Lane pickup location"
/>
</ScriptLeafletMap>
```

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
<ScriptLeafletMap
:center="[-37.8136, 144.9631]"
:zoom="13"
:interactive="false"
/>
```

## 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
<ScriptLeafletMap
:center="[-37.8136, 144.9631]"
:inject-styles="false"
/>
```

Use the same approach when an inline-style Content Security Policy prevents the default stylesheet injection.
22 changes: 22 additions & 0 deletions docs/content/scripts/leaflet/2.api/1.script-leaflet-map.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
title: <ScriptLeafletMap>
---

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
<ScriptLeafletMap
v-model:center="center"
v-model:zoom="zoom"
width="100%"
height="28rem"
@ready="({ map }) => console.log(map.value)"
/>
```

The `placeholder`, `awaitingLoad`, `loading`, and `error` slots customize each loading state. The default error state is visible and announced with `role="alert"`.
10 changes: 10 additions & 0 deletions docs/content/scripts/leaflet/2.api/2.tile-layer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
title: <ScriptLeafletTileLayer>
---

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`.
10 changes: 10 additions & 0 deletions docs/content/scripts/leaflet/2.api/3.marker.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
title: <ScriptLeafletMarker>
---

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 [`<ScriptLeafletPopup>`{lang="html"}](/scripts/leaflet/api/popup) in the default slot to bind it to the marker.
8 changes: 8 additions & 0 deletions docs/content/scripts/leaflet/2.api/4.popup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
title: <ScriptLeafletPopup>
---

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"}
::
10 changes: 10 additions & 0 deletions docs/content/scripts/leaflet/2.api/5.geojson.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
title: <ScriptLeafletGeoJson>
---

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.
Loading
Loading