npm package: @route-optimization/vue
Description: Vue 3 composables and components for route optimization and delivery management
Author: ks-arm
Latest version: 1.0.4

# @route-optimization/vue

Vue 3 composables and components for route optimization and delivery management with Google Maps integration.

## Installation

```bash
npm install @route-optimization/vue @route-optimization/core
# or
pnpm add @route-optimization/vue @route-optimization/core
# or
yarn add @route-optimization/vue @route-optimization/core
```

## Features

- ✅ Vue 3 Composition API composables
- ✅ Ready-to-use components with slots for customization
- ✅ TypeScript support with full type definitions
- ✅ Reactive map controls (zoom, center, bounds)
- ✅ Route rendering with automatic bounds fitting
- ✅ Loading and error states built-in
- ✅ Lightweight bundle (~7KB ES, ~5KB CJS)
- ✅ Zero runtime dependencies (except Vue 3 and core)

## Quick Start

### Basic Usage with Component

```vue
<template>
  <div>
    <RouteMapView
      :api-key="googleMapsApiKey"
      :route="deliveryRoute"
      height="500px"
      :auto-fit-bounds="true"
      @map-ready="onMapReady"
      @route-rendered="onRouteRendered"
      @error="onError"
    />
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { RouteMapView } from '@route-optimization/vue';
import type { Route } from '@route-optimization/core';

const googleMapsApiKey = 'YOUR_GOOGLE_MAPS_API_KEY';

const deliveryRoute = ref<Route>({
  id: 'route-1',
  vehicleId: 'vehicle-1',
  stops: [
    {
      id: 'stop-1',
      location: { lat: 13.7563, lng: 100.5018 },
      address: 'Bangkok, Thailand',
      type: 'START',
      sequence: 0,
    },
    {
      id: 'stop-2',
      location: { lat: 13.7467, lng: 100.5342 },
      address: 'Sukhumvit, Bangkok',
      type: 'DELIVERY',
      sequence: 1,
    },
  ],
  totalDistance: 5200,
  totalDuration: 15,
  status: 'pending',
});

const onMapReady = (map: google.maps.Map) => {
  console.log('Map ready:', map);
};

const onRouteRendered = (route: Route) => {
  console.log('Route rendered:', route);
};

const onError = (error: Error) => {
  console.error('Map error:', error);
};
</script>
```

### Using Composables

```vue
<template>
  <div>
    <div ref="mapContainer" style="height: 500px"></div>

    <div v-if="isLoading">Loading map...</div>
    <div v-if="error">Error: {{ error.message }}</div>

    <button @click="renderCurrentRoute" :disabled="!mapInstance">Render Route</button>
    <button @click="clearRoute">Clear</button>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { useRouteMap } from '@route-optimization/vue';
import type { Route } from '@route-optimization/core';

const mapContainer = ref<HTMLElement | null>(null);

const { mapInstance, isLoading, error, renderRoute, clearRoute, fitBounds } = useRouteMap({
  apiKey: 'YOUR_GOOGLE_MAPS_API_KEY',
  mapContainer,
  center: { lat: 13.7563, lng: 100.5018 },
  zoom: 12,
  onMapReady: (map) => {
    console.log('Map initialized:', map);
  },
  onError: (err) => {
    console.error('Map error:', err);
  },
});

const route = ref<Route>({
  // ... your route data
});

const renderCurrentRoute = async () => {
  if (route.value) {
    await renderRoute(route.value);
    fitBounds();
  }
};
</script>
```

### Map Controls

```vue
<template>
  <div style="position: relative">
    <RouteMapView ref="mapViewRef" :api-key="apiKey" :route="route" height="600px" />

    <MapControls
      v-if="mapViewRef?.mapInstance"
      :map-instance="mapViewRef.mapInstance"
      :show-zoom-level="true"
      :show-reset-button="true"
      @zoom-change="onZoomChange"
    >
      <template #zoom-in-icon>
        <PlusIcon />
      </template>
      <template #zoom-out-icon>
        <MinusIcon />
      </template>
    </MapControls>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { RouteMapView, MapControls } from '@route-optimization/vue';

const mapViewRef = ref();
const apiKey = 'YOUR_GOOGLE_MAPS_API_KEY';

const onZoomChange = (zoom: number) => {
  console.log('Zoom level:', zoom);
};
</script>
```

## API Reference

### RouteMapView Component

A complete map view component with route rendering.

**Props:**

| Prop            | Type            | Default                           | Description                        |
| --------------- | --------------- | --------------------------------- | ---------------------------------- |
| `apiKey`        | `string`        | required                          | Google Maps API key                |
| `route`         | `Route \| null` | `null`                            | Route to display on the map        |
| `center`        | `LatLng`        | `{ lat: 13.7563, lng: 100.5018 }` | Initial map center                 |
| `zoom`          | `number`        | `12`                              | Initial zoom level                 |
| `height`        | `string`        | `'500px'`                         | Map container height               |
| `width`         | `string`        | `'100%'`                          | Map container width                |
| `autoFitBounds` | `boolean`       | `true`                            | Auto-fit bounds when route changes |

**Events:**

| Event            | Payload           | Description                     |
| ---------------- | ----------------- | ------------------------------- |
| `map-ready`      | `google.maps.Map` | Emitted when map is initialized |
| `route-rendered` | `Route`           | Emitted when route is rendered  |
| `error`          | `Error`           | Emitted when an error occurs    |

