# MapVX Website Component

A modern, framework-agnostic web component built with Angular 22 that provides an interactive map and location discovery interface.

📖 **Documentation (Spanish and English): https://docs.mapvx.app/ssr-angular-component/**

## 🚀 Features

- ✅ Compatible with any framework (React, Vue, Vanilla JS, etc.)
- ✅ Self-contained: one ES module (`main.js`, which lazy-loads its `chunk-*.js` files) plus one stylesheet
- ✅ Scoped styles: renders in the light DOM (no Shadow DOM), with Angular's emulated style encapsulation
- ✅ Angular 22 with modern architecture (standalone components, zoneless)
- 🔑 **Requires API Key**: The component needs a valid API key to function

## 📦 Installation

### Via NPM

```bash
npm install @mapvx/web-components-ssr
```

After installation, you can reference the files in your application:

**JavaScript/TypeScript:**

```javascript
// Import the main component file
import '@mapvx/web-components-ssr/dist/browser/main.js'
// Import the styles
import '@mapvx/web-components-ssr/dist/browser/styles.css'
```

**HTML:**

```html
<!-- Include the component files. `type="module"` is required: the bundle is an
     ES module that lazy-loads sibling `chunk-*.js` files from the same folder. -->
<script type="module" src="node_modules/@mapvx/web-components-ssr/dist/browser/main.js"></script>
<link rel="stylesheet" href="node_modules/@mapvx/web-components-ssr/dist/browser/styles.css" />
```

**Webpack/Vite/Bundler:**

```javascript
// In your main entry file
import '@mapvx/web-components-ssr/dist/browser/main.js'
import '@mapvx/web-components-ssr/dist/browser/styles.css'
```

**Framework-specific examples:**

**React:**

```jsx
// In your main App.jsx or index.jsx
import '@mapvx/web-components-ssr/dist/browser/main.js'

function App() {
  return <mapvx-website api-key="your-api-key-here" />
}
```

```css
/* In your main CSS file (e.g., index.css, App.css) */
@import '@mapvx/web-components-ssr/dist/browser/styles.css';
```

**Vue:**

```javascript
// In your main.js
import '@mapvx/web-components-ssr/dist/browser/main.js'
```

```css
/* In your main CSS file (e.g., style.css, main.css) */
@import '@mapvx/web-components-ssr/dist/browser/styles.css';
```

**Angular:**

#### Method 1: HTML Script Tags

Configure assets in `angular.json` and use script tags in `index.html`:

```json
{
  "assets": [
    {
      "glob": "**/*",
      "input": "public"
    },
    {
      "glob": "**/*",
      "input": "src/assets",
      "output": "assets"
    },
    {
      "glob": "**/*",
      "input": "node_modules/@mapvx/web-components-ssr/dist/browser",
      "output": "website-component"
    }
  ]
}
```

Then reference the files in your `index.html`:

```html
<!-- Web Component Scripts. `type="module"` is required: the bundle is an ES module
     that lazy-loads sibling `chunk-*.js` files, so it must not be a classic script
     (a classic script fails with "Unexpected token 'export'"). `defer` is implicit
     for modules, and the `glob: "**/*"` asset rule above already copies the chunks. -->
<script type="module" src="website-component/main.js"></script>

<!-- Web Component Styles -->
<link rel="stylesheet" href="website-component/styles.css" />
```

**Why this configuration is needed:**

- Angular doesn't serve files from `node_modules` by default in development
- Without proper asset configuration, you'll get `SyntaxError: Unexpected token '<'` errors
- This ensures files are served as static assets with proper caching headers

#### Method 2: TypeScript Imports

Alternatively, you can import the files in your application:

```typescript
// In your main.ts - Import the JavaScript
import '@mapvx/web-components-ssr/dist/browser/main.js'
```

```scss
/* In your styles.scss - Import the styles (Modern Sass) */
@use '@mapvx/web-components-ssr/dist/browser/styles.css';

/* Alternative for older Sass versions or CSS files */
@import '@mapvx/web-components-ssr/dist/browser/styles.css';
```

