您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
Code - github/cypress-io/cypress - to check if an element is visible
当前为
此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.greasyfork.icu/scripts/482857/1304414/cypress-visibility.js
// ==UserScript== // @name cypress-visibility // @namespace flomk.userscripts // @version 1.2 // @description Code - github/cypress-io/cypress - to check if an element is visible // @author flomk // @grant none // @require https://unpkg.com/lodash // @include * // @connect unpkg.com // ==/UserScript== ((global, factory) => { global = typeof globalThis !== 'undefined' ? globalThis : global || self; factory(global); })(this, exports => { const $document = (() => { const docNode = Node.DOCUMENT_NODE; const docFragmentNode = Node.DOCUMENT_FRAGMENT_NODE; const isDocument = (obj) => { try { let node = obj; return (node === null || node === void 0 ? void 0 : node.nodeType) === docNode || (node === null || node === void 0 ? void 0 : node.nodeType) === docFragmentNode; } catch (error) { return false; } }; const hasActiveWindow = (doc) => { if (navigator.appCodeName === 'Mozilla' && !doc.location) return false; return !!doc.defaultView; }; const getDocumentFromElement = (el) => { if (isDocument(el)) return el; return el.ownerDocument; }; return { isDocument, hasActiveWindow, getDocumentFromElement } })(); const $window = (() => { const isWindow = function (obj) { try { return Boolean(obj && obj.window === obj); } catch (error) { return false; } }; const getWindowByDocument = (doc) => { // parentWindow for IE return doc.defaultView || doc.parentWindow } const getWindowByElement = function (el) { if ($window.isWindow(el)) { return el } const doc = $document.getDocumentFromElement(el) return getWindowByDocument(doc) } return { isWindow, getWindowByElement } })(); const $detached = (() => { const isAttached = function (elem) { if ($window.isWindow(elem)) return true; const nodes = []; if (elem) nodes.push(elem); if (nodes.length === 0) return false; return nodes.every((node) => { const doc = $document.getDocumentFromElement(node); if (!$document.hasActiveWindow(doc)) return false; return node.isConnected; }); }; const isDetached = elem => !isAttached(elem) return { isDetached } })(); const $utils = (() => { function switchCase(value, casesObj, defaultKey = 'default') { if (_.has(casesObj, value)) return _.result(casesObj, value); if (_.has(casesObj, defaultKey)) return _.result(casesObj, defaultKey); const keys = _.keys(casesObj); throw new Error(`The switch/case value: '${value}' did not match any cases: ${keys.join(', ')}.`); } const stringify = (el, form = 'long') => { // if we are formatting the window object if ($window.isWindow(el)) return '<window>'; // if we are formatting the document object if ($document.isDocument(el)) return '<document>'; // convert this to jquery if its not already one // const $el = $jquery.wrap(el); const long = () => { const str = el.cloneNode().outerHTML const text = _.chain(el.textContent).clean().truncate({ length: 10 }).value(); const children = el.children.length; if (children) return str.replace('></', '>...</'); if (text) return str.replace('></', `>${text}</`); return str; }; const short = () => { const id = el.id; const klass = el.getAttribute('class'); let str = el.tagName.toLowerCase(); if (id) str += `#${id}`; // using attr here instead of class because // svg's return an SVGAnimatedString object // instead of a normal string when calling // the property 'class' if (klass) str += `.${klass.split(/\s+/).join('.')}`; // if we have more than one element, // format it so that the user can see there's more // if ($el.length > 1) { // return `[ <${str}>, ${$el.length - 1} more... ]`; // } return `<${str}>`; }; return switchCase(form, { long, short }); }; return { stringify } })(); const $contenteditable = (() => { const isContentEditable = (el) => { return $nativeProps.getNativeProp(el, 'isContentEditable') || $document.getDocumentFromElement(el).designMode === 'on'; }; const isDesignModeDocumentElement = el => { return isElement(el) && $elementHelpers.getTagName(el) === 'html' && isContentEditable(el) } return { isDesignModeDocumentElement } })(); const $complexElements = (() => { const fixedOrStickyRe = /(fixed|sticky)/; const focusableSelectors = [ 'a[href]', 'area[href]', 'input:not([disabled])', 'select:not([disabled])', 'textarea:not([disabled])', 'button:not([disabled])', 'iframe', '[tabindex]', '[contenteditable]' ]; const isFocusable = elem => focusableSelectors.some(sel => elem.matches(sel)) || $contenteditable.isDesignModeDocumentElement(elem); const getFirstFixedOrStickyPositionParent = elem => { if (isUndefinedOrHTMLBodyDoc(elem)) return null; if (fixedOrStickyRe.test(getComputedStyle(elem).position)) return elem; /* walk up the tree until we find an element with a fixed/sticky position */ return $find.findParent(elem, node => { if (node.nodeType === Node.DOCUMENT_NODE || node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) return null else if (fixedOrStickyRe.test(getComputedStyle(node).position)) return node; return null; }); }; const elOrAncestorIsFixedOrSticky = elem => { return !!getFirstFixedOrStickyPositionParent(elem); }; return { isFocusable, elOrAncestorIsFixedOrSticky } })(); const $shadow = (() => { const isShadowRoot = (maybeRoot) => { return (maybeRoot === null || maybeRoot === void 0 ? void 0 : maybeRoot.toString()) === '[object ShadowRoot]'; }; const isWithinShadowRoot = (node) => { return isShadowRoot(node.getRootNode()); }; const getShadowElementFromPoint = (node, x, y) => { var _a; const nodeFromPoint = (_a = node === null || node === void 0 ? void 0 : node.shadowRoot) === null || _a === void 0 ? void 0 : _a.elementFromPoint(x, y); if (!nodeFromPoint || nodeFromPoint === node) return node; return getShadowElementFromPoint(nodeFromPoint, x, y); }; return { isWithinShadowRoot, getShadowElementFromPoint } })(); const $find = (() => { const getParentNode = el => { // if the element has a direct parent element, // simply return it. if (el.parentElement) return el.parentElement; const root = el.getRootNode(); // if the element is inside a shadow root, // return the host of the root. if (root && $shadow.isWithinShadowRoot(el)) return root.host; return null; }; const getParent = elem => getParentNode(elem); const findParent = (el, condition) => { const collectParent = node => { const parent = getParentNode(node); if (!parent) return null; const parentMatchingCondition = condition(parent, node); if (parentMatchingCondition) return parentMatchingCondition; return collectParent(parent); }; return collectParent(el); }; const isUndefinedOrHTMLBodyDoc = elem => { return !elem || elem.matches('body,html') || $document.isDocument(elem); }; const getAllParents = (el, untilSelectorOrEl) => { const collectParents = (parents, node) => { const parent = getParentNode(node); const selOrElemMatch = _.isString(untilSelectorOrEl) ? parent.matches(untilSelectorOrEl) : parent === untilSelectorOrEl; // if (!parent || (untilSelectorOrEl && parent.matches(untilSelectorOrEl))) return parents; if (!parent || (untilSelectorOrEl && selOrElemMatch)) return parents; return collectParents(parents.concat(parent), parent); }; return collectParents([], el); }; const isAncestor = (elem, maybeAncestor) => { return getAllParents(elem).indexOf(maybeAncestor) >= 0; }; const isChild = (elem, maybeChild) => { return Array.from(elem.children).indexOf(maybeChild) >= 0; }; const isDescendent = (elem1, elem2) => { if (!elem2) return false; if (elem1 === elem2) return true; return (findParent(elem2, node => { if (node === elem1) return node; }) === elem1); }; const getTagName = el => { const tagName = el.tagName || ''; return tagName.toLowerCase(); }; const getFirstParentWithTagName = (elem, tagName) => { if (isUndefinedOrHTMLBodyDoc(elem) || !tagName) return null; if (getTagName(elem) === tagName) return elem; return findParent(elem, node => { if (getTagName(node) === tagName) return node; return null; }); }; const elementFromPoint = (doc, x, y) => { let elFromPoint = doc.elementFromPoint(x, y); return $shadow.getShadowElementFromPoint(elFromPoint, x, y); }; return { isAncestor, isChild, isDescendent, isUndefinedOrHTMLBodyDoc, getParent, findParent, elementFromPoint, getFirstParentWithTagName, getAllParents } })(); const $elementHelpers = (() => { const getTagName = el => { const tagName = el.tagName || ''; return tagName.toLowerCase(); }; const isElement = function (obj) { try { return Boolean(obj && _.isElement(obj)); } catch (error) { return false; } }; const isInput = (el) => getTagName(el) === 'input'; const isTextarea = (el) => getTagName(el) === 'textarea'; const isSelect = (el) => getTagName(el) === 'select'; const isButton = (el) => getTagName(el) === 'button'; const isBody = (el) => getTagName(el) === 'body'; const isHTML = el => getTagName(el) === 'html'; const isOption = el => getTagName(el) === 'option'; const isOptgroup = el => getTagName(el) === 'optgroup'; const isSvg = function (el) { try { return 'ownerSVGElement' in el; } catch (error) { return false; } }; return { isSvg, isBody, isHTML, isOption, isElement, isOptgroup, isButton, isSelect, isTextarea, isInput } })(); const $nativeProps = (() => { const descriptor = (klass, prop) => { const desc = Object.getOwnPropertyDescriptor(window[klass].prototype, prop); if (desc === undefined) { throw new Error(`Error, could not get property descriptor for ${klass} ${prop}. This should never happen`); } return desc; }; const _isContentEditable = function () { if ($elementHelpers.isSvg(this)) return false; return descriptor('HTMLElement', 'isContentEditable').get; }; const _getValue = function () { if ($elementHelpers.isInput(this)) return descriptor('HTMLInputElement', 'value').get; if ($elementHelpers.isTextarea(this)) return descriptor('HTMLTextAreaElement', 'value').get; if ($elementHelpers.isSelect(this)) return descriptor('HTMLSelectElement', 'value').get; if ($elementHelpers.isButton(this)) return descriptor('HTMLButtonElement', 'value').get; return descriptor('HTMLOptionElement', 'value').get; }; const _getSelectionStart = function () { if ($elementHelpers.isInput(this)) return descriptor('HTMLInputElement', 'selectionStart').get; if ($elementHelpers.isTextarea(this)) return descriptor('HTMLTextAreaElement', 'selectionStart').get; throw new Error('this should never happen, cannot get selectionStart'); }; const _getSelectionEnd = function () { if ($elementHelpers.isInput(this)) return descriptor('HTMLInputElement', 'selectionEnd').get; if ($elementHelpers.isTextarea(this)) return descriptor('HTMLTextAreaElement', 'selectionEnd').get; throw new Error('this should never happen, cannot get selectionEnd'); }; const _getType = function () { if ($elementHelpers.isInput(this)) return descriptor('HTMLInputElement', 'type').get; if ($elementHelpers.isButton(this)) return descriptor('HTMLButtonElement', 'type').get; throw new Error('this should never happen, cannot get type'); }; const _getMaxLength = function () { if ($elementHelpers.isInput(this)) return descriptor('HTMLInputElement', 'maxLength').get; if ($elementHelpers.isTextarea(this)) return descriptor('HTMLTextAreaElement', 'maxLength').get; throw new Error('this should never happen, cannot get maxLength'); }; const nativeGetters = { value: _getValue, isContentEditable: _isContentEditable, isCollapsed: descriptor('Selection', 'isCollapsed').get, selectionStart: _getSelectionStart, selectionEnd: _getSelectionEnd, type: _getType, activeElement: descriptor('Document', 'activeElement').get, body: descriptor('Document', 'body').get, frameElement: Object.getOwnPropertyDescriptor(window, 'frameElement').get, maxLength: _getMaxLength, }; const getNativeProp = function (obj, prop) { const nativeProp = nativeGetters[prop]; if (!nativeProp) { const props = _.keys(nativeGetters).join(', '); throw new Error(`attempted to use a native getter prop called: ${prop}. Available props are: ${props}`); } let retProp = nativeProp.call(obj, prop); if (_.isFunction(retProp)) { retProp = retProp.call(obj, prop); } return retProp; }; return { getNativeProp } })(); const $elements = { ...$find, ...$elementHelpers, ...$complexElements, ...$detached, ...$utils, ...$nativeProps }; const $transform = (() => { const existsInvisibleBackface = (list) => { return !!_.find(list, { backfaceVisibility: 'hidden' }); }; const extractTransformInfo = (el) => { const style = getComputedStyle(el); const backfaceVisibility = style.getPropertyValue('backface-visibility'); if (backfaceVisibility === '') return null; return { backfaceVisibility, transformStyle: style.getPropertyValue('transform-style'), transform: style.getPropertyValue('transform'), }; }; const numberRegex = /-?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?/g; const defaultNormal = [0, 0, 1]; const viewVector = [0, 0, -1]; const identityMatrix3D = [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, ]; const TINY_NUMBER = 1e-5; const toMatrix3d = (m2d) => { return [ m2d[0], m2d[1], 0, 0, m2d[2], m2d[3], 0, 0, 0, 0, 1, 0, m2d[4], m2d[5], 0, 1, ]; }; const parseMatrix3D = (transform) => { if (transform === 'none') return identityMatrix3D; if (transform.startsWith('matrix3d')) { const matrix = transform.substring(8).match(numberRegex).map((n) => { return parseFloat(n); }); return matrix; } return toMatrix3d(transform.match(numberRegex).map((n) => parseFloat(n))); }; const nextPreserve3d = (i, list) => { return i + 1 < list.length && list[i + 1].transformStyle === 'preserve-3d'; }; const finalNormal = (startIndex, list) => { let i = startIndex; let normal = findNormal(parseMatrix3D(list[i].transform)); while (nextPreserve3d(i, list)) { i++; normal = findNormal(parseMatrix3D(list[i].transform), normal); } return normal; }; const checkBackface = (normal) => { let dot = viewVector[2] * normal[2]; if (Math.abs(dot) < TINY_NUMBER) { dot = 0; } return dot >= 0; }; const elIsBackface = (list) => { if (list.length > 1 && list[1].transformStyle === 'preserve-3d') { if (list[0].backfaceVisibility === 'hidden') { let normal = finalNormal(0, list); if (checkBackface(normal)) return true; } else { if (list[1].backfaceVisibility === 'hidden') { if (list[0].transform === 'none') { let normal = finalNormal(1, list); if (checkBackface(normal)) return true; } } let normal = finalNormal(0, list); return isElementOrthogonalWithView(normal); } } else { for (let i = 0; i < list.length; i++) { if (i > 0 && list[i].transformStyle === 'preserve-3d') { continue; } if (list[i].backfaceVisibility === 'hidden' && list[i].transform.startsWith('matrix3d')) { let normal = findNormal(parseMatrix3D(list[i].transform)); if (checkBackface(normal)) return true; } } } return false; }; const extractTransformInfoFromElements = (elem, list = []) => { const info = extractTransformInfo(elem); if (info) { list.push(info); } const parent = $elements.getParent(elem); if ($document.isDocument(parent) || parent === null) return list; return extractTransformInfoFromElements(parent, list); }; const isElementOrthogonalWithView = (normal) => { const dot = viewVector[2] * normal[2]; return Math.abs(dot) < TINY_NUMBER; }; const toUnitVector = (v) => { const length = Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]); return [v[0] / length, v[1] / length, v[2] / length]; }; const findNormal = (matrix, normal = defaultNormal) => { const m = matrix; const v = normal; const computedNormal = [ m[0] * v[0] + m[4] * v[1] + m[8] * v[2], m[1] * v[0] + m[5] * v[1] + m[9] * v[2], m[2] * v[0] + m[6] * v[1] + m[10] * v[2], ]; return toUnitVector(computedNormal); }; const is3DMatrixScaledTo0 = (m3d) => { const xAxisScaledTo0 = m3d[0] === 0 && m3d[4] === 0 && m3d[8] === 0; const yAxisScaledTo0 = m3d[1] === 0 && m3d[5] === 0 && m3d[9] === 0; const zAxisScaledTo0 = m3d[2] === 0 && m3d[6] === 0 && m3d[10] === 0; if (xAxisScaledTo0 || yAxisScaledTo0 || zAxisScaledTo0) return true; return false; }; const isTransformedToZero = ({ transform }) => { if (transform === 'none') return false; if (transform.startsWith('matrix3d')) { const matrix3d = parseMatrix3D(transform); if (is3DMatrixScaledTo0(matrix3d)) return true; const normal = findNormal(matrix3d); return isElementOrthogonalWithView(normal); } const m = parseMatrix2D(transform); if (is2DMatrixScaledTo0(m)) return true; return false; }; const parseMatrix2D = (transform) => { return transform.match(numberRegex).map((n) => parseFloat(n)); }; const is2DMatrixScaledTo0 = (m) => { const xAxisScaledTo0 = m[0] === 0 && m[2] === 0; const yAxisScaledTo0 = m[1] === 0 && m[3] === 0; if (xAxisScaledTo0 || yAxisScaledTo0) return true; return false; }; const elIsTransformedToZero = (list) => { if (list.some((info) => info.transformStyle === 'preserve-3d')) { const normal = finalNormal(0, list); return isElementOrthogonalWithView(normal); } return !!_.find(list, (info) => isTransformedToZero(info)); }; const detectVisibility = (elem) => { const list = extractTransformInfoFromElements(elem); if (existsInvisibleBackface(list)) return elIsBackface(list) ? 'backface' : 'visible'; return elIsTransformedToZero(list) ? 'transformed' : 'visible'; }; return { detectVisibility } })(); const $coordinates = (() => { const getElementAtPointFromViewport = (doc, x, y) => $elements.elementFromPoint(doc, x, y); const isAutIframe = (win) => { const parent = win.parent; return $window.isWindow(parent) && !$elements.getNativeProp(parent, 'frameElement'); }; const getFirstValidSizedRect = (el) => { return _.find(el.getClientRects(), (rect) => rect.width && rect.height) || el.getBoundingClientRect(); }; const getCoordsByPosition = (left, top, xPosition = 'center', yPosition = 'center') => { const getLeft = () => { switch (xPosition) { case 'left': return Math.ceil(left); case 'center': return Math.floor(left); case 'right': return Math.floor(left) - 1; } }; const getTop = () => { switch (yPosition) { case 'top': return Math.ceil(top); case 'center': return Math.floor(top); case 'bottom': return Math.floor(top) - 1; } }; return { x: getLeft(), y: getTop(), }; }; const getCenterCoordinates = (rect) => { const x = rect.left + (rect.width / 2); const y = rect.top + (rect.height / 2); return getCoordsByPosition(x, y, 'center', 'center'); }; const getElementPositioning = (el) => { let autFrame; const win = $window.getWindowByElement(el); const rect = getFirstValidSizedRect(el); const getRectFromAutIframe = (rect) => { let x = 0; let y = 0; let curWindow = win; let frame; while ($window.isWindow(curWindow) && !isAutIframe(curWindow) && curWindow.parent !== curWindow) { frame = $elements.getNativeProp(curWindow, 'frameElement'); if (curWindow && frame) { const frameRect = frame.getBoundingClientRect(); x += frameRect.left; y += frameRect.top; } curWindow = curWindow.parent; } autFrame = curWindow; return { left: x + rect.left, top: y + rect.top, right: x + rect.right, bottom: y + rect.top, width: rect.width, height: rect.height, }; }; const rectFromAut = getRectFromAutIframe(rect); const rectFromAutCenter = getCenterCoordinates(rectFromAut); const rectCenter = getCenterCoordinates(rect); const topCenter = Math.ceil(rectCenter.y); const leftCenter = Math.ceil(rectCenter.x); return { scrollTop: el.scrollTop, scrollLeft: el.scrollLeft, width: rect.width, height: rect.height, fromElViewport: { doc: win.document, top: rect.top, left: rect.left, right: rect.right, bottom: rect.bottom, topCenter, leftCenter, }, fromElWindow: { top: Math.ceil(rect.top + win.scrollY), left: rect.left + win.scrollX, topCenter: Math.ceil(topCenter + win.scrollY), leftCenter: leftCenter + win.scrollX, }, fromAutWindow: { top: Math.ceil(rectFromAut.top + autFrame.scrollY), left: rectFromAut.left + autFrame.scrollX, topCenter: Math.ceil(rectFromAutCenter.y + autFrame.scrollY), leftCenter: rectFromAutCenter.x + autFrame.scrollX, }, }; }; return { getElementPositioning, getElementAtPointFromViewport } })(); const { // find isAncestor, isChild, isDescendent, isUndefinedOrHTMLBodyDoc, getParent, getFirstParentWithTagName, getAllParents, // elementHelpers isElement, isBody, isHTML, isOption, isOptgroup, // complexElements elOrAncestorIsFixedOrSticky, isFocusable, // detached isDetached, // utils stringify: stringifyElement } = $elements; const isZeroLengthAndTransformNone = (width, height, transform) => (width <= 0 && transform === 'none') || (height <= 0 && transform === 'none'); const isZeroLengthAndOverflowHidden = (width, height, overflowHidden) => (width <= 0 && overflowHidden) || (height <= 0 && overflowHidden); const elOffsetWidth = elem => elem.offsetWidth; const elOffsetHeight = elem => elem.offsetHeight; const elHasNoOffsetWidthOrHeight = elem => (elOffsetWidth(elem) <= 0) || (elOffsetHeight(elem) <= 0); const elHasVisibilityHidden = elem => getComputedStyle(elem).getPropertyValue('visibility') === 'hidden'; const elHasVisibilityCollapse = elem => getComputedStyle(elem).getPropertyValue('visibility') === 'collapse'; const elHasVisibilityHiddenOrCollapse = ($el) => elHasVisibilityHidden($el) || elHasVisibilityCollapse($el); const elHasOpacityZero = elem => getComputedStyle(elem).getPropertyValue('opacity') === '0'; const elHasDisplayNone = elem => getComputedStyle(elem).getPropertyValue('display') === 'none'; const elHasDisplayInline = elem => getComputedStyle(elem).getPropertyValue('display') === 'inline'; const elHasOverflowHidden = elem => { const style = getComputedStyle(elem); const cssOverflow = [ style.getPropertyValue('overflow'), style.getPropertyValue('overflow-y'), style.getPropertyValue('overflow-x') ]; return cssOverflow.includes('hidden'); }; const elHasPositionRelative = elem => getComputedStyle(elem).getPropertyValue('position') === 'relative'; const elHasPositionAbsolute = elem => getComputedStyle(elem).getPropertyValue('position') === 'absolute'; const ensureEl = (el, methodName) => { if (!isElement(el)) { throw new Error(`\`${methodName}\` failed because it requires a DOM element. The subject received was: \`${el}\``); } }; const elHasNoEffectiveWidthOrHeight = (el) => { const style = getComputedStyle(el); const transform = style.getPropertyValue('transform'); const width = elOffsetWidth(el); const height = elOffsetHeight(el); const overflowHidden = elHasOverflowHidden(el); return isZeroLengthAndTransformNone(width, height, transform) || isZeroLengthAndOverflowHidden(width, height, overflowHidden) || (el.getClientRects().length <= 0); }; const elDescendentsHavePositionFixedOrAbsolute = function (parent, child) { const parents = getAllParents(child, parent); const arr = [...parents, child]; return arr.some(elem => fixedOrAbsoluteRe.test(getComputedStyle(elem).getPropertyValue('position'))) // const $els = $jquery.wrap(parents).add(child); // return _.some($els.get(), (el) => { // return fixedOrAbsoluteRe.test($jquery.wrap(el).css('position')); // }); }; const elIsHiddenByAncestors = (elem, checkOpacity, origEl = elem) => { const parent = getParent(elem); if (isUndefinedOrHTMLBodyDoc(parent)) return false; if (elHasOpacityZero(parent) && checkOpacity) return true; if (elHasOverflowHidden(parent) && elHasNoEffectiveWidthOrHeight(parent)) return !elDescendentsHavePositionFixedOrAbsolute(parent, origEl); return elIsHiddenByAncestors(parent, checkOpacity, origEl); }; const elAtCenterPoint = elem => { const doc = $document.getDocumentFromElement(elem); const elProps = $coordinates.getElementPositioning(elem); const { topCenter, leftCenter } = elProps.fromElViewport; const el = $coordinates.getElementAtPointFromViewport(doc, leftCenter, topCenter); if (el) return el }; const elIsNotElementFromPoint = elem => { const elAtPoint = elAtCenterPoint(elem); if (isDescendent(elem, elAtPoint)) return false; if ((getComputedStyle(elem).getPropertyValue('pointer-events') === 'none' || getComputedStyle(elem.parentElement).getPropertyValue('pointer-events') === 'none') && (elAtPoint && isAncestor(elem, elAtPoint))) return false; return true; }; const elHasClippableOverflow = elem => { const style = getComputedStyle(elem) return OVERFLOW_PROPS.includes(style.getPropertyValue('overflow')) || OVERFLOW_PROPS.includes(style.getPropertyValue('overflow-y')) || OVERFLOW_PROPS.includes(style.getPropertyValue('overflow-x')); }; const canClipContent = (elem, ancestor) => { if (!elHasClippableOverflow(ancestor)) return false; const offsetParent = elem.offsetParent; if (!elHasPositionRelative(elem) && isAncestor(ancestor, offsetParent)) return false; if (elHasPositionAbsolute(offsetParent) && isChild(ancestor, offsetParent)) return false; return true; }; const elIsOutOfBoundsOfAncestorsOverflow = (elem, ancestor = getParent(elem)) => { if (isUndefinedOrHTMLBodyDoc(ancestor)) return false; const elProps = $coordinates.getElementPositioning(elem); if (canClipContent(elem, ancestor)) { const ancestorProps = $coordinates.getElementPositioning(ancestor); if ((elProps.fromElWindow.left > (ancestorProps.width + ancestorProps.fromElWindow.left)) || ((elProps.fromElWindow.left + elProps.width) < ancestorProps.fromElWindow.left) || (elProps.fromElWindow.top > (ancestorProps.height + ancestorProps.fromElWindow.top)) || ((elProps.fromElWindow.top + elProps.height) < ancestorProps.fromElWindow.top)) return true; } return elIsOutOfBoundsOfAncestorsOverflow(elem, getParent(ancestor)); }; const isHiddenByAncestors = (elem, methodName = 'isHiddenByAncestors()', options = { checkOpacity: true }) => { ensureEl(elem, methodName); if (elIsHiddenByAncestors(elem, options.checkOpacity)) return true; if (elOrAncestorIsFixedOrSticky(elem)) return elIsNotElementFromPoint(elem); return elIsOutOfBoundsOfAncestorsOverflow(elem); }; const fixedOrAbsoluteRe = /(fixed|absolute)/; const OVERFLOW_PROPS = ['hidden', 'scroll', 'auto']; const isVisible = elem => !isHidden(elem, 'isVisible()'); const isHidden = (el, methodName = 'isHidden()', options = { checkOpacity: true }) => { if (isStrictlyHidden(el, methodName, options, isHidden)) return true; return isHiddenByAncestors(el, methodName, options); }; const isStrictlyHidden = (elem, methodName = 'isStrictlyHidden()', options = { checkOpacity: true }, recurse) => { ensureEl(elem, methodName); if (isBody(elem) || isHTML(elem)) return false; if (isOption(elem) || isOptgroup(elem)) { if (elHasDisplayNone(elem)) return true; const select = getFirstParentWithTagName(elem, 'select'); if (select) return recurse ? recurse(select, methodName, options) : isStrictlyHidden(select, methodName, options); } if (elHasNoEffectiveWidthOrHeight(elem)) { if (elHasDisplayInline(elem)) return !elHasVisibleChild(elem); return true; } if (elHasVisibilityHiddenOrCollapse(elem)) return true; // try { if ($transform.detectVisibility(elem) !== 'visible') return true; // } catch(err){} if (elHasOpacityZero(elem) && options.checkOpacity) return true; return false; }; const isW3CRendered = elem => !(parentHasDisplayNone(elem) || getComputedStyle(elem).getPropertyValue('visibility') === 'hidden'); const isW3CFocusable = elem => isFocusable(elem) && isW3CRendered(elem); const elHasVisibleChild = elem => Array.from(elem.children).some(child => isVisible(child)); const parentHasNoOffsetWidthOrHeightAndOverflowHidden = function ($el) { if (isUndefinedOrHTMLBodyDoc($el)) return false; if (elHasOverflowHidden($el) && elHasNoEffectiveWidthOrHeight($el)) return $el; return parentHasNoOffsetWidthOrHeightAndOverflowHidden(getParent($el)); }; const parentHasDisplayNone = elem => { if ($document.isDocument(elem) || elem === null) return false; if (elHasDisplayNone(elem)) return elem; return parentHasDisplayNone(getParent(elem)); }; const parentHasVisibilityHidden = elem => { if ($document.isDocument(elem) || elem === null) return false; if (elHasVisibilityHidden(elem)) return elem; return parentHasVisibilityHidden(getParent(elem)); }; const parentHasVisibilityCollapse = elem => { if ($document.isDocument(elem) || elem === null) return false; if (elHasVisibilityCollapse(elem)) return elem; return parentHasVisibilityCollapse(getParent(elem)); }; const parentHasOpacityZero = elem => { if ($document.isDocument(elem) || elem === null) return false; if (elHasOpacityZero(elem)) return elem; return parentHasOpacityZero(getParent(elem)); }; const getReasonIsHidden = (elem, options = { checkOpacity: true }) => { const node = stringifyElement(elem, 'short'); let width = elOffsetWidth(elem); let height = elOffsetHeight(elem); let $parent; let parentNode; if (elHasDisplayNone(elem)) return `This element \`${node}\` is not visible because it has CSS property: \`display: none\``; if ($parent = parentHasDisplayNone(getParent(elem))) { parentNode = stringifyElement($parent, 'short'); return `This element \`${node}\` is not visible because its parent \`${parentNode}\` has CSS property: \`display: none\``; } if ($parent = parentHasVisibilityHidden(getParent(elem))) { parentNode = stringifyElement($parent, 'short'); return `This element \`${node}\` is not visible because its parent \`${parentNode}\` has CSS property: \`visibility: hidden\``; } if ($parent = parentHasVisibilityCollapse(getParent(elem))) { parentNode = stringifyElement($parent, 'short'); return `This element \`${node}\` is not visible because its parent \`${parentNode}\` has CSS property: \`visibility: collapse\``; } if (isDetached(elem)) return `This element \`${node}\` is not visible because it is detached from the DOM`; if (elHasVisibilityHidden(elem)) return `This element \`${node}\` is not visible because it has CSS property: \`visibility: hidden\``; if (elHasVisibilityCollapse(elem)) return `This element \`${node}\` is not visible because it has CSS property: \`visibility: collapse\``; if (elHasOpacityZero(elem) && options.checkOpacity) return `This element \`${node}\` is not visible because it has CSS property: \`opacity: 0\``; if (($parent = parentHasOpacityZero(getParent(elem))) && options.checkOpacity) { parentNode = stringifyElement($parent, 'short'); return `This element \`${node}\` is not visible because its parent \`${parentNode}\` has CSS property: \`opacity: 0\``; } if (elHasNoOffsetWidthOrHeight(elem)) return `This element \`${node}\` is not visible because it has an effective width and height of: \`${width} x ${height}\` pixels.`; const transformResult = $transform.detectVisibility(elem); if (transformResult === 'transformed') return `This element \`${node}\` is not visible because it is hidden by transform.`; if (transformResult === 'backface') return `This element \`${node}\` is not visible because it is rotated and its backface is hidden.`; if ($parent = parentHasNoOffsetWidthOrHeightAndOverflowHidden(getParent(elem))) { parentNode = stringifyElement($parent, 'short'); width = elOffsetWidth($parent); height = elOffsetHeight($parent); return `This element \`${node}\` is not visible because its parent \`${parentNode}\` has CSS property: \`overflow: hidden\` and an effective width and height of: \`${width} x ${height}\` pixels.`; } if (elOrAncestorIsFixedOrSticky(elem)) { if (elIsNotElementFromPoint(elem)) { const covered = stringifyElement(elAtCenterPoint(elem)); if (covered) return `This element \`${node}\` is not visible because it has CSS property: \`position: fixed\` and it's being covered by another element:\n\n\`${covered}\``; return `This element \`${node}\` is not visible because its ancestor has \`position: fixed\` CSS property and it is overflowed by other elements. How about scrolling to the element with \`cy.scrollIntoView()\`?`; } } else { if (elIsOutOfBoundsOfAncestorsOverflow(elem)) return `This element \`${node}\` is not visible because its content is being clipped by one of its parent elements, which has a CSS property of overflow: \`hidden\`, \`scroll\` or \`auto\``; } return `This element \`${node}\` is not visible.`; }; Object.assign(exports, { isVisible, isHidden, isStrictlyHidden, isHiddenByAncestors, getReasonIsHidden, isW3CFocusable, isW3CRendered }) })