L.Label = L.Class.extend({ includes: L.Mixin.Events, options: { className: '', clickable: false, direction: 'right', noHide: false, offset: [12, -15], // 6 (width of the label triangle) + 6 (padding) opacity: 1, zoomAnimation: true }, initialize: function (options, source) { L.setOptions(this, options); this._source = source; this._animated = L.Browser.any3d && this.options.zoomAnimation; this._isOpen = false; }, onAdd: function (map) { this._map = map; this._pane = this._source instanceof L.Marker ? map._panes.markerPane : map._panes.popupPane; if (!this._container) { this._initLayout(); } this._pane.appendChild(this._container); this._initInteraction(); this._update(); this.setOpacity(this.options.opacity); map .on('moveend', this._onMoveEnd, this) .on('viewreset', this._onViewReset, this); if (this._animated) { map.on('zoomanim', this._zoomAnimation, this); } if (L.Browser.touch && !this.options.noHide) { L.DomEvent.on(this._container, 'click', this.close, this); } }, onRemove: function (map) { this._pane.removeChild(this._container); map.off({ zoomanim: this._zoomAnimation, moveend: this._onMoveEnd, viewreset: this._onViewReset }, this); this._removeInteraction(); this._map = null; }, setLatLng: function (latlng) { this._latlng = L.latLng(latlng); if (this._map) { this._updatePosition(); } return this; }, setContent: function (content) { // Backup previous content and store new content this._previousContent = this._content; this._content = content; this._updateContent(); return this; }, close: function () { var map = this._map; if (map) { if (L.Browser.touch && !this.options.noHide) { L.DomEvent.off(this._container, 'click', this.close); } map.removeLayer(this); } }, updateZIndex: function (zIndex) { this._zIndex = zIndex; if (this._container && this._zIndex) { this._container.style.zIndex = zIndex; } }, setOpacity: function (opacity) { this.options.opacity = opacity; if (this._container) { L.DomUtil.setOpacity(this._container, opacity); } }, _initLayout: function () { this._container = L.DomUtil.create('div', 'leaflet-label ' + this.options.className + ' leaflet-zoom-animated'); this.updateZIndex(this._zIndex); }, _update: function () { if (!this._map) { return; } this._container.style.visibility = 'hidden'; this._updateContent(); this._updatePosition(); this._container.style.visibility = ''; }, _updateContent: function () { if (!this._content || !this._map || this._prevContent === this._content) { return; } if (typeof this._content === 'string') { this._container.innerHTML = this._content; this._prevContent = this._content; this._labelWidth = this._container.offsetWidth; } }, _updatePosition: function () { var pos = this._map.latLngToLayerPoint(this._latlng); this._setPosition(pos); }, _setPosition: function (pos) { var map = this._map, container = this._container, centerPoint = map.latLngToContainerPoint(map.getCenter()), labelPoint = map.layerPointToContainerPoint(pos), direction = this.options.direction, labelWidth = this._labelWidth, offset = L.point(this.options.offset); // position to the right (right or auto & needs to) if (direction === 'right' || direction === 'auto' && labelPoint.x < centerPoint.x) { L.DomUtil.addClass(container, 'leaflet-label-right'); L.DomUtil.removeClass(container, 'leaflet-label-left'); pos = pos.add(offset); } else { // position to the left L.DomUtil.addClass(container, 'leaflet-label-left'); L.DomUtil.removeClass(container, 'leaflet-label-right'); pos = pos.add(L.point(-offset.x - labelWidth, offset.y)); } L.DomUtil.setPosition(container, pos); }, _zoomAnimation: function (opt) { var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round(); this._setPosition(pos); }, _onMoveEnd: function () { if (!this._animated || this.options.direction === 'auto') { this._updatePosition(); } }, _onViewReset: function (e) { /* if map resets hard, we must update the label */ if (e && e.hard) { this._update(); } }, _initInteraction: function () { if (!this.options.clickable) { return; } var container = this._container, events = ['dblclick', 'mousedown', 'mouseover', 'mouseout', 'contextmenu']; L.DomUtil.addClass(container, 'leaflet-clickable'); L.DomEvent.on(container, 'click', this._onMouseClick, this); for (var i = 0; i < events.length; i++) { L.DomEvent.on(container, events[i], this._fireMouseEvent, this); } }, _removeInteraction: function () { if (!this.options.clickable) { return; } var container = this._container, events = ['dblclick', 'mousedown', 'mouseover', 'mouseout', 'contextmenu']; L.DomUtil.removeClass(container, 'leaflet-clickable'); L.DomEvent.off(container, 'click', this._onMouseClick, this); for (var i = 0; i < events.length; i++) { L.DomEvent.off(container, events[i], this._fireMouseEvent, this); } }, _onMouseClick: function (e) { if (this.hasEventListeners(e.type)) { L.DomEvent.stopPropagation(e); } this.fire(e.type, { originalEvent: e }); }, _fireMouseEvent: function (e) { this.fire(e.type, { originalEvent: e }); // TODO proper custom event propagation // this line will always be called if marker is in a FeatureGroup if (e.type === 'contextmenu' && this.hasEventListeners(e.type)) { L.DomEvent.preventDefault(e); } if (e.type !== 'mousedown') { L.DomEvent.stopPropagation(e); } else { L.DomEvent.preventDefault(e); } } });