**Important**: Choose ONE method only:

- ✅ **Method 1**: HTML script tags + assets configuration
- ✅ **Method 2**: TypeScript imports + SCSS imports
- ❌ **Don't use both**: This can cause duplicate loading and conflicts

### Helper Package (SSR Support)

For Server-Side Rendering (SSR) applications that need to preload initial data on the server:

```bash
npm install @mapvx/web-components-ssr-helpers
```

### Via CDN (unpkg)

```html
<!-- Include the component files. `type="module"` is required: the bundle is an
     ES module that lazy-loads sibling `chunk-*.js` files from the same URL prefix. -->
<script
  type="module"
  src="https://unpkg.com/@mapvx/web-components-ssr@latest/dist/browser/main.js"
></script>
<link
  rel="stylesheet"
  href="https://unpkg.com/@mapvx/web-components-ssr@latest/dist/browser/styles.css"
/>
```

## 🎯 Usage

### Basic Usage

```html
<!-- Basic implementation with required API key -->
<mapvx-website api-key="your-api-key-here"></mapvx-website>
```

### Advanced Usage with Configuration

```html
<!-- With all available options -->
<mapvx-website
  api-key="your-api-key-here"
  institution-id="optional-institution-id"
  initial-data='{"custom": "data"}'
  default-to-map="true"
  show-category-filters="false"
  show-city-filter="true"
  inherit-font-family="false"
>
</mapvx-website>
```

### Framework Integration

#### React

```jsx
function App() {
  return (
    <mapvx-website
      api-key="your-api-key-here"
      default-to-map="true"
      show-category-filters="false"
    />
  )
}
```

#### Vue

```vue
<template>
  <mapvx-website
    api-key="your-api-key-here"
    :default-to-map="true"
    :show-category-filters="false"
  />
</template>
```

#### Angular

```typescript
// In your component
@Component({
  template: `
    <mapvx-website
      [attr.api-key]="apiKey"
      [attr.default-to-map]="defaultToMap"
      [attr.show-category-filters]="showFilters"
    />
  `,
})
export class MyComponent {
  apiKey = 'your-api-key-here'
  defaultToMap = true
  showFilters = false
}
```

## 📋 Input Properties

The web component accepts the following input properties to customize its behavior:

| Property                  | Type      | Required   | Default                         | Description                                                                                       |
| ------------------------- | --------- | ---------- | ------------------------------- | ------------------------------------------------------------------------------------------------- |
| `api-key`                 | `string`  | ✅ **Yes** | -                               | Valid API key to initialize the SDK                                                               |
| `institution-id`          | `string`  | ✅ **Yes** | -                               | Institution ID for specific data filtering and search                                             |
| `initial-data`            | `string`  | ❌ No      | -                               | JSON string with initial data (use `prepareInitialData` from `@mapvx/web-components-ssr-helpers`) |
| `default-to-map`          | `boolean` | ❌ No      | `false`                         | If `true`, shows map view by default                                                              |
| `show-category-filters`   | `boolean` | ❌ No      | `true`                          | If `true`, shows category filters                                                                 |
| `show-city-filter`        | `boolean` | ❌ No      | `true`                          | If `true`, shows the city picker. Hiding it keeps the city scope, it only takes away the control  |
| `show-view-toggle`        | `boolean` | ❌ No      | `true`                          | If `true`, shows the Grid View / Map View toggle                                                  |
| `show-search-bar`         | `boolean` | ❌ No      | `true`                          | If `true`, shows the search bar                                                                   |
| `show-drop-pin`           | `boolean` | ❌ No      | `false`                         | If `true`, shows the map button that drops a pin to route from                                    |
| `selected-categories`     | `string`  | ❌ No      | `''`                            | Categories to filter by, comma-separated, for a host driving the filter itself                    |
| `search-term`             | `string`  | ❌ No      | `''`                            | Term to search for, for a host driving the search itself. Works with `show-search-bar="false"`    |
| `full-width-map`          | `boolean` | ❌ No      | `false`                         | If `true`, the map fills the width in map view and the card list beside it is hidden              |
| `zoom`                    | `number`  | ❌ No      | configured                      | Zoom level the map opens at, overriding the one configured for the place                          |
| `center`                  | `string`  | ❌ No      | configured                      | Where the map is centred, as `lat,lng`, overriding the configured centre                          |
| `hide-unselected-markers` | `boolean` | ❌ No      | `false`                         | If `true`, while a place profile is open only that place's marker stays on the map                |
| `inherit-font-family`     | `boolean` | ❌ No      | `false`                         | If `true`, inherits font family from parent                                                       |
| `hide-deals`              | `boolean` | ❌ No      | `false`                         | If `true`, hides deals and promotional content                                                    |
| `show-deal-labels`        | `boolean` | ❌ No      | `false`                         | If `true`, shows deal labels over their images on the home screen                                 |
| `card-width`              | `number`  | ❌ No      | `237` (desktop), `170` (mobile) | Custom card width in pixels for grid view. Affects card size and number of cards per row          |
| `rows-per-page`           | `number`  | ❌ No      | `2`                             | Maximum number of rows per page (1-10). Shows fewer rows if not enough cards to fill all rows     |

