map-gl-style-switcher 0.7.2 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -13,14 +13,17 @@ A TypeScript control for switching Mapbox GL / MapLibre GL map styles. Easily ad
13
13
 
14
14
  - `StyleSwitcherControl` - Direct IControl implementation for Mapbox/MapLibre GL
15
15
  - `MapGLStyleSwitcher` - React component for react-map-gl integration
16
+ _Live demo of the style switcher control in action_
16
17
 
17
18
  **<a href="https://map-gl-style-switcher.netlify.app/" target="_blank">🌐 Live Demo</a>**
18
19
 
19
- ## Demo
20
+ ## Animated Demo
20
21
 
21
22
  ![Demo GIF](./images/demo.gif)
22
23
 
23
- _Live demo of the style switcher control in action_
24
+ ## Available Styles
25
+
26
+ ![Available Styles](./images/styles.png)
24
27
 
25
28
  ## Features
26
29
 
@@ -51,7 +54,131 @@ yarn add map-gl-style-switcher
51
54
  pnpm add map-gl-style-switcher
52
55
  ```
53
56
 
54
- ## Usage
57
+ ## CDN Usage
58
+
59
+ For quick prototyping or when you don't want to use a build system, you can include the package directly from a CDN:
60
+
61
+ ### unpkg CDN and jsDelivr CDN
62
+
63
+ ```html
64
+ <!DOCTYPE html>
65
+ <html>
66
+ <head>
67
+ <!-- jsDelivr CDN -->
68
+ <!-- MapLibre GL CSS -->
69
+ <!-- <link href="https://cdn.jsdelivr.net/npm/maplibre-gl@4/dist/maplibre-gl.css" rel="stylesheet" /> -->
70
+
71
+ <!-- Style Switcher CSS -->
72
+ <!-- <link
73
+ rel="stylesheet"
74
+ href="https://cdn.jsdelivr.net/npm/map-gl-style-switcher@latest/dist/map-gl-style-switcher.css"
75
+ /> -->
76
+ <!-- MapLibre GL CSS -->
77
+ <link href="https://unpkg.com/maplibre-gl@4/dist/maplibre-gl.css" rel="stylesheet" />
78
+
79
+ <!-- Style Switcher CSS -->
80
+ <link
81
+ rel="stylesheet"
82
+ href="https://unpkg.com/map-gl-style-switcher@latest/dist/map-gl-style-switcher.css"
83
+ />
84
+ </head>
85
+ <body>
86
+ <div id="map" style="width: 100%; height: 100vh;"></div>
87
+ <!-- jsDelivr CDN -->
88
+ <!-- MapLibre GL JS -->
89
+ <!-- <script src="https://cdn.jsdelivr.net/npm/maplibre-gl@4/dist/maplibre-gl.js"></script> -->
90
+
91
+ <!-- Style Switcher UMD -->
92
+ <!-- <script src="https://cdn.jsdelivr.net/npm/map-gl-style-switcher@latest/dist/index.umd.js"></script> -->
93
+ <!-- MapLibre GL JS -->
94
+ <script src="https://unpkg.com/maplibre-gl@4/dist/maplibre-gl.js"></script>
95
+
96
+ <!-- Style Switcher UMD -->
97
+ <script src="https://unpkg.com/map-gl-style-switcher@latest/dist/index.umd.js"></script>
98
+
99
+ <script>
100
+ // StyleSwitcherControl is available globally as MapGLStyleSwitcher.StyleSwitcherControl
101
+ const { StyleSwitcherControl } = MapGLStyleSwitcher;
102
+
103
+ const styles = [
104
+ {
105
+ id: 'voyager',
106
+ name: 'Voyager',
107
+ image: 'https://github.com/muimsd/map-gl-style-switcher/tree/main/public/voyager.png',
108
+ styleUrl: 'https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json',
109
+ description: 'Voyager style from Carto',
110
+ },
111
+ {
112
+ id: 'positron',
113
+ name: 'Positron',
114
+ image: 'https://github.com/muimsd/map-gl-style-switcher/tree/main/public/positron.png',
115
+ styleUrl: 'https://basemaps.cartocdn.com/gl/positron-gl-style/style.json',
116
+ description: 'Positron style from Carto',
117
+ },
118
+ ];
119
+
120
+ const map = new maplibregl.Map({
121
+ container: 'map',
122
+ style: styles[0].styleUrl,
123
+ center: [0, 0],
124
+ zoom: 2,
125
+ });
126
+
127
+ const styleSwitcher = new StyleSwitcherControl({
128
+ styles: styles,
129
+ theme: 'auto',
130
+ activeStyleId: styles[0].id,
131
+ onAfterStyleChange: (from, to) => {
132
+ map.setStyle(to.styleUrl);
133
+ },
134
+ });
135
+
136
+ map.addControl(styleSwitcher, 'bottom-left');
137
+ </script>
138
+ </body>
139
+ </html>
140
+ ```
141
+
142
+ ### ES Modules from CDN
143
+
144
+ For modern browsers that support ES modules:
145
+
146
+ ```html
147
+ <script type="module">
148
+ import { StyleSwitcherControl } from 'https://unpkg.com/map-gl-style-switcher@latest/dist/index.js';
149
+
150
+ // Your code here
151
+ const styleSwitcher = new StyleSwitcherControl({
152
+ styles: styles,
153
+ theme: 'auto',
154
+ });
155
+ </script>
156
+ ```
157
+
158
+ ### Entry Point Guide
159
+
160
+ #### For Vanilla JavaScript/TypeScript Users
161
+
162
+ ```ts
163
+ import { StyleSwitcherControl } from 'map-gl-style-switcher';
164
+ // No React dependencies required
165
+ ```
166
+
167
+ #### For React with Custom Map Instance
168
+
169
+ ```tsx
170
+ import { useStyleSwitcher } from 'map-gl-style-switcher/react';
171
+ // Pure React hook - works with any map instance (MapLibre, Mapbox, etc.)
172
+ // Only requires: react (no react-map-gl dependency)
173
+ ```
174
+
175
+ #### For React with react-map-gl
176
+
177
+ ```tsx
178
+ import { MapGLStyleSwitcher } from 'map-gl-style-switcher/react-map-gl';
179
+ // React component using react-map-gl's useControl
180
+ // Requires: react + react-map-gl
181
+ ```
55
182
 
56
183
  ### Basic MapLibre GL Integration
57
184
 
@@ -65,28 +192,28 @@ const styles = [
65
192
  {
66
193
  id: 'voyager',
67
194
  name: 'Voyager',
68
- image: './voyager.png',
195
+ image: 'https://github.com/muimsd/map-gl-style-switcher/tree/main/public/voyager.png',
69
196
  styleUrl: 'https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json',
70
197
  description: 'Voyager style from Carto',
71
198
  },
72
199
  {
73
200
  id: 'positron',
74
201
  name: 'Positron',
75
- image: './positron.png',
202
+ image: 'https://github.com/muimsd/map-gl-style-switcher/tree/main/public/positron.png',
76
203
  styleUrl: 'https://basemaps.cartocdn.com/gl/positron-gl-style/style.json',
77
204
  description: 'Positron style from Carto',
78
205
  },
79
206
  {
80
207
  id: 'dark-matter',
81
208
  name: 'Dark Matter',
82
- image: './dark.png',
209
+ image: 'https://github.com/muimsd/map-gl-style-switcher/tree/main/public/dark.png',
83
210
  styleUrl: 'https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json',
84
211
  description: 'Dark style from Carto',
85
212
  },
86
213
  {
87
214
  id: 'arcgis-hybrid',
88
215
  name: 'ArcGIS Hybrid',
89
- image: './arcgis-hybrid.png',
216
+ image: 'https://github.com/muimsd/map-gl-style-switcher/tree/main/public/arcgis-hybrid.png',
90
217
  styleUrl:
91
218
  'https://raw.githubusercontent.com/go2garret/maps/main/src/assets/json/arcgis_hybrid.json',
92
219
  description: 'Hybrid Satellite style from ESRI',
@@ -94,7 +221,7 @@ const styles = [
94
221
  {
95
222
  id: 'osm',
96
223
  name: 'OSM',
97
- image: './osm.png',
224
+ image: 'https://github.com/muimsd/map-gl-style-switcher/tree/main/public/osm.png',
98
225
  styleUrl:
99
226
  'https://raw.githubusercontent.com/go2garret/maps/main/src/assets/json/openStreetMap.json',
100
227
  description: 'OSM style',
@@ -127,42 +254,102 @@ const styleSwitcher = new StyleSwitcherControl({
127
254
  map.addControl(styleSwitcher, 'bottom-left');
128
255
  ```
