// ==UserScript==
// @name cypress-visibility
// @namespace flomk.userscripts
// @version 1.3
// @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.cypressVisibility={});
})(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;
// removed because I am just trying to find out if the element is "visible" outside the viewport
// 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
})
})