### Input Usage Examples

#### Required Properties Only

```html
<mapvx-website api-key="your-api-key-here" institution-id="institution-123"></mapvx-website>
```

#### With Optional Configuration

```html
<mapvx-website
  api-key="your-api-key-here"
  institution-id="institution-123"
  default-to-map="true"
  show-category-filters="false"
  show-city-filter="true"
  hide-deals="true"
  show-deal-labels="true"
  card-width="300"
>
</mapvx-website>
```

#### Custom Card Width

```html
<!-- Larger cards (300px) -->
<mapvx-website api-key="your-api-key-here" institution-id="institution-123" card-width="300">
</mapvx-website>

<!-- Smaller cards (200px) -->
<mapvx-website api-key="your-api-key-here" institution-id="institution-123" card-width="200">
</mapvx-website>
```

#### Custom Rows Per Page

```html
<!-- Show 3 rows per page -->
<mapvx-website api-key="your-api-key-here" institution-id="institution-123" rows-per-page="3">
</mapvx-website>

<!-- Show 1 row per page (minimal) -->
<mapvx-website api-key="your-api-key-here" institution-id="institution-123" rows-per-page="1">
</mapvx-website>

<!-- Combine with custom card width -->
<mapvx-website
  api-key="your-api-key-here"
  institution-id="institution-123"
  card-width="250"
  rows-per-page="4"
>
</mapvx-website>
```

## 📤 Output Events

The web component emits custom events that you can listen to for user interactions:

| Event Name         | Type                    | Description                                                                                                                                           |
| ------------------ | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| `cardSelected`     | `string`                | ⚠️ **Deprecated**: Use `userAction` with type `show-place` instead. Emitted when a location card is selected. Contains the card ID.                   |
| `userAction`       | `UserAction`            | Emitted when a user performs an action (filter selection, search, place navigation, etc.). Contains action type and associated data.                  |
| `categoriesChange` | `CategoryFiltersData[]` | Emitted when the categories on offer change. The resolved list — unreachable ones dropped, `All` added — for a host drawing its own category control. |
| `droppedPinChange` | `LatLng`                | Emitted when the visitor drops the pin or drags it somewhere else. Carries the coordinate it ended up on, ready to write back into `center`.          |
| `citiesChange`     | `CityChoice[]`          | Emitted when the cities the view can be scoped to change. Each entry carries the value to write back into `institution-id`.                           |

### Output Usage Examples

#### Vanilla JavaScript

