UNPKG

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
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