**Slots:**

| Slot      | Description                                  |
| --------- | -------------------------------------------- |
| `loading` | Custom loading indicator                     |
| `error`   | Custom error display (receives `error` prop) |

**Exposed Methods:**

- `mapInstance`: Ref to Google Maps instance
- `renderRoute(route)`: Manually render a route
- `clearRoute()`: Clear current route
- `fitBounds()`: Fit map to route bounds

### MapControls Component

Customizable map zoom controls.

**Props:**

| Prop              | Type                           | Default  | Description             |
| ----------------- | ------------------------------ | -------- | ----------------------- |
| `mapInstance`     | `Ref<google.maps.Map \| null>` | required | Map instance reference  |
| `initialZoom`     | `number`                       | `12`     | Initial zoom level      |
| `minZoom`         | `number`                       | `1`      | Minimum zoom level      |
| `maxZoom`         | `number`                       | `20`     | Maximum zoom level      |
| `showZoomLevel`   | `boolean`                      | `false`  | Show current zoom level |
| `showResetButton` | `boolean`                      | `false`  | Show reset zoom button  |

**Events:**

| Event         | Payload  | Description                   |
| ------------- | -------- | ----------------------------- |
| `zoom-change` | `number` | Emitted when zoom changes     |
| `zoom-in`     | -        | Emitted when zoom in clicked  |
| `zoom-out`    | -        | Emitted when zoom out clicked |
| `reset`       | -        | Emitted when reset clicked    |

**Slots:**

| Slot              | Description                    |
| ----------------- | ------------------------------ |
| `before-controls` | Content before zoom controls   |
| `after-controls`  | Content after zoom controls    |
| `zoom-in-icon`    | Custom zoom in button content  |
| `zoom-out-icon`   | Custom zoom out button content |
| `reset-icon`      | Custom reset button content    |

### useRouteMap Composable

Composable for managing map instance and route rendering.

```typescript
function useRouteMap(options: UseRouteMapOptions): UseRouteMapReturn;

interface UseRouteMapOptions {
  apiKey: string;
  mapContainer: Ref<HTMLElement | null>;
  center?: LatLng;
  zoom?: number;
  onMapReady?: (map: google.maps.Map) => void;
  onError?: (error: Error) => void;
}

interface UseRouteMapReturn {
  mapInstance: Ref<google.maps.Map | null>;
  isLoading: Ref<boolean>;
  error: Ref<Error | null>;
  renderRoute: (route: Route) => Promise<void>;
  clearRoute: () => void;
  fitBounds: () => void;
}
```

### useMapControls Composable

Composable for managing map zoom controls.

```typescript
function useMapControls(
  mapInstance: Ref<google.maps.Map | null>,
  options?: UseMapControlsOptions
): UseMapControlsReturn;

interface UseMapControlsOptions {
  initialZoom?: number;
  minZoom?: number;
  maxZoom?: number;
  onZoomChange?: (zoom: number) => void;
}

interface UseMapControlsReturn {
  zoom: Ref<number>;
  canZoomIn: Ref<boolean>;
  canZoomOut: Ref<boolean>;
  zoomIn: () => void;
  zoomOut: () => void;
  setZoom: (level: number) => void;
  resetZoom: () => void;
}
```

## Advanced Usage

### Custom Loading State

```vue
<RouteMapView :api-key="apiKey" :route="route">
  <template #loading>
    <div class="custom-loader">
      <SpinnerIcon />
      <p>Initializing map...</p>
    </div>
  </template>
</RouteMapView>
```

### Custom Error Handling

```vue
<RouteMapView :api-key="apiKey" :route="route">
  <template #error="{ error }">
    <div class="error-card">
      <ErrorIcon />
      <h3>Map Error</h3>
      <p>{{ error.message }}</p>
      <button @click="retry">Retry</button>
    </div>
  </template>
</RouteMapView>
```

### Dynamic Route Updates

```vue
<script setup lang="ts">
import { ref, watch } from 'vue';
import { RouteMapView } from '@route-optimization/vue';

const currentRoute = ref<Route | null>(null);

// Watch for route changes from your state management
watch(
  () => store.optimizedRoute,
  (newRoute) => {
    currentRoute.value = newRoute;
  }
);
</script>

<template>
  <RouteMapView :api-key="apiKey" :route="currentRoute" :auto-fit-bounds="true" />
</template>
```

## Type Definitions

This package re-exports all types from `@route-optimization/core`:

```typescript
import type { Route, Stop, Vehicle, LatLng, RouteMetrics, StopType } from '@route-optimization/vue';
```

## Bundle Size

- ES Module: ~7.0 KB (2.3 KB gzipped)
- CommonJS: ~5.1 KB (1.9 KB gzipped)
- Includes CSS: ~1.6 KB (0.6 KB gzipped)

## Browser Support

- Modern browsers with ES2020 support
- Vue 3.3.0 or higher required
- Google Maps JavaScript API v3

## Related Packages

- [@route-optimization/core](../core) - Core types and utilities
- [@route-optimization/react](../react) - React hooks and components
- [@route-optimization/converter](../converter) - DirectionsResult format conversion

## License

MIT

## Contributing

Contributions are welcome! Please read our contributing guidelines before submitting PRs.