```javascript
// Wait for the web component to be registered
function setupUserActionListener() {
  const element = document.querySelector('mapvx-website')
  if (element) {
    element.addEventListener('userAction', (event) => {
      if (event instanceof CustomEvent && event.detail.type === 'show-place') {
        const placeId = event.detail.data.placeId
        console.log('Place shown:', placeId)
        // Handle place display
      }
    })
  } else {
    // Retry if element is not yet available
    setTimeout(setupUserActionListener, 100)
  }
}

// Check if web component is already registered
if (customElements.get('mapvx-website')) {
  setupUserActionListener()
} else {
  // Wait for registration
  const checkInterval = setInterval(() => {
    if (customElements.get('mapvx-website')) {
      setupUserActionListener()
      clearInterval(checkInterval)
    }
  }, 100)
}
```

#### Update Browser URL with `history.pushState`

You can react to user actions to change the browser URL without a full page reload, which is especially useful in SSR apps looking to keep client-side navigation in sync with the selected place.

```html
<script>
  // Wait for the web component to be registered
  function setupUrlUpdater() {
    const element = document.querySelector('mapvx-website')
    if (!element) {
      setTimeout(setupUrlUpdater, 100)
      return
    }

    const handleUserAction = (event) => {
      if (event instanceof CustomEvent) {
        const action = event.detail
        switch (action.type) {
          case 'return-to-home':
          case 'select-filter': {
            const { filter } = action.data
            history.pushState(
              { page: 'home', filter },
              '',
              `${location.pathname}?tab=${encodeURIComponent(filter)}`,
            )
            break
          }
          case 'show-place': {
            const { placeId, alias } = action.data
            if (alias) {
              history.pushState(
                { page: 'profile', id: alias },
                '',
                `${location.pathname}?tenant=${alias}`,
              )
            } else {
              history.pushState(
                { page: 'profile', id: placeId },
                '',
                `${location.pathname}?tenant=${placeId}`,
              )
            }
            break
          }
          case 'search': {
            const { searchTerm } = action.data
            history.pushState(
              { page: 'search', term: searchTerm },
              '',
              `${location.pathname}?search=${encodeURIComponent(searchTerm)}`,
            )
            break
          }
          case 'select-destination': {
            const { destinationId } = action.data
            history.pushState(
              { page: 'route', id: destinationId },
              '',
              `${location.pathname}?destination=${destinationId}`,
            )
            break
          }
        }
      }
    }

    element.addEventListener('userAction', handleUserAction)
  }

  // Check if web component is already registered
  if (customElements.get('mapvx-website')) {
    setupUrlUpdater()
  } else {
    // Wait for registration
    const waitForRegistration = setInterval(() => {
      if (customElements.get('mapvx-website')) {
        clearInterval(waitForRegistration)
        setupUrlUpdater()
      }
    }, 100)
  }
</script>
```

#### React

```jsx
import { useEffect, useRef } from 'react'

function App() {
  const webComponentRef = useRef(null)

  useEffect(() => {
    const element = webComponentRef.current
    if (!element) return

    const handleUserAction = (event) => {
      if (event instanceof CustomEvent && event.detail.type === 'show-place') {
        const placeId = event.detail.data.placeId
        console.log('Place shown:', placeId)
        // Handle place display
      }
    }

    element.addEventListener('userAction', handleUserAction)

    return () => {
      element.removeEventListener('userAction', handleUserAction)
    }
  }, [])

  return <mapvx-website ref={webComponentRef} api-key="your-api-key-here" />
}
```

#### Vue

```vue
<template>
  <mapvx-website ref="webComponent" api-key="your-api-key-here" />
</template>

<script setup>
import { ref, onMounted, onUnmounted } from 'vue'

const webComponent = ref(null)

const handleUserAction = (event) => {
  if (event instanceof CustomEvent && event.detail.type === 'show-place') {
    const placeId = event.detail.data.placeId
    console.log('Place shown:', placeId)
    // Handle place display
  }
}

onMounted(() => {
  if (webComponent.value) {
    webComponent.value.addEventListener('userAction', handleUserAction)
  }
})

onUnmounted(() => {
  if (webComponent.value) {
    webComponent.value.removeEventListener('userAction', handleUserAction)
  }
})
</script>
```