129
256
 
257
+ ### React Hook for Custom Map Instances
258
+
259
+ For React applications where you manage the map instance yourself (e.g., with `useEffect`), use the `useStyleSwitcher` hook:
260
+
261
+ ```tsx
262
+ import React, { useEffect, useRef } from 'react';
263
+ import { useStyleSwitcher } from 'map-gl-style-switcher/react';
264
+ import maplibregl from 'maplibre-gl';
265
+ import 'map-gl-style-switcher/dist/map-gl-style-switcher.css';
266
+
267
+ const styles = [
268
+ {
269
+ id: 'voyager',
270
+ name: 'Voyager',
271
+ image: 'https://github.com/muimsd/map-gl-style-switcher/tree/main/public/voyager.png',
272
+ styleUrl: 'https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json',
273
+ description: 'Voyager style from Carto',
274
+ },
275
+ {
276
+ id: 'positron',
277
+ name: 'Positron',
278
+ image: 'https://github.com/muimsd/map-gl-style-switcher/tree/main/public/positron.png',
279
+ styleUrl: 'https://basemaps.cartocdn.com/gl/positron-gl-style/style.json',
280
+ description: 'Positron style from Carto',
281
+ },
282
+ ];
283
+
284
+ function MyMapComponent() {
285
+ const mapContainer = useRef<HTMLDivElement>(null);
286
+ const map = useRef<maplibregl.Map | null>(null);
287
+
288
+ useEffect(() => {
289
+ if (!mapContainer.current) return;
290
+
291
+ map.current = new maplibregl.Map({
292
+ container: mapContainer.current,
293
+ style: styles[0].styleUrl,
294
+ center: [0, 0],
295
+ zoom: 2,
296
+ });
297
+
298
+ return () => map.current?.remove();
299
+ }, []);
300
+
301
+ // Add style switcher control to the map
302
+ useStyleSwitcher(map.current, {
303
+ styles,
304
+ theme: 'auto',
305
+ position: 'top-right',
306
+ onAfterStyleChange: (from, to) => {
307
+ if (map.current) {
308
+ map.current.setStyle(to.styleUrl);
309
+ }
310
+ },
311
+ });
312
+
313
+ return <div ref={mapContainer} style={{ width: '100%', height: '400px' }} />;
314
+ }
315
+ ```
316
+
130
317
  ### React Integration with react-map-gl
