Preview: InfoBox.tsx
Size: 21.33 KB
/var/www/sitesecurity.bitkit.dk/httpdocs/node_modules/@react-google-maps/infobox/src/InfoBox.tsx
/* global google */
/* eslint-disable filenames/match-regex */
import type { InfoBoxOptions } from './types'
// This handler prevents an event in the InfoBox from being passed on to the map.
function cancelHandler(event: Event) {
event.cancelBubble = true
if (event.stopPropagation) {
event.stopPropagation()
}
}
export class InfoBox {
content: string | Node
disableAutoPan: boolean
maxWidth: number
pixelOffset: google.maps.Size
position: google.maps.LatLng
zIndex: number | undefined | null
boxClass: string
boxStyle: Partial<CSSStyleDeclaration>
closeBoxMargin: string
closeBoxURL: string
infoBoxClearance: google.maps.Size
isHidden: boolean
alignBottom: boolean
pane: keyof google.maps.MapPanes
enableEventPropagation: boolean
div: HTMLDivElement | null
closeListener: google.maps.MapsEventListener | null
moveListener: google.maps.MapsEventListener | null
mapListener: google.maps.MapsEventListener | null
contextListener: google.maps.MapsEventListener | null
eventListeners: google.maps.MapsEventListener[] | null
fixedWidthSet: boolean | null
constructor(options: InfoBoxOptions = {}) {
this.getCloseClickHandler = this.getCloseClickHandler.bind(this)
this.closeClickHandler = this.closeClickHandler.bind(this)
this.createInfoBoxDiv = this.createInfoBoxDiv.bind(this)
this.addClickHandler = this.addClickHandler.bind(this)
this.getCloseBoxImg = this.getCloseBoxImg.bind(this)
this.getBoxWidths = this.getBoxWidths.bind(this)
this.setBoxStyle = this.setBoxStyle.bind(this)
this.setPosition = this.setPosition.bind(this)
this.getPosition = this.getPosition.bind(this)
this.setOptions = this.setOptions.bind(this)
this.setContent = this.setContent.bind(this)
this.setVisible = this.setVisible.bind(this)
this.getContent = this.getContent.bind(this)
this.getVisible = this.getVisible.bind(this)
this.setZIndex = this.setZIndex.bind(this)
this.getZIndex = this.getZIndex.bind(this)
this.onRemove = this.onRemove.bind(this)
this.panBox = this.panBox.bind(this)
this.extend = this.extend.bind(this)
this.close = this.close.bind(this)
this.draw = this.draw.bind(this)
this.show = this.show.bind(this)
this.hide = this.hide.bind(this)
this.open = this.open.bind(this)
this.extend(InfoBox, google.maps.OverlayView)
// Standard options (in common with google.maps.InfoWindow):
this.content = options.content || ''
this.disableAutoPan = options.disableAutoPan || false
this.maxWidth = options.maxWidth || 0
this.pixelOffset = options.pixelOffset || new google.maps.Size(0, 0)
this.position = options.position || new google.maps.LatLng(0, 0)
this.zIndex = options.zIndex || null
// Additional options (unique to InfoBox):
this.boxClass = options.boxClass || 'infoBox'
this.boxStyle = options.boxStyle || {} as Partial<CSSStyleDeclaration>
this.closeBoxMargin = options.closeBoxMargin || '2px'
this.closeBoxURL = options.closeBoxURL || 'http://www.google.com/intl/en_us/mapfiles/close.gif'
if (options.closeBoxURL === '') {
this.closeBoxURL = ''
}
this.infoBoxClearance = options.infoBoxClearance || new google.maps.Size(1, 1)
if (typeof options.visible === 'undefined') {
if (typeof options.isHidden === 'undefined') {
options.visible = true
} else {
options.visible = !options.isHidden
}
}
this.isHidden = !options.visible
this.alignBottom = options.alignBottom || false
this.pane = options.pane || 'floatPane'
this.enableEventPropagation = options.enableEventPropagation || false
this.div = null
this.closeListener = null
this.moveListener = null
this.mapListener = null
this.contextListener = null
this.eventListeners = null
this.fixedWidthSet = null
}
createInfoBoxDiv(): void {
// This handler ignores the current event in the InfoBox and conditionally prevents
// the event from being passed on to the map. It is used for the contextmenu event.
const ignoreHandler = (event: Event) => {
event.returnValue = false
if (event.preventDefault) {
event.preventDefault()
}
if (!this.enableEventPropagation) {
cancelHandler(event)
}
}
if (!this.div) {
this.div = document.createElement('div')
this.setBoxStyle()
if (typeof this.content === 'string') {
this.div.innerHTML = this.getCloseBoxImg() + this.content
} else {
this.div.innerHTML = this.getCloseBoxImg()
this.div.appendChild(this.content)
}
const panes = (this as unknown as google.maps.OverlayView).getPanes()
if (panes !== null) {
panes[this.pane].appendChild(this.div) // Add the InfoBox div to the DOM
}
this.addClickHandler()
if (this.div.style.width) {
this.fixedWidthSet = true
} else {
if (this.maxWidth !== 0 && this.div.offsetWidth > this.maxWidth) {
this.div.style.width = this.maxWidth + 'px'
this.fixedWidthSet = true
} else {
// The following code is needed to overcome problems with MSIE
const bw = this.getBoxWidths()
this.div.style.width = this.div.offsetWidth - bw.left - bw.right + 'px'
this.fixedWidthSet = false
}
}
this.panBox(this.disableAutoPan)
if (!this.enableEventPropagation) {
this.eventListeners = []
// Cancel event propagation.
// Note: mousemove not included (to resolve Issue 152)
const events = [
'mousedown',
'mouseover',
'mouseout',
'mouseup',
'click',
'dblclick',
'touchstart',
'touchend',
'touchmove',
]
for (const event of events) {
this.eventListeners.push(
google.maps.event.addListener(this.div, event, cancelHandler)
)
}
// Workaround for Google bug that causes the cursor to change to a pointer
// when the mouse moves over a marker underneath InfoBox.
this.eventListeners.push(
google.maps.event.addListener(
this.div,
'mouseover',
() => {
if (this.div) {
this.div.style.cursor = 'default'
}
}
)
)
}
this.contextListener = google.maps.event.addListener(
this.div,
'contextmenu',
ignoreHandler
)
/**
* This event is fired when the DIV containing the InfoBox's content is attached to the DOM.
* @name InfoBox#domready
* @event
*/
google.maps.event.trigger(this, 'domready')
}
}
getCloseBoxImg(): string {
let img = ''
if (this.closeBoxURL !== '') {
img = '<img alt=""'
img += ' aria-hidden="true"'
img += " src='" + this.closeBoxURL + "'"
img += ' align=right' // Do this because Opera chokes on style='float: right;'
img += " style='"
img += ' position: relative;' // Required by MSIE
img += ' cursor: pointer;'
img += ' margin: ' + this.closeBoxMargin + ';'
img += "'>"
}
return img
}
addClickHandler(): void {
this.closeListener = this.div && this.div.firstChild && this.closeBoxURL !== ''
? google.maps.event.addListener(
this.div.firstChild,
'click',
this.getCloseClickHandler()
)
: null;
}
closeClickHandler(event: Event): void {
// 1.0.3 fix: Always prevent propagation of a close box click to the map:
event.cancelBubble = true
if (event.stopPropagation) {
event.stopPropagation()
}
/**
* This event is fired when the InfoBox's close box is clicked.
* @name InfoBox#closeclick
* @event
*/
google.maps.event.trigger(this, 'closeclick')
this.close()
}
getCloseClickHandler(): (event: Event) => void {
return this.closeClickHandler
}
panBox(disablePan?: boolean | undefined): void {
if (this.div && !disablePan) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const map: google.maps.Map | google.maps.StreetViewPanorama | null | undefined = this.getMap()
// Only pan if attached to map, not panorama
if (map instanceof google.maps.Map) {
let xOffset = 0
let yOffset = 0
const bounds = map.getBounds()
if (bounds && !bounds.contains(this.position)) {
// Marker not in visible area of map, so set center
// of map to the marker position first.
map.setCenter(this.position)
}
const mapDiv = map.getDiv()
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const mapWidth = mapDiv.offsetWidth
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const mapHeight = mapDiv.offsetHeight
const iwOffsetX = this.pixelOffset.width
const iwOffsetY = this.pixelOffset.height
const iwWidth = this.div.offsetWidth
const iwHeight = this.div.offsetHeight
const padX = this.infoBoxClearance.width
const padY = this.infoBoxClearance.height
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const projection: google.maps.MapCanvasProjection = this.getProjection()
const pixPosition = projection.fromLatLngToContainerPixel(this.position)
if (pixPosition !== null) {
if (pixPosition.x < -iwOffsetX + padX) {
xOffset = pixPosition.x + iwOffsetX - padX
} else if (pixPosition.x + iwWidth + iwOffsetX + padX > mapWidth) {
xOffset = pixPosition.x + iwWidth + iwOffsetX + padX - mapWidth
}
if (this.alignBottom) {
if (pixPosition.y < -iwOffsetY + padY + iwHeight) {
yOffset = pixPosition.y + iwOffsetY - padY - iwHeight
} else if (pixPosition.y + iwOffsetY + padY > mapHeight) {
yOffset = pixPosition.y + iwOffsetY + padY - mapHeight
}
} else {
if (pixPosition.y < -iwOffsetY + padY) {
yOffset = pixPosition.y + iwOffsetY - padY
} else if (pixPosition.y + iwHeight + iwOffsetY + padY > mapHeight) {
yOffset = pixPosition.y + iwHeight + iwOffsetY + padY - mapHeight
}
}
}
if (!(xOffset === 0 && yOffset === 0)) {
// Move the map to the shifted center.
map.panBy(xOffset, yOffset)
}
}
}
}
setBoxStyle(): void {
if (this.div) {
// Apply style values from the style sheet defined in the boxClass parameter:
this.div.className = this.boxClass
// Clear existing inline style values:
this.div.style.cssText = ''
// Apply style values defined in the boxStyle parameter:
const boxStyle: Partial<CSSStyleDeclaration> = this.boxStyle
for (const i in boxStyle) {
if (Object.prototype.hasOwnProperty.call(boxStyle, i)) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
this.div.style[i] = boxStyle[i]
}
}
// Fix for iOS disappearing InfoBox problem
// See http://stackoverflow.com/questions/9229535/google-maps-markers-disappear-at-certain-zoom-level-only-on-iphone-ipad
this.div.style.webkitTransform = 'translateZ(0)'
// Fix up opacity style for benefit of MSIE
if (typeof this.div.style.opacity !== 'undefined' && this.div.style.opacity !== '') {
// See http://www.quirksmode.org/css/opacity.html
const opacity = parseFloat(this.div.style.opacity || '')
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
this.div.style.msFilter =
'"progid:DXImageTransform.Microsoft.Alpha(Opacity=' + opacity * 100 + ')"'
this.div.style.filter = 'alpha(opacity=' + opacity * 100 + ')'
}
// Apply required styles
this.div.style.position = 'absolute'
this.div.style.visibility = 'hidden'
if (this.zIndex !== null) {
this.div.style.zIndex = this.zIndex + ''
}
if (!this.div.style.overflow) {
this.div.style.overflow = 'auto'
}
}
}
getBoxWidths(): { bottom: number; left: number; right: number; top: number } {
const bw = { top: 0, bottom: 0, left: 0, right: 0 }
if (!this.div) {
return bw
}
if (document.defaultView) {
const ownerDocument = this.div.ownerDocument
const computedStyle =
ownerDocument && ownerDocument.defaultView
? ownerDocument.defaultView.getComputedStyle(this.div, '')
: null
if (computedStyle) {
// The computed styles are always in pixel units (good!)
bw.top = parseInt(computedStyle.borderTopWidth || '', 10) || 0
bw.bottom = parseInt(computedStyle.borderBottomWidth || '', 10) || 0
bw.left = parseInt(computedStyle.borderLeftWidth || '', 10) || 0
bw.right = parseInt(computedStyle.borderRightWidth || '', 10) || 0
}
} else if (
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
document.documentElement.currentStyle // MSIE
) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const currentStyle = this.div.currentStyle
if (currentStyle) {
// The current styles may not be in pixel units, but assume they are (bad!)
bw.top = parseInt(currentStyle.borderTopWidth || '', 10) || 0
bw.bottom = parseInt(currentStyle.borderBottomWidth || '', 10) || 0
bw.left = parseInt(currentStyle.borderLeftWidth || '', 10) || 0
bw.right = parseInt(currentStyle.borderRightWidth || '', 10) || 0
}
}
return bw
}
onRemove(): void {
if (this.div && this.div.parentNode) {
this.div.parentNode.removeChild(this.div)
this.div = null
}
}
draw(): void {
this.createInfoBoxDiv()
if (this.div) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const projection: google.maps.MapCanvasProjection = this.getProjection()
const pixPosition = projection.fromLatLngToDivPixel(this.position)
if (pixPosition !== null) {
this.div.style.left = pixPosition.x + this.pixelOffset.width + 'px'
if (this.alignBottom) {
this.div.style.bottom = -(pixPosition.y + this.pixelOffset.height) + 'px'
} else {
this.div.style.top = pixPosition.y + this.pixelOffset.height + 'px'
}
}
if (this.isHidden) {
this.div.style.visibility = 'hidden'
} else {
this.div.style.visibility = 'visible'
}
}
}
setOptions(options: InfoBoxOptions = {}): void {
if (typeof options.boxClass !== 'undefined') {
// Must be first
this.boxClass = options.boxClass
this.setBoxStyle()
}
if (typeof options.boxStyle !== 'undefined') {
// Must be second
this.boxStyle = options.boxStyle
this.setBoxStyle()
}
if (typeof options.content !== 'undefined') {
this.setContent(options.content)
}
if (typeof options.disableAutoPan !== 'undefined') {
this.disableAutoPan = options.disableAutoPan
}
if (typeof options.maxWidth !== 'undefined') {
this.maxWidth = options.maxWidth
}
if (typeof options.pixelOffset !== 'undefined') {
this.pixelOffset = options.pixelOffset
}
if (typeof options.alignBottom !== 'undefined') {
this.alignBottom = options.alignBottom
}
if (typeof options.position !== 'undefined') {
this.setPosition(options.position)
}
if (typeof options.zIndex !== 'undefined') {
this.setZIndex(options.zIndex)
}
if (typeof options.closeBoxMargin !== 'undefined') {
this.closeBoxMargin = options.closeBoxMargin
}
if (typeof options.closeBoxURL !== 'undefined') {
this.closeBoxURL = options.closeBoxURL
}
if (typeof options.infoBoxClearance !== 'undefined') {
this.infoBoxClearance = options.infoBoxClearance
}
if (typeof options.isHidden !== 'undefined') {
this.isHidden = options.isHidden
}
if (typeof options.visible !== 'undefined') {
this.isHidden = !options.visible
}
if (typeof options.enableEventPropagation !== 'undefined') {
this.enableEventPropagation = options.enableEventPropagation
}
if (this.div) {
this.draw()
}
}
setContent(content: string | Node): void {
this.content = content
if (this.div) {
if (this.closeListener) {
google.maps.event.removeListener(this.closeListener)
this.closeListener = null
}
// Odd code required to make things work with MSIE.
if (!this.fixedWidthSet) {
this.div.style.width = ''
}
if (typeof content === 'string') {
this.div.innerHTML = this.getCloseBoxImg() + content
} else {
this.div.innerHTML = this.getCloseBoxImg()
this.div.appendChild(content)
}
// Perverse code required to make things work with MSIE.
// (Ensures the close box does, in fact, float to the right.)
if (!this.fixedWidthSet) {
this.div.style.width = this.div.offsetWidth + 'px'
if (typeof content === 'string') {
this.div.innerHTML = this.getCloseBoxImg() + content
} else {
this.div.innerHTML = this.getCloseBoxImg()
this.div.appendChild(content)
}
}
this.addClickHandler()
}
/**
* This event is fired when the content of the InfoBox changes.
* @name InfoBox#content_changed
* @event
*/
google.maps.event.trigger(this, 'content_changed')
}
setPosition(latLng: google.maps.LatLng): void {
this.position = latLng
if (this.div) {
this.draw()
}
/**
* This event is fired when the position of the InfoBox changes.
* @name InfoBox#position_changed
* @event
*/
google.maps.event.trigger(this, 'position_changed')
}
setVisible(isVisible: boolean): void {
this.isHidden = !isVisible
if (this.div) {
this.div.style.visibility = this.isHidden ? 'hidden' : 'visible'
}
}
setZIndex(index: number): void {
this.zIndex = index
if (this.div) {
this.div.style.zIndex = index + ''
}
/**
* This event is fired when the zIndex of the InfoBox changes.
* @name InfoBox#zindex_changed
* @event
*/
google.maps.event.trigger(this, 'zindex_changed')
}
getContent(): string | Node {
return this.content
}
getPosition(): google.maps.LatLng {
return this.position
}
getZIndex(): number | null | undefined {
return this.zIndex
}
getVisible(): boolean {
const map: google.maps.Map | google.maps.StreetViewPanorama | null | undefined = (this as unknown as google.maps.OverlayView).getMap()
return typeof map === 'undefined' || map === null ? false : !this.isHidden
}
show(): void {
this.isHidden = false
if (this.div) {
this.div.style.visibility = 'visible'
}
}
hide(): void {
this.isHidden = true
if (this.div) {
this.div.style.visibility = 'hidden'
}
}
open(
map: google.maps.Map | google.maps.StreetViewPanorama,
anchor?: google.maps.MVCObject | undefined
): void {
if (anchor) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
this.position = anchor.getPosition()
this.moveListener = google.maps.event.addListener(
anchor,
'position_changed',
() => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const position = anchor.getPosition()
this.setPosition(position)
}
)
this.mapListener = google.maps.event.addListener(
anchor,
'map_changed',
() => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
this.setMap(anchor.map)
}
)
}
(this as unknown as google.maps.OverlayView).setMap(map)
if (this.div) {
this.panBox()
}
}
close() {
if (this.closeListener) {
google.maps.event.removeListener(this.closeListener)
this.closeListener = null
}
if (this.eventListeners) {
for (const eventListener of this.eventListeners) {
google.maps.event.removeListener(eventListener)
}
this.eventListeners = null
}
if (this.moveListener) {
google.maps.event.removeListener(this.moveListener)
this.moveListener = null
}
if (this.mapListener) {
google.maps.event.removeListener(this.mapListener)
this.mapListener = null
}
if (this.contextListener) {
google.maps.event.removeListener(this.contextListener)
this.contextListener = null
}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
this.setMap(null)
}
extend<A extends typeof InfoBox>(obj1: A, obj2: typeof google.maps.OverlayView): A {
return function applyExtend(this: A, object: typeof google.maps.OverlayView): A {
for (const property in object.prototype) {
if (!Object.prototype.hasOwnProperty.call(this, property)) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
this.prototype[property] = object.prototype[property as keyof google.maps.OverlayView]
}
}
return this
}.apply(obj1, [obj2])
}
}
Directory Contents
Dirs: 0 × Files: 3