#### Angular

```typescript
import { Component, ElementRef, OnDestroy, ViewChild, AfterViewInit } from '@angular/core'

@Component({
  selector: 'app-mapvx',
  template: ` <mapvx-website #webComponent [attr.api-key]="apiKey" /> `,
})
export class MapvxComponent implements AfterViewInit, OnDestroy {
  @ViewChild('webComponent', { static: false }) webComponentRef!: ElementRef<HTMLElement>

  apiKey = 'your-api-key-here'
  private userActionListener?: (event: Event) => void

  ngAfterViewInit() {
    this.setupUserActionListener()
  }

  private setupUserActionListener() {
    const element = this.webComponentRef?.nativeElement
    if (!element) {
      // Retry if element is not yet available
      setTimeout(() => this.setupUserActionListener(), 100)
      return
    }

    this.userActionListener = (event: Event) => {
      if (event instanceof CustomEvent) {
        const action = event.detail as { type: string; data: any }
        if (action.type === 'show-place') {
          const placeId = action.data.placeId
          console.log('Place shown:', placeId)
          // Handle place display
        }
      }
    }

    element.addEventListener('userAction', this.userActionListener)
  }

  ngOnDestroy() {
    const element = this.webComponentRef?.nativeElement
    if (element && this.userActionListener) {
      element.removeEventListener('userAction', this.userActionListener)
    }
  }
}
```

### User Action Event

The `userAction` event provides detailed information about user interactions within the component. The event detail contains an object with `type` and `data` properties.

#### UserAction Types

| Type | Description | Data Structure |
| -------------------- | ------------------------------------------ | --------------------------------------- | ------------ |
| `select-filter` | Emitted when a filter is selected | `{ filter: string }` |
| `show-place` | Emitted when a place is displayed | `{ placeId: string, alias: string       | undefined }` |
| `select-destination` | Emitted when a destination is selected | `{ destinationId: string, alias: string | undefined }` |
| `search` | Emitted when a search is performed | `{ searchTerm: string }` |
| `return-to-home` | Emitted when user returns to the home view | `{ filter: string }` |

#### UserAction Usage Examples

#### Vanilla JavaScript

```javascript
// Wait for the web component to be registered
function setupUserActionListener() {
  const element = document.querySelector('mapvx-website')
  if (element) {
    element.addEventListener('userAction', (event) => {
      if (event instanceof CustomEvent) {
        const action = event.detail
        console.log('User action:', action.type, action.data)

        // Handle different action types
        switch (action.type) {
          case 'select-filter':
            console.log('Filter selected:', action.data.filter)
            break
          case 'show-place':
            console.log('Place shown:', action.data.placeId)
            break
          case 'select-destination':
            console.log('Destination selected:', action.data.destinationId)
            break
          case 'search':
            console.log('Search performed:', action.data.searchTerm)
            break
          case 'return-to-home':
            console.log('Returned to home with filter:', action.data.filter)
            break
        }
      }
    })
  } else {
    // Retry if element is not yet available
    setTimeout(setupUserActionListener, 100)
  }
}

// Check if web component is already registered
if (customElements.get('mapvx-website')) {
  setupUserActionListener()
} else {
  // Wait for registration
  const checkInterval = setInterval(() => {
    if (customElements.get('mapvx-website')) {
      setupUserActionListener()
      clearInterval(checkInterval)
    }
  }, 100)
}
```

#### React

```jsx
import { useEffect, useRef } from 'react'

