map-gl-style-switcher
Version:
A customizable style switcher control for Mapbox GL JS and MapLibre GL JS
181 lines (179 loc) • 7.08 kB
JavaScript
class StyleSwitcherControl {
constructor(options) {
Object.defineProperty(this, "_container", {
enumerable: true,
configurable: true,
writable: true,
value: null
});
Object.defineProperty(this, "_options", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "_activeStyleId", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "_expanded", {
enumerable: true,
configurable: true,
writable: true,
value: false
});
Object.defineProperty(this, "_classNames", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
// Ensure at least one of showLabels or showImages is true
const showLabels = options.showLabels !== false;
const showImages = options.showImages !== false;
if (!showLabels && !showImages) {
throw new Error('At least one of showLabels or showImages must be true.');
}
// Validate activeStyleId if provided
if (options.activeStyleId &&
!options.styles.find(s => s.id === options.activeStyleId)) {
console.warn(`StyleSwitcherControl: activeStyleId "${options.activeStyleId}" does not match any style. Using first style instead.`);
}
this._options = {
showLabels,
showImages,
animationDuration: 200,
maxHeight: 300,
theme: 'light',
...options,
};
this._activeStyleId = options.activeStyleId || options.styles[0]?.id;
this._classNames = {
container: 'maplibregl-ctrl maplibregl-ctrl-group mapboxgl-ctrl mapboxgl-ctrl-group style-switcher',
list: 'style-switcher-list',
item: 'style-switcher-item',
itemSelected: 'selected',
itemHideLabel: 'hide-label',
dark: 'style-switcher-dark',
light: 'style-switcher-light',
...options.classNames,
};
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
onAdd(_map) {
this._container = document.createElement('div');
this._container.className = this._classNames.container;
this._container.tabIndex = 0;
this._container.setAttribute('role', 'button');
this._container.setAttribute('aria-label', 'Style switcher');
this._container.setAttribute('aria-expanded', 'false');
// Apply RTL if specified
if (this._options.rtl) {
this._container.setAttribute('dir', 'rtl');
}
// Theme support
this._applyTheme();
this._container.addEventListener('mouseenter', () => this._setExpanded(true));
this._container.addEventListener('mouseleave', () => this._setExpanded(false));
this._container.addEventListener('focus', () => this._setExpanded(true));
this._container.addEventListener('blur', () => this._setExpanded(false));
this._render();
return this._container;
}
_applyTheme() {
if (!this._container)
return;
this._container.classList.remove(this._classNames.dark, this._classNames.light);
if (this._options.theme === 'dark') {
this._container.classList.add(this._classNames.dark);
}
else if (this._options.theme === 'light') {
this._container.classList.add(this._classNames.light);
}
else if (this._options.theme === 'auto') {
// Auto-detect dark mode
const isDark = window.matchMedia &&
window.matchMedia('(prefers-color-scheme: dark)').matches;
this._container.classList.add(isDark ? this._classNames.dark : this._classNames.light);
}
}
onRemove() {
if (this._container && this._container.parentNode) {
this._container.parentNode.removeChild(this._container);
}
this._container = null;
}
_setExpanded(expanded) {
if (this._expanded === expanded)
return;
this._expanded = expanded;
this._container?.setAttribute('aria-expanded', expanded.toString());
this._render();
this._applyTheme();
}
_handleStyleChange(style) {
if (style.id === this._activeStyleId)
return;
const from = this._options.styles.find(s => s.id === this._activeStyleId);
if (this._options.onBeforeStyleChange)
this._options.onBeforeStyleChange(from, style);
this._activeStyleId = style.id;
this._render();
if (this._options.onAfterStyleChange)
this._options.onAfterStyleChange(from, style);
}
_render() {
if (!this._container)
return;
this._container.innerHTML = '';
this._applyTheme();
const currentStyle = this._options.styles.find(s => s.id === this._activeStyleId) ||
this._options.styles[0];
// List (expanded)
if (this._expanded) {
const list = document.createElement('div');
list.className = this._classNames.list;
list.style.display = 'flex';
for (const style of this._options.styles) {
const item = this._createStyleItem(style, style.id === this._activeStyleId);
item.onclick = () => this._handleStyleChange(style);
list.appendChild(item);
}
this._container.appendChild(list);
}
// Single (collapsed) - Always show the active style
const selected = this._createStyleItem(currentStyle, true);
this._container.appendChild(selected);
}
_createStyleItem(style, selected) {
const div = document.createElement('div');
let className = this._classNames.item;
if (selected)
className += ' ' + this._classNames.itemSelected;
if (this._options.showLabels === false)
className += ' ' + this._classNames.itemHideLabel;
div.className = className;
div.setAttribute('role', 'option');
div.setAttribute('aria-selected', selected.toString());
div.setAttribute('title', style.description || style.name);
// Image
if (this._options.showImages !== false) {
const img = document.createElement('img');
img.src = style.image;
img.alt = style.name;
img.loading = 'lazy';
div.appendChild(img);
}
// Label
if (this._options.showLabels !== false) {
const span = document.createElement('span');
span.textContent = style.name;
div.appendChild(span);
}
return div;
}
}
export { StyleSwitcherControl };
//# sourceMappingURL=index.js.map