@deciosfernandes/mapbox-v3-gl-style-switcher
v1.2.1
Published
A modern, fully-typed TypeScript control for Mapbox GL JS with dropdown-style interface for switching between map styles
Maintainers
Readme
Mapbox GL JS Style Switcher
A modern, fully-typed TypeScript control for Mapbox GL JS that provides an intuitive dropdown-style interface for switching between map styles.
✨ Features
- 🎨 Pre-configured styles - Comes with 5 popular Mapbox styles out of the box
- 🔧 Fully customizable - Use your own styles and configurations
- 🎯 TypeScript first - Built with modern TypeScript, full type safety
- 📱 Responsive design - Works seamlessly on desktop and mobile
- 🎪 Event-driven - Rich event system for custom interactions
- 🚀 Modern syntax - Leverages optional chaining and nullish coalescing
- 🛡️ Error resilient - Robust error handling and validation
- 📦 Zero dependencies - Only requires Mapbox GL JS
🚀 Installation
npm i @deciosfernandes/mapbox-v3-gl-style-switcher --saveyarn add @deciosfernandes/mapbox-v3-gl-style-switcherpnpm add @deciosfernandes/mapbox-v3-gl-style-switcher📋 Requirements
- Mapbox GL JS v3.x
- TypeScript 5.0+ (for TypeScript projects)
- Node.js 16+ (for development)
🎯 Quick Start
import { MapboxStyleSwitcherControl } from '@deciosfernandes/mapbox-v3-gl-style-switcher';
import { Map as MapboxMap } from 'mapbox-gl';
// Import the CSS styles
import '@deciosfernandes/mapbox-v3-gl-style-switcher/styles.css';
// Create your map
const map = new MapboxMap({
container: 'map',
accessToken: 'your-mapbox-token',
style: 'mapbox://styles/mapbox/streets-v12',
center: [-74.5, 40],
zoom: 9,
});
// Add the style switcher control
map.addControl(new MapboxStyleSwitcherControl());🎨 Default Styles
The control includes these pre-configured Mapbox styles:
| Style Name | Description | URI |
| ------------- | ------------------------------ | ---------------------------------------------- |
| Streets | Standard street map (default) | mapbox://styles/mapbox/streets-v12 |
| Light | Clean, minimal light theme | mapbox://styles/mapbox/light-v11 |
| Dark | Elegant dark theme | mapbox://styles/mapbox/dark-v11 |
| Outdoors | Perfect for outdoor activities | mapbox://styles/mapbox/outdoors-v12 |
| Satellite | Satellite imagery with streets | mapbox://styles/mapbox/satellite-streets-v12 |
🔧 Configuration
Custom Styles
import { MapboxStyleDefinition, MapboxStyleSwitcherControl } from '@deciosfernandes/mapbox-v3-gl-style-switcher';
const customStyles: MapboxStyleDefinition[] = [
{
title: 'Custom Dark',
uri: 'mapbox://styles/your-username/your-dark-style-id',
},
{
title: 'Custom Light',
uri: 'mapbox://styles/your-username/your-light-style-id',
},
{
title: 'Vintage',
uri: 'mapbox://styles/your-username/vintage-style-id',
},
];
map.addControl(new MapboxStyleSwitcherControl(customStyles));Advanced Configuration
import { MapboxStyleSwitcherOptions } from '@deciosfernandes/mapbox-v3-gl-style-switcher';
const options: MapboxStyleSwitcherOptions = {
defaultStyle: 'Custom Dark',
eventListeners: {
onOpen: (event) => {
console.log('Style selector opened');
// Return true to prevent default behavior
return false;
},
onSelect: (event) => {
console.log('Style button clicked');
return false;
},
onChange: (event, styleUri) => {
console.log(`Map style changed to: ${styleUri}`);
// Analytics tracking example
analytics.track('style_changed', { style: styleUri });
},
},
};
map.addControl(new MapboxStyleSwitcherControl(customStyles, options));Backward Compatible Syntax
// Simple string-based default style (legacy support)
map.addControl(new MapboxStyleSwitcherControl(customStyles, 'Dark'));📖 API Reference
MapboxStyleSwitcherControl
Constructor
constructor(
styles?: MapboxStyleDefinition[],
options?: MapboxStyleSwitcherOptions | string
)Parameters:
styles- Optional array of custom style definitions. Defaults to built-in Mapbox stylesoptions- Configuration options object, or string for backward compatibility (default style name)
Public Methods
getCurrentStyle(): MapboxStyleDefinition | null
Returns the currently active style definition.
const control = new MapboxStyleSwitcherControl();
map.addControl(control);
// Later in your code
const currentStyle = control.getCurrentStyle();
console.log(`Current style: ${currentStyle?.title}`);setStyle(styleName: string): boolean
Programmatically changes the map style.
const success = control.setStyle('Dark');
if (success) {
console.log('Style changed successfully');
} else {
console.log('Style not found or change failed');
}getStyles(): ReadonlyArray<MapboxStyleDefinition>
Returns all available style definitions.
const availableStyles = control.getStyles();
console.log(
'Available styles:',
availableStyles.map((s) => s.title),
);getDefaultPosition(): ControlPosition
Returns the default control position ('top-right').
Interfaces
MapboxStyleDefinition
interface MapboxStyleDefinition {
/** Display title for the style */
title: string;
/** Mapbox style URI */
uri: string;
}MapboxStyleSwitcherOptions
interface MapboxStyleSwitcherOptions {
/** Default style to be selected on initialization */
defaultStyle?: string;
/** Event listeners for style switcher interactions */
eventListeners?: MapboxStyleSwitcherEvents;
}MapboxStyleSwitcherEvents
interface MapboxStyleSwitcherEvents {
/** Fired when the style selector is opened */
onOpen?: (event: MouseEvent) => boolean;
/** Fired when a style button is clicked */
onSelect?: (event: MouseEvent) => boolean;
/** Fired after the map style is changed */
onChange?: (event: MouseEvent, style: string) => void;
}Event Handler Return Values (onOpen, onSelect):
- Return
trueto prevent the default behavior - Return
falseorundefinedto allow normal processing
onChangefires after the style has already changed and has no return value.
💡 Advanced Examples
Dynamic Style Management
class MapManager {
private map: MapboxMap;
private styleControl: MapboxStyleSwitcherControl;
constructor() {
this.map = new MapboxMap({
/* config */
});
this.styleControl = new MapboxStyleSwitcherControl(this.getCustomStyles(), {
defaultStyle: 'Corporate',
eventListeners: {
onChange: this.handleStyleChange.bind(this),
},
});
this.map.addControl(this.styleControl);
}
private getCustomStyles(): MapboxStyleDefinition[] {
return [
{ title: 'Corporate', uri: 'mapbox://styles/company/corporate' },
{ title: 'Presentation', uri: 'mapbox://styles/company/presentation' },
{ title: 'Analysis', uri: 'mapbox://styles/company/analysis' },
];
}
private handleStyleChange(event: MouseEvent, styleUri: string): void {
// Custom logic when style changes
this.updateUI(styleUri);
this.saveUserPreference(styleUri);
}
public switchToAnalysisMode(): void {
this.styleControl.setStyle('Analysis');
}
}Integration with UI Frameworks
React Example
import React, { useEffect, useRef } from 'react';
import mapboxgl from 'mapbox-gl';
import { MapboxStyleSwitcherControl } from '@deciosfernandes/mapbox-v3-gl-style-switcher';
const MapComponent: React.FC = () => {
const mapContainer = useRef<HTMLDivElement>(null);
const map = useRef<mapboxgl.Map | null>(null);
useEffect(() => {
if (!mapContainer.current) return;
map.current = new mapboxgl.Map({
container: mapContainer.current,
style: 'mapbox://styles/mapbox/streets-v12',
center: [-74.5, 40],
zoom: 9,
});
const styleControl = new MapboxStyleSwitcherControl(undefined, {
defaultStyle: 'Streets',
eventListeners: {
onChange: (event, style) => {
console.log('React: Style changed to', style);
},
},
});
map.current.addControl(styleControl);
return () => {
map.current?.remove();
};
}, []);
return (
<div
ref={mapContainer}
style={{ width: '100%', height: '400px' }}
/>
);
};
export default MapComponent;Vue Example
<template>
<div
ref="mapContainer"
class="map-container"></div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue';
import mapboxgl from 'mapbox-gl';
import { MapboxStyleSwitcherControl } from '@deciosfernandes/mapbox-v3-gl-style-switcher';
const mapContainer = ref<HTMLDivElement>();
let map: mapboxgl.Map | null = null;
onMounted(() => {
if (!mapContainer.value) return;
map = new mapboxgl.Map({
container: mapContainer.value,
style: 'mapbox://styles/mapbox/streets-v12',
center: [-74.5, 40],
zoom: 9,
});
const styleControl = new MapboxStyleSwitcherControl();
map.addControl(styleControl);
});
onUnmounted(() => {
map?.remove();
});
</script>
<style scoped>
.map-container {
width: 100%;
height: 400px;
}
</style>🎨 Styling & CSS
The control uses CSS classes that can be customized:
/* Main control container */
.mapboxgl-ctrl.mapboxgl-ctrl-group {
/* Your custom styles */
}
/* Style switcher button */
.mapboxgl-style-switcher {
/* Button styles */
}
/* Style list dropdown */
.mapboxgl-style-list {
/* Dropdown styles */
}
/* Individual style buttons */
.mapboxgl-style-list button {
/* Style button styles */
}
/* Active style button */
.mapboxgl-style-list button.active {
/* Active state styles */
}🛠️ Development
Setup
git clone https://github.com/deciosfernandes/style-switcher.git
cd style-switcher
npm installBuild
npm run buildDevelopment Workflow
The project uses modern TypeScript 5.9.3 with:
- Optional chaining and nullish coalescing
- Strict type checking
- ES2020 target with full ES2020 library features
- Comprehensive JSDoc documentation
🤝 Contributing
Contributions are welcome! Please read our contributing guidelines:
- Fork the repository
- Create a feature branch:
git checkout -b feature/amazing-feature - Commit your changes:
git commit -m 'Add amazing feature' - Push to the branch:
git push origin feature/amazing-feature - Open a Pull Request
Development Guidelines
- Follow TypeScript strict mode conventions
- Add JSDoc comments for public APIs
- Include unit tests for new features
- Update documentation for API changes
- Ensure backward compatibility when possible
📄 License
This project is licensed under the GPL-3.0 License - see the LICENSE file for details.
🙏 Acknowledgments
- Original concept by Eliz Kilic
- Built for the Mapbox GL JS ecosystem
- TypeScript definitions inspired by the Mapbox GL JS type definitions
Made with ❤️ for the Mapbox community