function App() {
  const webComponentRef = useRef(null)

  useEffect(() => {
    const element = webComponentRef.current
    if (!element) return

    const handleUserAction = (event) => {
      if (event instanceof CustomEvent) {
        const action = event.detail
        console.log('User action:', action.type, action.data)

        // Handle different action types
        switch (action.type) {
          case 'select-filter':
            // Handle filter selection
            break
          case 'show-place':
            // Handle place display
            break
          case 'search':
            // Handle search
            break
          // ... other cases
        }
      }
    }

    element.addEventListener('userAction', handleUserAction)

    return () => {
      element.removeEventListener('userAction', handleUserAction)
    }
  }, [])

  return <mapvx-website ref={webComponentRef} api-key="your-api-key-here" />
}
```

#### Vue

```vue
<template>
  <mapvx-website ref="webComponent" api-key="your-api-key-here" />
</template>

<script setup>
import { ref, onMounted, onUnmounted } from 'vue'

const webComponent = ref(null)

const handleUserAction = (event) => {
  if (event instanceof CustomEvent) {
    const action = event.detail
    console.log('User action:', action.type, action.data)

    // Handle different action types
    switch (action.type) {
      case 'select-filter':
        // Handle filter selection
        break
      case 'show-place':
        // Handle place display
        break
      case 'search':
        // Handle search
        break
      // ... other cases
    }
  }
}

onMounted(() => {
  if (webComponent.value) {
    webComponent.value.addEventListener('userAction', handleUserAction)
  }
})

onUnmounted(() => {
  if (webComponent.value) {
    webComponent.value.removeEventListener('userAction', handleUserAction)
  }
})
</script>
```

#### Angular

```typescript
import { Component, ElementRef, OnDestroy, ViewChild, AfterViewInit } from '@angular/core'

@Component({
  selector: 'app-mapvx',
  template: ` <mapvx-website #webComponent [attr.api-key]="apiKey" /> `,
})
export class MapvxComponent implements AfterViewInit, OnDestroy {
  @ViewChild('webComponent', { static: false }) webComponentRef!: ElementRef<HTMLElement>

  apiKey = 'your-api-key-here'
  private userActionListener?: (event: Event) => void

  ngAfterViewInit() {
    this.setupUserActionListener()
  }

  private setupUserActionListener() {
    const element = this.webComponentRef?.nativeElement
    if (!element) {
      // Retry if element is not yet available
      setTimeout(() => this.setupUserActionListener(), 100)
      return
    }

    this.userActionListener = (event: Event) => {
      if (event instanceof CustomEvent) {
        const action = event.detail as { type: string; data: any }
        console.log('User action:', action.type, action.data)

        // Handle different action types
        switch (action.type) {
          case 'select-filter':
            // Handle filter selection
            break
          case 'show-place':
            // Handle place display
            break
          case 'search':
            // Handle search
            break
          // ... other cases
        }
      }
    }

    element.addEventListener('userAction', this.userActionListener)
  }

  ngOnDestroy() {
    const element = this.webComponentRef?.nativeElement
    if (element && this.userActionListener) {
      element.removeEventListener('userAction', this.userActionListener)
    }
  }
}
```

## 🔧 Server-Side Rendering (SSR) Support

For applications using Server-Side Rendering (SSR) where you need to preload initial data on the server before sending it to the browser:

### Installation

```bash
npm install @mapvx/web-components-ssr-helpers
```

### Server-Side Usage

#### Next.js (App Router)

**1. Install dependencies:**

```bash
npm install @mapvx/web-components-ssr @mapvx/web-components-ssr-helpers
```

**2. Copy the bundle script to your `package.json`:**

```json
{
  "scripts": {
    "copy-bundle": "node copy-bundle.mjs"
  }
}
```

**3. Create `copy-bundle.mjs` in your project root:**

```javascript
#!/usr/bin/env node

import { existsSync, mkdirSync, copyFileSync, readdirSync } from 'fs'
import { join } from 'path'