131
318
 
132
- For React applications using `react-map-gl`, This package provides a ready-to-use `MapGLStyleSwitcher` component:
319
+ For React applications using `react-map-gl`, this package provides a ready-to-use `MapGLStyleSwitcher` component:
133
320
 
134
321
  ```tsx
135
322
  import React, { useState } from 'react';
136
323
  import { Map } from 'react-map-gl/maplibre';
137
- import { MapGLStyleSwitcher } from 'map-gl-style-switcher';
324
+ import { MapGLStyleSwitcher } from 'map-gl-style-switcher/react-map-gl';
138
325
  import 'map-gl-style-switcher/dist/map-gl-style-switcher.css';
139
326
 
140
327
  const styles = [
141
328
  {
142
329
  id: 'voyager',
143
330
  name: 'Voyager',
144
- image: './voyager.png',
331
+ image: 'https://github.com/muimsd/map-gl-style-switcher/tree/main/public/voyager.png',
145
332
  styleUrl: 'https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json',
146
333
  description: 'Voyager style from Carto',
147
334
  },
148
335
  {
149
336
  id: 'positron',
150
337
  name: 'Positron',
151
- image: './positron.png',
338
+ image: 'https://github.com/muimsd/map-gl-style-switcher/tree/main/public/positron.png',
152
339
  styleUrl: 'https://basemaps.cartocdn.com/gl/positron-gl-style/style.json',
153
340
  description: 'Positron style from Carto',
154
341
  },
155
342
  {
156
343
  id: 'dark-matter',
157
344
  name: 'Dark Matter',
158
- image: './dark.png',
345
+ image: 'https://github.com/muimsd/map-gl-style-switcher/tree/main/public/dark.png',
159
346
  styleUrl: 'https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json',
160
347
  description: 'Dark style from Carto',
161
348
  },
162
349
  {
163
350
  id: 'arcgis-hybrid',
164
351
  name: 'ArcGIS Hybrid',
165
- image: './arcgis-hybrid.png',
352
+ image: 'https://github.com/muimsd/map-gl-style-switcher/tree/main/public/arcgis-hybrid.png',
166
353
  styleUrl:
167
354
  'https://raw.githubusercontent.com/go2garret/maps/main/src/assets/json/arcgis_hybrid.json',
168
355
  description: 'Hybrid Satellite style from ESRI',
@@ -170,7 +357,7 @@ const styles = [
170
357
  {
171
358
  id: 'osm',
172
359
  name: 'OSM',
173
- image: './osm.png',
360
+ image: 'https://github.com/muimsd/map-gl-style-switcher/tree/main/public/osm.png',
174
361
  styleUrl:
175
362
  'https://raw.githubusercontent.com/go2garret/maps/main/src/assets/json/openStreetMap.json',
176
363
  description: 'OSM style',
@@ -205,12 +392,6 @@ export const MapComponent = () => {
205
392
  };
206
393
  ```
