UNPKG

11.8 kBJavaScriptView Raw
1
2import * as Browser from '../core/Browser';
3import {DivOverlay} from './DivOverlay';
4import {toPoint} from '../geometry/Point';
5import {Map} from '../map/Map';
6import {Layer} from './Layer';
7import * as Util from '../core/Util';
8import * as DomUtil from '../dom/DomUtil';
9
10/*
11 * @class Tooltip
12 * @inherits DivOverlay
13 * @aka L.Tooltip
14 * Used to display small texts on top of map layers.
15 *
16 * @example
17 *
18 * ```js
19 * marker.bindTooltip("my tooltip text").openTooltip();
20 * ```
21 * Note about tooltip offset. Leaflet takes two options in consideration
22 * for computing tooltip offsetting:
23 * - the `offset` Tooltip option: it defaults to [0, 0], and it's specific to one tooltip.
24 * Add a positive x offset to move the tooltip to the right, and a positive y offset to
25 * move it to the bottom. Negatives will move to the left and top.
26 * - the `tooltipAnchor` Icon option: this will only be considered for Marker. You
27 * should adapt this value if you use a custom icon.
28 */
29
30
31// @namespace Tooltip
32export var Tooltip = DivOverlay.extend({
33
34 // @section
35 // @aka Tooltip options
36 options: {
37 // @option pane: String = 'tooltipPane'
38 // `Map pane` where the tooltip will be added.
39 pane: 'tooltipPane',
40
41 // @option offset: Point = Point(0, 0)
42 // Optional offset of the tooltip position.
43 offset: [0, 0],
44
45 // @option direction: String = 'auto'
46 // Direction where to open the tooltip. Possible values are: `right`, `left`,
47 // `top`, `bottom`, `center`, `auto`.
48 // `auto` will dynamically switch between `right` and `left` according to the tooltip
49 // position on the map.
50 direction: 'auto',
51
52 // @option permanent: Boolean = false
53 // Whether to open the tooltip permanently or only on mouseover.
54 permanent: false,
55
56 // @option sticky: Boolean = false
57 // If true, the tooltip will follow the mouse instead of being fixed at the feature center.
58 sticky: false,
59
60 // @option interactive: Boolean = false
61 // If true, the tooltip will listen to the feature events.
62 interactive: false,
63
64 // @option opacity: Number = 0.9
65 // Tooltip container opacity.
66 opacity: 0.9
67 },
68
69 onAdd: function (map) {
70 DivOverlay.prototype.onAdd.call(this, map);
71 this.setOpacity(this.options.opacity);
72
73 // @namespace Map
74 // @section Tooltip events
75 // @event tooltipopen: TooltipEvent
76 // Fired when a tooltip is opened in the map.
77 map.fire('tooltipopen', {tooltip: this});
78
79 if (this._source) {
80 // @namespace Layer
81 // @section Tooltip events
82 // @event tooltipopen: TooltipEvent
83 // Fired when a tooltip bound to this layer is opened.
84 this._source.fire('tooltipopen', {tooltip: this}, true);
85 }
86 },
87
88 onRemove: function (map) {
89 DivOverlay.prototype.onRemove.call(this, map);
90
91 // @namespace Map
92 // @section Tooltip events
93 // @event tooltipclose: TooltipEvent
94 // Fired when a tooltip in the map is closed.
95 map.fire('tooltipclose', {tooltip: this});
96
97 if (this._source) {
98 // @namespace Layer
99 // @section Tooltip events
100 // @event tooltipclose: TooltipEvent
101 // Fired when a tooltip bound to this layer is closed.
102 this._source.fire('tooltipclose', {tooltip: this}, true);
103 }
104 },
105
106 getEvents: function () {
107 var events = DivOverlay.prototype.getEvents.call(this);
108
109 if (Browser.touch && !this.options.permanent) {
110 events.preclick = this._close;
111 }
112
113 return events;
114 },
115
116 _close: function () {
117 if (this._map) {
118 this._map.closeTooltip(this);
119 }
120 },
121
122 _initLayout: function () {
123 var prefix = 'leaflet-tooltip',
124 className = prefix + ' ' + (this.options.className || '') + ' leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
125
126 this._contentNode = this._container = DomUtil.create('div', className);
127 },
128
129 _updateLayout: function () {},
130
131 _adjustPan: function () {},
132
133 _setPosition: function (pos) {
134 var map = this._map,
135 container = this._container,
136 centerPoint = map.latLngToContainerPoint(map.getCenter()),
137 tooltipPoint = map.layerPointToContainerPoint(pos),
138 direction = this.options.direction,
139 tooltipWidth = container.offsetWidth,
140 tooltipHeight = container.offsetHeight,
141 offset = toPoint(this.options.offset),
142 anchor = this._getAnchor();
143
144 if (direction === 'top') {
145 pos = pos.add(toPoint(-tooltipWidth / 2 + offset.x, -tooltipHeight + offset.y + anchor.y, true));
146 } else if (direction === 'bottom') {
147 pos = pos.subtract(toPoint(tooltipWidth / 2 - offset.x, -offset.y, true));
148 } else if (direction === 'center') {
149 pos = pos.subtract(toPoint(tooltipWidth / 2 + offset.x, tooltipHeight / 2 - anchor.y + offset.y, true));
150 } else if (direction === 'right' || direction === 'auto' && tooltipPoint.x < centerPoint.x) {
151 direction = 'right';
152 pos = pos.add(toPoint(offset.x + anchor.x, anchor.y - tooltipHeight / 2 + offset.y, true));
153 } else {
154 direction = 'left';
155 pos = pos.subtract(toPoint(tooltipWidth + anchor.x - offset.x, tooltipHeight / 2 - anchor.y - offset.y, true));
156 }
157
158 DomUtil.removeClass(container, 'leaflet-tooltip-right');
159 DomUtil.removeClass(container, 'leaflet-tooltip-left');
160 DomUtil.removeClass(container, 'leaflet-tooltip-top');
161 DomUtil.removeClass(container, 'leaflet-tooltip-bottom');
162 DomUtil.addClass(container, 'leaflet-tooltip-' + direction);
163 DomUtil.setPosition(container, pos);
164 },
165
166 _updatePosition: function () {
167 var pos = this._map.latLngToLayerPoint(this._latlng);
168 this._setPosition(pos);
169 },
170
171 setOpacity: function (opacity) {
172 this.options.opacity = opacity;
173
174 if (this._container) {
175 DomUtil.setOpacity(this._container, opacity);
176 }
177 },
178
179 _animateZoom: function (e) {
180 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center);
181 this._setPosition(pos);
182 },
183
184 _getAnchor: function () {
185 // Where should we anchor the tooltip on the source layer?
186 return toPoint(this._source && this._source._getTooltipAnchor && !this.options.sticky ? this._source._getTooltipAnchor() : [0, 0]);
187 }
188
189});
190
191// @namespace Tooltip
192// @factory L.tooltip(options?: Tooltip options, source?: Layer)
193// Instantiates a Tooltip object given an optional `options` object that describes its appearance and location and an optional `source` object that is used to tag the tooltip with a reference to the Layer to which it refers.
194export var tooltip = function (options, source) {
195 return new Tooltip(options, source);
196};
197
198// @namespace Map
199// @section Methods for Layers and Controls
200Map.include({
201
202 // @method openTooltip(tooltip: Tooltip): this
203 // Opens the specified tooltip.
204 // @alternative
205 // @method openTooltip(content: String|HTMLElement, latlng: LatLng, options?: Tooltip options): this
206 // Creates a tooltip with the specified content and options and open it.
207 openTooltip: function (tooltip, latlng, options) {
208 if (!(tooltip instanceof Tooltip)) {
209 tooltip = new Tooltip(options).setContent(tooltip);
210 }
211
212 if (latlng) {
213 tooltip.setLatLng(latlng);
214 }
215
216 if (this.hasLayer(tooltip)) {
217 return this;
218 }
219
220 return this.addLayer(tooltip);
221 },
222
223 // @method closeTooltip(tooltip?: Tooltip): this
224 // Closes the tooltip given as parameter.
225 closeTooltip: function (tooltip) {
226 if (tooltip) {
227 this.removeLayer(tooltip);
228 }
229 return this;
230 }
231
232});
233
234/*
235 * @namespace Layer
236 * @section Tooltip methods example
237 *
238 * All layers share a set of methods convenient for binding tooltips to it.
239 *
240 * ```js
241 * var layer = L.Polygon(latlngs).bindTooltip('Hi There!').addTo(map);
242 * layer.openTooltip();
243 * layer.closeTooltip();
244 * ```
245 */
246
247// @section Tooltip methods
248Layer.include({
249
250 // @method bindTooltip(content: String|HTMLElement|Function|Tooltip, options?: Tooltip options): this
251 // Binds a tooltip to the layer with the passed `content` and sets up the
252 // necessary event listeners. If a `Function` is passed it will receive
253 // the layer as the first argument and should return a `String` or `HTMLElement`.
254 bindTooltip: function (content, options) {
255
256 if (content instanceof Tooltip) {
257 Util.setOptions(content, options);
258 this._tooltip = content;
259 content._source = this;
260 } else {
261 if (!this._tooltip || options) {
262 this._tooltip = new Tooltip(options, this);
263 }
264 this._tooltip.setContent(content);
265
266 }
267
268 this._initTooltipInteractions();
269
270 if (this._tooltip.options.permanent && this._map && this._map.hasLayer(this)) {
271 this.openTooltip();
272 }
273
274 return this;
275 },
276
277 // @method unbindTooltip(): this
278 // Removes the tooltip previously bound with `bindTooltip`.
279 unbindTooltip: function () {
280 if (this._tooltip) {
281 this._initTooltipInteractions(true);
282 this.closeTooltip();
283 this._tooltip = null;
284 }
285 return this;
286 },
287
288 _initTooltipInteractions: function (remove) {
289 if (!remove && this._tooltipHandlersAdded) { return; }
290 var onOff = remove ? 'off' : 'on',
291 events = {
292 remove: this.closeTooltip,
293 move: this._moveTooltip
294 };
295 if (!this._tooltip.options.permanent) {
296 events.mouseover = this._openTooltip;
297 events.mouseout = this.closeTooltip;
298 if (this._tooltip.options.sticky) {
299 events.mousemove = this._moveTooltip;
300 }
301 if (Browser.touch) {
302 events.click = this._openTooltip;
303 }
304 } else {
305 events.add = this._openTooltip;
306 }
307 this[onOff](events);
308 this._tooltipHandlersAdded = !remove;
309 },
310
311 // @method openTooltip(latlng?: LatLng): this
312 // Opens the bound tooltip at the specified `latlng` or at the default tooltip anchor if no `latlng` is passed.
313 openTooltip: function (layer, latlng) {
314 if (this._tooltip && this._map) {
315 latlng = this._tooltip._prepareOpen(this, layer, latlng);
316
317 // open the tooltip on the map
318 this._map.openTooltip(this._tooltip, latlng);
319
320 // Tooltip container may not be defined if not permanent and never
321 // opened.
322 if (this._tooltip.options.interactive && this._tooltip._container) {
323 DomUtil.addClass(this._tooltip._container, 'leaflet-clickable');
324 this.addInteractiveTarget(this._tooltip._container);
325 }
326 }
327
328 return this;
329 },
330
331 // @method closeTooltip(): this
332 // Closes the tooltip bound to this layer if it is open.
333 closeTooltip: function () {
334 if (this._tooltip) {
335 this._tooltip._close();
336 if (this._tooltip.options.interactive && this._tooltip._container) {
337 DomUtil.removeClass(this._tooltip._container, 'leaflet-clickable');
338 this.removeInteractiveTarget(this._tooltip._container);
339 }
340 }
341 return this;
342 },
343
344 // @method toggleTooltip(): this
345 // Opens or closes the tooltip bound to this layer depending on its current state.
346 toggleTooltip: function (target) {
347 if (this._tooltip) {
348 if (this._tooltip._map) {
349 this.closeTooltip();
350 } else {
351 this.openTooltip(target);
352 }
353 }
354 return this;
355 },
356
357 // @method isTooltipOpen(): boolean
358 // Returns `true` if the tooltip bound to this layer is currently open.
359 isTooltipOpen: function () {
360 return this._tooltip.isOpen();
361 },
362
363 // @method setTooltipContent(content: String|HTMLElement|Tooltip): this
364 // Sets the content of the tooltip bound to this layer.
365 setTooltipContent: function (content) {
366 if (this._tooltip) {
367 this._tooltip.setContent(content);
368 }
369 return this;
370 },
371
372 // @method getTooltip(): Tooltip
373 // Returns the tooltip bound to this layer.
374 getTooltip: function () {
375 return this._tooltip;
376 },
377
378 _openTooltip: function (e) {
379 var layer = e.layer || e.target;
380
381 if (!this._tooltip || !this._map) {
382 return;
383 }
384 this.openTooltip(layer, this._tooltip.options.sticky ? e.latlng : undefined);
385 },
386
387 _moveTooltip: function (e) {
388 var latlng = e.latlng, containerPoint, layerPoint;
389 if (this._tooltip.options.sticky && e.originalEvent) {
390 containerPoint = this._map.mouseEventToContainerPoint(e.originalEvent);
391 layerPoint = this._map.containerPointToLayerPoint(containerPoint);
392 latlng = this._map.layerPointToLatLng(layerPoint);
393 }
394 this._tooltip.setLatLng(latlng);
395 }
396});