- // ==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
- })
- })