207
394
 
208
- **Installation for React:**
209
-
210
- ```sh
211
- npm install react-map-gl maplibre-gl map-gl-style-switcher
212
- ```
213
-
214
395
  #### MapGLStyleSwitcher Props
215
396
 
216
397
  ```tsx
@@ -284,6 +465,8 @@ const MapComponent = () => {
284
465
  };
285
466
  ```
286
467
 
468
+ **Note:** This hook automatically handles adding/removing the control when the map or options change.
469
+
287
470
  ## Examples
288
471
 
289
472
  ### React + Vite + TypeScript Example
@@ -302,94 +485,9 @@ This example demonstrates:
302
485
  - MapLibre GL integration with react-map-gl
303
486
  - MapGLStyleSwitcher component usage
304
487
  - Multiple basemap styles with thumbnails
305
- - Responsive design with auto theme detection
306
- - Style switching with smooth transitions
307
488
 
308
489
  [View the complete example →](examples/react-map-gl/)
309
490
 
310
- ### Vanilla JavaScript Example
311
-
312
- The main demo at the root level demonstrates vanilla JavaScript usage:
313
-
314
- ```bash
315
- npm install
316
- npm run dev
317
- ```
318
-
319
- This example shows:
320
-
321
- - Pure TypeScript/JavaScript implementation
322
- - MapLibre GL integration
323
- - StyleSwitcherControl direct usage
324
- - Multiple themes and configuration options
325
-
326
- ## Available Styles
327
-
328
- ![Available Styles](./images/styles.png)
329
-
330
- The style switcher supports various map styles. Here are some popular options you can use:
331
-
332
- ### Carto Basemaps
333
-
334
- - **Voyager** - A balanced, colorful style perfect for data visualization and general mapping applications
335
- - **Positron** - A clean, minimal light basemap ideal for overlaying data with maximum contrast
336
- - **Dark Matter** - A dark theme basemap excellent for creating striking data visualizations and night mode interfaces
337
-
338
- ### Satellite & Hybrid
339
-
340
- - **ArcGIS Hybrid** - Combines satellite imagery with street labels, perfect for geographic context and navigation
341
- - **Satellite** - High-resolution satellite imagery for detailed geographic analysis
342
-
343
- ### OpenStreetMap
344
-
345
- - **OSM (OpenStreetMap)** - Community-driven open-source mapping with detailed street-level information
346
-
347
- ### Example Styles Configuration
348
-
349
- ```ts
350
- const styles = [
351
- {
352
- id: 'voyager',
353
- name: 'Voyager',
354
- image: './voyager.png',
355
- styleUrl: 'https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json',
356
- description: 'Voyager style from Carto',
357
- },
358
- {
359
- id: 'positron',
360
- name: 'Positron',
361
- image: './positron.png',
362
- styleUrl: 'https://basemaps.cartocdn.com/gl/positron-gl-style/style.json',
363
- description: 'Positron style from Carto',
364
- },
365
- {
366
- id: 'dark-matter',
367
- name: 'Dark Matter',
368
- image: './dark.png',
369
- styleUrl: 'https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json',
370
- description: 'Dark style from Carto',
371
- },
372
- {
373
- id: 'arcgis-hybrid',
374
- name: 'ArcGIS Hybrid',
375
- image: './arcgis-hybrid.png',
376
- styleUrl:
377
- 'https://raw.githubusercontent.com/go2garret/maps/main/src/assets/json/arcgis_hybrid.json',
378
- description: 'Hybrid Satellite style from ESRI',
379
- },
380
- {
381
- id: 'osm',
382
- name: 'OSM',
383
- image: './osm.png',
384
- styleUrl:
385
- 'https://raw.githubusercontent.com/go2garret/maps/main/src/assets/json/openStreetMap.json',
386
- description: 'OSM style',
387
- },
388
- ];
389
- ```
390
-
391
- > **Note**: The `description` property is optional and will be shown as a tooltip when users hover over a style option.
392
-
393
491
  ## Configuration Options
