cypress-visibility

Code - github/cypress-io/cypress - to check if an element is visible

Od 01.01.2024.. Pogledajte najnovija verzija.

Ovu skriptu ne treba izravno instalirati. To je biblioteka za druge skripte koje se uključuju u meta direktivu // @require https://update.greatest.deepsurf.us/scripts/482857/1304414/cypress-visibility.js

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

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