* check if element is within the viewport or is overlapped by another element or disabled
* @param {HTMLElement} elem element to check
* @return {Boolean} false if element is not overlapped
*/
export default function isElementClickable (elem: HTMLElement) {
if (!elem.getBoundingClientRect || !elem.scrollIntoView || !elem.contains || !elem.getClientRects || !document.elementFromPoint) {
return false
}
const isOldEdge = !!(window as unknown as { StyleMedia: unknown }).StyleMedia
const scrollIntoViewFullSupport = !((window as { safari?: boolean }).safari || isOldEdge)
function getOverlappingElement (elem: HTMLElement, context?: Document) {
context = context || document
const elemDimension = elem.getBoundingClientRect()
const x = elemDimension.left + (elem.clientWidth / 2)
const y = elemDimension.top + (elem.clientHeight / 2)
return context.elementFromPoint(x, y)
}
function getOverlappingRects (elem: HTMLElement, context?: Document) {
context = context || document
const rects = elem.getClientRects()
const rect = rects[0]
const x = rect.left + (rect.width / 2)
const y = rect.top + (rect.height / 2)
return [context.elementFromPoint(x, y)]
}
function getOverlappingElements (elem: HTMLElement, context?: Document) {
return [getOverlappingElement(elem, context)].concat(getOverlappingRects(elem, context))
}
function nodeContains (elem: HTMLElement, otherNode: HTMLElement) {
if (isOldEdge) {
let tmpElement = otherNode as HTMLElement | ShadowRoot | Element
while (tmpElement) {
if (tmpElement === elem) {
return true
}
tmpElement = tmpElement.parentNode as ShadowRoot
if (tmpElement && tmpElement.nodeType === 11 && tmpElement.host) {
tmpElement = tmpElement.host
}
}
return false
}
return elem.contains(otherNode)
}
function isOverlappingElementMatch (elementsFromPoint: HTMLElement[], elem: HTMLElement): boolean {
if (elementsFromPoint.some(function (elementFromPoint) {
return elementFromPoint === elem || nodeContains(elem, elementFromPoint)
})) {
return true
}
let elemsWithShadowRoot = [].concat(elementsFromPoint)
elemsWithShadowRoot = elemsWithShadowRoot.filter(function (x: HTMLElement) {
return x && x.shadowRoot && (x.shadowRoot as ShadowRoot).elementFromPoint
})
let shadowElementsFromPoint: HTMLElement[] = []
for (let i = 0; i < elemsWithShadowRoot.length; ++i) {
const shadowElement = elemsWithShadowRoot[i]
shadowElementsFromPoint = shadowElementsFromPoint.concat(
getOverlappingElements(elem, (shadowElement as HTMLElement).shadowRoot as unknown as Document) as HTMLElement[]
)
}
shadowElementsFromPoint = [].concat(shadowElementsFromPoint)
shadowElementsFromPoint = shadowElementsFromPoint.filter(function (x) {
return !elementsFromPoint.includes(x)
})
if (shadowElementsFromPoint.length === 0) {
return false
}
return isOverlappingElementMatch(shadowElementsFromPoint, elem)
}
function isElementInViewport (elem: HTMLElement) {
if (!elem.getBoundingClientRect) {
return false
}
const rect = elem.getBoundingClientRect()
const windowHeight = (window.innerHeight || document.documentElement.clientHeight)
const windowWidth = (window.innerWidth || document.documentElement.clientWidth)
const vertInView = (rect.top <= windowHeight) && ((rect.top + rect.height) > 0)
const horInView = (rect.left <= windowWidth) && ((rect.left + rect.width) > 0)
return (vertInView && horInView)
}
function isEnabled(elem: HTMLFormElement) {
return elem.disabled !== true
}
function hasOverlaps(elem: HTMLElement) {
return !isOverlappingElementMatch(getOverlappingElements(elem) as unknown as HTMLElement[], elem)
}
function isFullyDisplayedInViewport(elem: HTMLElement) {
return isElementInViewport(elem) && !hasOverlaps(elem)
}
function getViewportScrollPositions() {
return {
x: window.scrollX !== null && window.scrollX !== void 0
? window.scrollX
: window.pageXOffset,
y: window.scrollY !== null && window.scrollY !== void 0
? window.scrollY
: window.pageYOffset,
}
}
let _isFullyDisplayedInViewport = isFullyDisplayedInViewport(elem)
if (!_isFullyDisplayedInViewport) {
const { x: originalX, y: originalY } = getViewportScrollPositions()
elem.scrollIntoView(scrollIntoViewFullSupport ? { block: 'center', inline: 'center' } : false)
_isFullyDisplayedInViewport = isFullyDisplayedInViewport(elem)
const { x: currentX, y: currentY } = getViewportScrollPositions()
if (currentX !== originalX || currentY !== originalY) {
window.scroll(originalX, originalY)
}
}
return _isFullyDisplayedInViewport && isEnabled(elem as HTMLFormElement)
}