Greasy Fork is available in English.

cypress-visibility

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

สคริปต์นี้ไม่ควรถูกติดตั้งโดยตรง มันเป็นคลังสำหรับสคริปต์อื่น ๆ เพื่อบรรจุด้วยคำสั่งเมทา // @require https://update.greasyfork.org/scripts/482857/1305426/cypress-visibility.js

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