// The bundle is an ES module that lazy-loads sibling `chunk-*.js` files via relative
// `import('./chunk-*.js')` calls, so every .js file must be copied and the chunks must
// keep their original names. Copying main.js alone leaves the component unable to boot.
const sourceDir = 'node_modules/@mapvx/web-components-ssr/dist/browser'
const sourcePath = join(sourceDir, 'main.js')
const targetDir = 'public/mapvx-website'
const targetPath = join(targetDir, 'bundle.js')

if (!existsSync(sourcePath)) {
  console.error(`❌ Source file not found: ${sourcePath}`)
  process.exit(1)
}

if (!existsSync(targetDir)) {
  mkdirSync(targetDir, { recursive: true })
}

copyFileSync(sourcePath, targetPath)

for (const chunk of readdirSync(sourceDir).filter((f) => f.endsWith('.js') && f !== 'main.js')) {
  copyFileSync(join(sourceDir, chunk), join(targetDir, chunk))
}

console.log(`✅ Bundle copied successfully!`)
```

**4. Create your page component:**

```typescript
// app/page.tsx
import { prepareInitialData } from '@mapvx/web-components-ssr-helpers';
import '@mapvx/web-components-ssr/dist/browser/styles.css';
import Script from 'next/script';

export default async function HomePage() {
  const initialData = await prepareInitialData(process.env.NEXT_PUBLIC_MAPVX_API_KEY || '');

  return (
    <div>
      <Script type='module' src='/mapvx-website/bundle.js' />
      <mapvx-website
        api-key={process.env.NEXT_PUBLIC_MAPVX_API_KEY}
        institution-id={process.env.NEXT_PUBLIC_MAPVX_INSTITUTION_ID}
        initial-data={initialData}
        default-to-map="true"
      />
    </div>
  );
}
```

**5. Set environment variables in `.env.local`:**

```bash
NEXT_PUBLIC_MAPVX_API_KEY=your-api-key-here
NEXT_PUBLIC_MAPVX_INSTITUTION_ID=your-institution-id
```

**6. Run the copy script before building:**

```bash
npm run copy-bundle
npm run build
```

#### Nuxt.js

```vue
<!-- pages/index.vue -->
<template>
  <div>
    <h1>My App</h1>
    <mapvx-website :api-key="apiKey" :initial-data="initialData" default-to-map="true" />
  </div>
</template>

<script setup>
import { prepareInitialData } from '@mapvx/web-components-ssr-helpers'

// Server-side data fetching
const { data: initialData } = await useAsyncData('mapvx-data', async () => {
  return await prepareInitialData(process.env.MAPVX_API_KEY)
})

const apiKey = process.env.MAPVX_API_KEY
</script>
```

#### SvelteKit

```typescript
// src/routes/+page.server.ts
import { prepareInitialData } from '@mapvx/web-components-ssr-helpers'
import type { PageServerLoad } from './$types'

export const load: PageServerLoad = async () => {
  // Execute on the server
  const initialData = await prepareInitialData(process.env.MAPVX_API_KEY!)

  return {
    initialData,
  }
}
```

```svelte
<!-- src/routes/+page.svelte -->
<script lang="ts">
  import type { PageData } from './$types';

  export let data: PageData;
</script>

<div>
  <h1>My App</h1>
  <mapvx-website
    api-key={process.env.MAPVX_API_KEY}
    initial-data={data.initialData}
    default-to-map="true"
  />
</div>
```

### What `prepareInitialData` does

The `prepareInitialData` function is specifically designed for SSR applications:

- **Server-side execution**: Must be called on the server, not in the browser
- **Data fetching**: Fetches available places and institutions from the API
- **Performance optimization**: Preloads data on the server to avoid client-side API calls
- **SEO benefits**: Ensures data is available during server-side rendering
- **Returns**: JSON string with combined places and institutions data

### Important Notes

⚠️ **SSR Only**: This function should only be used in server-side rendering contexts. For client-side applications, the web component will fetch data automatically when needed.

## 🎨 Customization

### Custom Colors

The web component supports custom color theming through CSS custom properties. You can override the default colors by defining CSS variables in your application's root:

```css
:root {
  --mapvx-secondary-orange: #b41500;
  --mapvx-secondary-blue: #053a96;
}
```

#### Example Implementation

**HTML with inline styles:**

```html
<style>
  :root {
    --mapvx-secondary-orange: #ff6b35;
    --mapvx-secondary-blue: #1e3a8a;
  }