394
492
 
395
493
  ```ts
@@ -463,100 +561,6 @@ const styleSwitcher = new StyleSwitcherControl({
463
561
 
464
562
  See the default class names in the `StyleSwitcherControl` source for all available keys.
465
563
 
466
- ## File Structure
467
-
468
- ```
469
- map-gl-style-switcher/
470
- ├── src/
471
- │ ├── components/
472
- │ │ ├── StyleSwitcherControl.ts # Main IControl implementation
473
- │ │ └── MapGLStyleSwitcher.tsx # React component for react-map-gl
474
- │ ├── styles/
475
- │ │ └── style-switcher.css # Control styles (themes, RTL support)
476
- │ ├── types/
477
- │ │ ├── index.ts # TypeScript type definitions
478
- │ │ └── css.d.ts # CSS module declarations
479
- │ ├── tests/
480
- │ │ ├── StyleSwitcherControl.test.ts # Core control test suite
481
- │ │ ├── MapGLStyleSwitcher.test.tsx # React component test suite
482
- │ │ └── test-setup.ts # Jest test setup
483
- │ ├── demo/
484
- │ │ ├── main.tsx # Demo entry point
485
- │ │ ├── App.tsx # Demo component
486
- │ │ └── index.css # Demo-specific styles
487
- │ └── index.ts # Package entry point
488
- ├── examples/
489
- │ └── react-map-gl/ # Complete React example
490
- │ ├── src/
491
- │ │ ├── App.tsx # Example React application
492
- │ │ └── main.tsx # React entry point
493
- │ ├── package.json # Example dependencies
494
- │ └── vite.config.ts # Vite configuration
495
- ├── dist/ # Built package output
496
- │ ├── index.js # ES Module
497
- │ ├── index.umd.js # UMD Module
498
- │ ├── index.d.ts # TypeScript declarations
499
- │ └── map-gl-style-switcher.css # Bundled CSS
500
- ├── package.json # Package configuration
501
- ├── rollup.config.js # Production build configuration
502
- ├── vite.config.ts # Development build configuration
503
- ├── jest.config.js # Test configuration
504
- ├── tsconfig.json # TypeScript configuration
505
- └── README.md # Documentation
506
- ```
507
-
508
- ## Development
509
-
510
- This project uses npm for dependency management.
511
-
512
- ### Prerequisites
513
-
514
- ```sh
515
- # Ensure you have Node.js 16+ and npm 8+ installed
516
- node --version
517
- npm --version
518
- ```
519
-
520
- ### Quick Start
521
-
522
- ```sh
523
- # Install dependencies
524
- npm install
525
-
526
- # Start development server
527
- npm run dev
528
-
529
- # Build for production
530
- npm run build
531
-
532
- # Run tests
533
- npm test
534
-
535
- # Lint and format code
536
- npm run lint
537
- npm run format
538
-
539
- # Validate before publishing
540
- npm run validate
541
- ```
542
-
543
- ## Build for npm
544
-
545
- The project uses Rollup for optimized production builds, generating:
546
-
547
- - `dist/index.js` - ES module format
548
- - `dist/index.umd.js` - UMD format for browser usage
549
- - `dist/index.d.ts` - TypeScript declarations
550
- - `dist/map-gl-style-switcher.css` - Minified CSS styles
551
-
552
- ```sh
553
- # Standard build
554
- npm run build
555
-
556
- # Production build with full validation
557
- npm run build:prod
558
- ```
559
-
560
564
  ## Contributing
561
565
 
562
566
  We welcome contributions! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
@@ -595,23 +599,6 @@ We welcome contributions! Please feel free to submit a Pull Request. For major c
595
599
 
596
600
  6. **Submit a pull request**
597
601
 
598
- ### Available Scripts
599
-
600
- - `npm run dev` - Start development server with hot reload
601
- - `npm run build` - Build library with Rollup (ES modules, UMD, TypeScript declarations, and CSS)
602
- - `npm run build:prod` - Production build with validation (type-check, lint, and build)
603
- - `npm run build:watch` - Build in watch mode with Rollup
604
- - `npm test` - Run tests
605
- - `npm run test:watch` - Run tests in watch mode
606
- - `npm run test:coverage` - Run tests with coverage
607
- - `npm run lint` - Lint code
608
- - `npm run lint:fix` - Lint and auto-fix issues
609
- - `npm run format` - Format code with Prettier
610
- - `npm run format:check` - Check code formatting
611
- - `npm run validate` - Run all validation checks
612
- - `npm run type-check` - Type check TypeScript
613
- - `npm run clean` - Clean build artifacts
614
-
615
602
  ### Guidelines
616
603
 
617
604
  - Use npm for dependency management
@@ -0,0 +1,52 @@
1
+ type MapInstance = any;
2
+ interface IControl {
3
+ onAdd(map: MapInstance): HTMLElement;
4
+ onRemove(map: MapInstance): void;
5
+ }
6
+ interface StyleItem {
7
+ id: string;
8
+ name: string;
9
+ image: string;
10
+ styleUrl: string;
11
+ description?: string;
12
+ }
13
+ interface StyleSwitcherControlOptions {
14
+ styles: StyleItem[];
15
+ activeStyleId?: string;
16
+ onBeforeStyleChange?: (from: StyleItem, to: StyleItem) => void;
17
+ onAfterStyleChange?: (from: StyleItem, to: StyleItem) => void;
18
+ showLabels?: boolean;
19
+ showImages?: boolean;
20
+ animationDuration?: number;
21
+ maxHeight?: number;
22
+ theme?: 'light' | 'dark' | 'auto';
23
+ classNames?: Partial<StyleSwitcherClassNames>;
24
+ rtl?: boolean;
25
+ }
26
+ interface StyleSwitcherClassNames {
27
+ container: string;
28
+ list: string;
29
+ item: string;
30
+ itemSelected: string;
31
+ itemHideLabel: string;
32
+ dark: string;
33
+ light: string;
34
+ }
35
+ declare class StyleSwitcherControl implements IControl {
36
+ private _container;
37
+ private _options;
38
+ private _activeStyleId;
39
+ private _expanded;
40
+ private _classNames;
41
+ constructor(options: StyleSwitcherControlOptions);
42
+ onAdd(_map: MapInstance): HTMLElement;
43
+ private _applyTheme;
44
+ onRemove(): void;
45
+ private _setExpanded;
46
+ private _handleStyleChange;
47
+ private _render;
48
+ private _createStyleItem;
49
+ }
50
+
51
+ export { StyleSwitcherControl as a };
52
+ export type { IControl as I, StyleSwitcherControlOptions as S, StyleItem as b, StyleSwitcherClassNames as c };