</style>

<mapvx-website api-key="your-api-key-here"></mapvx-website>
```

**CSS file:**

```css
/* styles.css */
:root {
  --mapvx-secondary-orange: #e74c3c;
  --mapvx-secondary-blue: #3498db;
}
```

**Framework-specific examples:**

**React:**

```jsx
// In your CSS file or styled-components
:root {
  --mapvx-secondary-orange: #ff6b35;
  --mapvx-secondary-blue: #1e3a8a;
}

function App() {
  return <mapvx-website api-key="your-api-key-here" />;
}
```

**Vue:**

```vue
<template>
  <mapvx-website api-key="your-api-key-here" />
</template>

<style>
:root {
  --mapvx-secondary-orange: #ff6b35;
  --mapvx-secondary-blue: #1e3a8a;
}
</style>
```

**Angular:**

```scss
// styles.scss
:root {
  --mapvx-secondary-orange: #ff6b35;
  --mapvx-secondary-blue: #1e3a8a;
}
```

### Available Color Variables

| Variable                   | Default Value | Description                                |
| -------------------------- | ------------- | ------------------------------------------ |
| `--mapvx-secondary-orange` | `#EE5845`     | Secondary orange color used in UI elements |
| `--mapvx-secondary-blue`   | `#2C57A0`     | Secondary blue color used in UI elements   |

### Notes

- Colors are applied globally when defined in `:root`
- The web component will automatically pick up these custom colors
- Use valid CSS color values (hex, rgb, hsl, etc.)

## 🔧 Troubleshooting

### Common Issues

#### `SyntaxError: Unexpected token 'export'`

**Problem**: The bundle is loaded as a classic script. Since `1.3.0` `main.js` is an ES module
that lazy-loads sibling `chunk-*.js` files, so a plain `<script src="…/main.js">` fails to parse.
Nothing on the page runs, `customElements.define('mapvx-website', …)` is never reached, and the
`<mapvx-website>` element stays empty — no list, no map, and no other error in the console.

**Solution**: add `type="module"` to the script tag (this replaces the old `defer`, which modules
apply on their own):

```html
<script
  type="module"
  src="https://unpkg.com/@mapvx/web-components-ssr@1.6.0/dist/browser/main.js"
></script>
<link
  rel="stylesheet"
  href="https://unpkg.com/@mapvx/web-components-ssr@1.6.0/dist/browser/styles.css"
/>
```

When self-hosting the bundle instead of using a CDN, copy every `.js` file out of
`dist/browser` keeping the `chunk-*.js` names: the relative `import()` calls in `main.js` resolve
against its own URL.

#### `SyntaxError: Unexpected token '<'` in Angular

**Problem**: Angular returns HTML instead of JavaScript files from `node_modules`.

**Solution**: Configure assets in `angular.json` as shown in the Angular Configuration section above.

#### Slow Loading (>10 seconds)

**Problem**: Large bundle size or incorrect asset serving.

**Solutions**:

- Use the recommended Angular assets configuration
- Ensure files are served as static assets, not through routing
- Check network tab in DevTools for 404 errors

#### Web Component Not Loading

**Problem**: Scripts not loading or component not registering.

**Solutions**:

- Verify file paths are correct
- Check browser console for errors
- Ensure the script tag carries `type="module"` (see the `Unexpected token 'export'` entry above)
- Ensure the `chunk-*.js` files sit next to `main.js` and are reachable (404s in the network tab)

## 🏠 Homepage

Visit [MapVX](https://mapvx.com) for more information.
