cypress-visibility

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

目前为 2024-01-03 提交的版本。查看 最新版本

此脚本不应直接安装,它是一个供其他脚本使用的外部库。如果您需要使用该库,请在脚本元属性加入:// @require https://update.cn-greasyfork.org/scripts/482857/1305426/cypress-visibility.js

  1. // ==UserScript==
  2. // @name cypress-visibility
  3. // @namespace flomk.userscripts
  4. // @version 1.3
  5. // @description Code - github/cypress-io/cypress - to check if an element is visible
  6. // @author flomk
  7. // @grant none
  8. // @require https://unpkg.com/lodash
  9. // @include *
  10. // @connect unpkg.com
  11. // ==/UserScript==
  12.  
  13. ((global, factory) => {
  14. global = typeof globalThis !== 'undefined' ? globalThis : global || self;
  15. factory(global.cypressVisibility={});
  16. })(this, exports => {
  17. const $document = (() => {
  18. const docNode = Node.DOCUMENT_NODE;
  19. const docFragmentNode = Node.DOCUMENT_FRAGMENT_NODE;
  20. const isDocument = (obj) => {
  21. try {
  22. let node = obj;
  23. return (node === null || node === void 0 ? void 0 : node.nodeType) === docNode || (node === null || node === void 0 ? void 0 : node.nodeType) === docFragmentNode;
  24. }
  25. catch (error) {
  26. return false;
  27. }
  28. };
  29. const hasActiveWindow = (doc) => {
  30. if (navigator.appCodeName === 'Mozilla' && !doc.location) return false;
  31. return !!doc.defaultView;
  32. };
  33. const getDocumentFromElement = (el) => {
  34. if (isDocument(el)) return el;
  35. return el.ownerDocument;
  36. };
  37. return {
  38. isDocument,
  39. hasActiveWindow,
  40. getDocumentFromElement
  41. }
  42. })();
  43.  
  44.  
  45. const $window = (() => {
  46. const isWindow = function (obj) {
  47. try {
  48. return Boolean(obj && obj.window === obj);
  49. }
  50. catch (error) {
  51. return false;
  52. }
  53. };
  54. const getWindowByDocument = (doc) => {
  55. // parentWindow for IE
  56. return doc.defaultView || doc.parentWindow
  57. }
  58. const getWindowByElement = function (el) {
  59. if ($window.isWindow(el)) {
  60. return el
  61. }
  62.  
  63. const doc = $document.getDocumentFromElement(el)
  64.  
  65. return getWindowByDocument(doc)
  66. }
  67. return {
  68. isWindow,
  69. getWindowByElement
  70. }
  71. })();
  72.  
  73. const $detached = (() => {
  74.  
  75. const isAttached = function (elem) {
  76. if ($window.isWindow(elem)) return true;
  77.  
  78. const nodes = [];
  79. if (elem) nodes.push(elem);
  80. if (nodes.length === 0) return false;
  81.  
  82. return nodes.every((node) => {
  83. const doc = $document.getDocumentFromElement(node);
  84. if (!$document.hasActiveWindow(doc)) return false;
  85. return node.isConnected;
  86. });
  87. };
  88.  
  89. const isDetached = elem => !isAttached(elem)
  90.  
  91. return {
  92. isDetached
  93. }
  94. })();
  95.  
  96. const $utils = (() => {
  97. function switchCase(value, casesObj, defaultKey = 'default') {
  98. if (_.has(casesObj, value)) return _.result(casesObj, value);
  99. if (_.has(casesObj, defaultKey)) return _.result(casesObj, defaultKey);
  100. const keys = _.keys(casesObj);
  101. throw new Error(`The switch/case value: '${value}' did not match any cases: ${keys.join(', ')}.`);
  102. }
  103.  
  104. const stringify = (el, form = 'long') => {
  105. // if we are formatting the window object
  106. if ($window.isWindow(el)) return '<window>';
  107.  
  108. // if we are formatting the document object
  109. if ($document.isDocument(el)) return '<document>';
  110.  
  111. // convert this to jquery if its not already one
  112. // const $el = $jquery.wrap(el);
  113.  
  114. const long = () => {
  115. const str = el.cloneNode().outerHTML
  116.  
  117. const text = _.chain(el.textContent).clean().truncate({ length: 10 }).value();
  118. const children = el.children.length;
  119.  
  120. if (children) return str.replace('></', '>...</');
  121.  
  122. if (text) return str.replace('></', `>${text}</`);
  123.  
  124. return str;
  125. };
  126.  
  127. const short = () => {
  128. const id = el.id;
  129. const klass = el.getAttribute('class');
  130. let str = el.tagName.toLowerCase();
  131.  
  132. if (id) str += `#${id}`;
  133.  
  134. // using attr here instead of class because
  135. // svg's return an SVGAnimatedString object
  136. // instead of a normal string when calling
  137. // the property 'class'
  138. if (klass) str += `.${klass.split(/\s+/).join('.')}`;
  139.  
  140. // if we have more than one element,
  141. // format it so that the user can see there's more
  142. // if ($el.length > 1) {
  143. // return `[ <${str}>, ${$el.length - 1} more... ]`;
  144. // }
  145.  
  146. return `<${str}>`;
  147. };
  148.  
  149. return switchCase(form, {
  150. long,
  151. short
  152. });
  153. };
  154. return { stringify }
  155. })();
  156.  
  157. const $contenteditable = (() => {
  158. const isContentEditable = (el) => {
  159. return $nativeProps.getNativeProp(el, 'isContentEditable') || $document.getDocumentFromElement(el).designMode === 'on';
  160. };
  161.  
  162. const isDesignModeDocumentElement = el => {
  163. return isElement(el) && $elementHelpers.getTagName(el) === 'html' && isContentEditable(el)
  164. }
  165.  
  166. return {
  167. isDesignModeDocumentElement
  168. }
  169. })();
  170.  
  171. const $complexElements = (() => {
  172. const fixedOrStickyRe = /(fixed|sticky)/;
  173.  
  174. const focusableSelectors = [
  175. 'a[href]',
  176. 'area[href]',
  177. 'input:not([disabled])',
  178. 'select:not([disabled])',
  179. 'textarea:not([disabled])',
  180. 'button:not([disabled])',
  181. 'iframe',
  182. '[tabindex]',
  183. '[contenteditable]'
  184. ];
  185. const isFocusable = elem => focusableSelectors.some(sel => elem.matches(sel)) || $contenteditable.isDesignModeDocumentElement(elem);
  186.  
  187. const getFirstFixedOrStickyPositionParent = elem => {
  188. if (isUndefinedOrHTMLBodyDoc(elem)) return null;
  189.  
  190. if (fixedOrStickyRe.test(getComputedStyle(elem).position)) return elem;
  191.  
  192. /* walk up the tree until we find an element with a fixed/sticky position */
  193. return $find.findParent(elem, node => {
  194.  
  195. if (node.nodeType === Node.DOCUMENT_NODE || node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) return null
  196. else if (fixedOrStickyRe.test(getComputedStyle(node).position)) return node;
  197.  
  198. return null;
  199. });
  200. };
  201.  
  202. const elOrAncestorIsFixedOrSticky = elem => {
  203. return !!getFirstFixedOrStickyPositionParent(elem);
  204. };
  205. return {
  206. isFocusable,
  207. elOrAncestorIsFixedOrSticky
  208. }
  209. })();
  210.  
  211.  
  212. const $shadow = (() => {
  213. const isShadowRoot = (maybeRoot) => {
  214. return (maybeRoot === null || maybeRoot === void 0 ? void 0 : maybeRoot.toString()) === '[object ShadowRoot]';
  215. };
  216. const isWithinShadowRoot = (node) => {
  217. return isShadowRoot(node.getRootNode());
  218. };
  219. const getShadowElementFromPoint = (node, x, y) => {
  220. var _a;
  221. const nodeFromPoint = (_a = node === null || node === void 0 ? void 0 : node.shadowRoot) === null || _a === void 0 ? void 0 : _a.elementFromPoint(x, y);
  222. if (!nodeFromPoint || nodeFromPoint === node)
  223. return node;
  224. return getShadowElementFromPoint(nodeFromPoint, x, y);
  225. };
  226.  
  227. return {
  228. isWithinShadowRoot,
  229. getShadowElementFromPoint
  230. }
  231. })();
  232.  
  233.  
  234. const $find = (() => {
  235. const getParentNode = el => {
  236. // if the element has a direct parent element,
  237. // simply return it.
  238. if (el.parentElement) return el.parentElement;
  239.  
  240. const root = el.getRootNode();
  241.  
  242. // if the element is inside a shadow root,
  243. // return the host of the root.
  244. if (root && $shadow.isWithinShadowRoot(el)) return root.host;
  245.  
  246. return null;
  247. };
  248.  
  249. const getParent = elem => getParentNode(elem);
  250.  
  251. const findParent = (el, condition) => {
  252. const collectParent = node => {
  253. const parent = getParentNode(node);
  254.  
  255. if (!parent) return null;
  256.  
  257. const parentMatchingCondition = condition(parent, node);
  258.  
  259. if (parentMatchingCondition) return parentMatchingCondition;
  260.  
  261. return collectParent(parent);
  262. };
  263.  
  264. return collectParent(el);
  265. };
  266. const isUndefinedOrHTMLBodyDoc = elem => {
  267. return !elem || elem.matches('body,html') || $document.isDocument(elem);
  268. };
  269.  
  270. const getAllParents = (el, untilSelectorOrEl) => {
  271. const collectParents = (parents, node) => {
  272. const parent = getParentNode(node);
  273. const selOrElemMatch = _.isString(untilSelectorOrEl) ? parent.matches(untilSelectorOrEl) : parent === untilSelectorOrEl;
  274. // if (!parent || (untilSelectorOrEl && parent.matches(untilSelectorOrEl))) return parents;
  275. if (!parent || (untilSelectorOrEl && selOrElemMatch)) return parents;
  276. return collectParents(parents.concat(parent), parent);
  277. };
  278. return collectParents([], el);
  279. };
  280. const isAncestor = (elem, maybeAncestor) => {
  281. return getAllParents(elem).indexOf(maybeAncestor) >= 0;
  282. };
  283. const isChild = (elem, maybeChild) => {
  284. return Array.from(elem.children).indexOf(maybeChild) >= 0;
  285. };
  286. const isDescendent = (elem1, elem2) => {
  287. if (!elem2) return false;
  288. if (elem1 === elem2) return true;
  289. return (findParent(elem2, node => {
  290. if (node === elem1) return node;
  291. }) === elem1);
  292. };
  293.  
  294. const getTagName = el => {
  295. const tagName = el.tagName || '';
  296. return tagName.toLowerCase();
  297. };
  298. const getFirstParentWithTagName = (elem, tagName) => {
  299. if (isUndefinedOrHTMLBodyDoc(elem) || !tagName) return null;
  300. if (getTagName(elem) === tagName) return elem;
  301. return findParent(elem, node => {
  302. if (getTagName(node) === tagName) return node;
  303. return null;
  304. });
  305. };
  306.  
  307. const elementFromPoint = (doc, x, y) => {
  308. let elFromPoint = doc.elementFromPoint(x, y);
  309. return $shadow.getShadowElementFromPoint(elFromPoint, x, y);
  310. };
  311. return {
  312. isAncestor,
  313. isChild,
  314. isDescendent,
  315. isUndefinedOrHTMLBodyDoc,
  316. getParent,
  317. findParent,
  318. elementFromPoint,
  319. getFirstParentWithTagName,
  320. getAllParents
  321. }
  322. })();
  323.  
  324.  
  325. const $elementHelpers = (() => {
  326. const getTagName = el => {
  327. const tagName = el.tagName || '';
  328. return tagName.toLowerCase();
  329. };
  330. const isElement = function (obj) {
  331. try {
  332. return Boolean(obj && _.isElement(obj));
  333. }
  334. catch (error) {
  335. return false;
  336. }
  337. };
  338. const isInput = (el) => getTagName(el) === 'input';
  339. const isTextarea = (el) => getTagName(el) === 'textarea';
  340. const isSelect = (el) => getTagName(el) === 'select';
  341. const isButton = (el) => getTagName(el) === 'button';
  342. const isBody = (el) => getTagName(el) === 'body';
  343. const isHTML = el => getTagName(el) === 'html';
  344. const isOption = el => getTagName(el) === 'option';
  345. const isOptgroup = el => getTagName(el) === 'optgroup';
  346. const isSvg = function (el) {
  347. try {
  348. return 'ownerSVGElement' in el;
  349. }
  350. catch (error) {
  351. return false;
  352. }
  353. };
  354. return {
  355. isSvg,
  356. isBody,
  357. isHTML,
  358. isOption,
  359. isElement,
  360. isOptgroup,
  361. isButton,
  362. isSelect,
  363. isTextarea,
  364. isInput
  365. }
  366. })();
  367.  
  368.  
  369. const $nativeProps = (() => {
  370. const descriptor = (klass, prop) => {
  371. const desc = Object.getOwnPropertyDescriptor(window[klass].prototype, prop);
  372. if (desc === undefined) {
  373. throw new Error(`Error, could not get property descriptor for ${klass} ${prop}. This should never happen`);
  374. }
  375. return desc;
  376. };
  377. const _isContentEditable = function () {
  378. if ($elementHelpers.isSvg(this)) return false;
  379. return descriptor('HTMLElement', 'isContentEditable').get;
  380. };
  381. const _getValue = function () {
  382. if ($elementHelpers.isInput(this)) return descriptor('HTMLInputElement', 'value').get;
  383. if ($elementHelpers.isTextarea(this)) return descriptor('HTMLTextAreaElement', 'value').get;
  384. if ($elementHelpers.isSelect(this)) return descriptor('HTMLSelectElement', 'value').get;
  385. if ($elementHelpers.isButton(this)) return descriptor('HTMLButtonElement', 'value').get;
  386. return descriptor('HTMLOptionElement', 'value').get;
  387. };
  388. const _getSelectionStart = function () {
  389. if ($elementHelpers.isInput(this)) return descriptor('HTMLInputElement', 'selectionStart').get;
  390. if ($elementHelpers.isTextarea(this)) return descriptor('HTMLTextAreaElement', 'selectionStart').get;
  391. throw new Error('this should never happen, cannot get selectionStart');
  392. };
  393. const _getSelectionEnd = function () {
  394. if ($elementHelpers.isInput(this)) return descriptor('HTMLInputElement', 'selectionEnd').get;
  395. if ($elementHelpers.isTextarea(this)) return descriptor('HTMLTextAreaElement', 'selectionEnd').get;
  396. throw new Error('this should never happen, cannot get selectionEnd');
  397. };
  398. const _getType = function () {
  399. if ($elementHelpers.isInput(this)) return descriptor('HTMLInputElement', 'type').get;
  400. if ($elementHelpers.isButton(this)) return descriptor('HTMLButtonElement', 'type').get;
  401. throw new Error('this should never happen, cannot get type');
  402. };
  403. const _getMaxLength = function () {
  404. if ($elementHelpers.isInput(this)) return descriptor('HTMLInputElement', 'maxLength').get;
  405. if ($elementHelpers.isTextarea(this)) return descriptor('HTMLTextAreaElement', 'maxLength').get;
  406. throw new Error('this should never happen, cannot get maxLength');
  407. };
  408. const nativeGetters = {
  409. value: _getValue,
  410. isContentEditable: _isContentEditable,
  411. isCollapsed: descriptor('Selection', 'isCollapsed').get,
  412. selectionStart: _getSelectionStart,
  413. selectionEnd: _getSelectionEnd,
  414. type: _getType,
  415. activeElement: descriptor('Document', 'activeElement').get,
  416. body: descriptor('Document', 'body').get,
  417. frameElement: Object.getOwnPropertyDescriptor(window, 'frameElement').get,
  418. maxLength: _getMaxLength,
  419. };
  420. const getNativeProp = function (obj, prop) {
  421. const nativeProp = nativeGetters[prop];
  422. if (!nativeProp) {
  423. const props = _.keys(nativeGetters).join(', ');
  424. throw new Error(`attempted to use a native getter prop called: ${prop}. Available props are: ${props}`);
  425. }
  426. let retProp = nativeProp.call(obj, prop);
  427. if (_.isFunction(retProp)) {
  428. retProp = retProp.call(obj, prop);
  429. }
  430. return retProp;
  431. };
  432.  
  433. return {
  434. getNativeProp
  435. }
  436. })();
  437.  
  438.  
  439. const $elements = {
  440. ...$find,
  441. ...$elementHelpers,
  442. ...$complexElements,
  443. ...$detached,
  444. ...$utils,
  445. ...$nativeProps
  446. };
  447.  
  448. const $transform = (() => {
  449. const existsInvisibleBackface = (list) => {
  450. return !!_.find(list, { backfaceVisibility: 'hidden' });
  451. };
  452. const extractTransformInfo = (el) => {
  453. const style = getComputedStyle(el);
  454. const backfaceVisibility = style.getPropertyValue('backface-visibility');
  455. if (backfaceVisibility === '') return null;
  456. return {
  457. backfaceVisibility,
  458. transformStyle: style.getPropertyValue('transform-style'),
  459. transform: style.getPropertyValue('transform'),
  460. };
  461. };
  462. const numberRegex = /-?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?/g;
  463. const defaultNormal = [0, 0, 1];
  464. const viewVector = [0, 0, -1];
  465. const identityMatrix3D = [
  466. 1, 0, 0, 0,
  467. 0, 1, 0, 0,
  468. 0, 0, 1, 0,
  469. 0, 0, 0, 1,
  470. ];
  471. const TINY_NUMBER = 1e-5;
  472. const toMatrix3d = (m2d) => {
  473. return [
  474. m2d[0], m2d[1], 0, 0,
  475. m2d[2], m2d[3], 0, 0,
  476. 0, 0, 1, 0,
  477. m2d[4], m2d[5], 0, 1,
  478. ];
  479. };
  480. const parseMatrix3D = (transform) => {
  481. if (transform === 'none') return identityMatrix3D;
  482. if (transform.startsWith('matrix3d')) {
  483. const matrix = transform.substring(8).match(numberRegex).map((n) => {
  484. return parseFloat(n);
  485. });
  486. return matrix;
  487. }
  488. return toMatrix3d(transform.match(numberRegex).map((n) => parseFloat(n)));
  489. };
  490. const nextPreserve3d = (i, list) => {
  491. return i + 1 < list.length && list[i + 1].transformStyle === 'preserve-3d';
  492. };
  493. const finalNormal = (startIndex, list) => {
  494. let i = startIndex;
  495. let normal = findNormal(parseMatrix3D(list[i].transform));
  496. while (nextPreserve3d(i, list)) {
  497. i++;
  498. normal = findNormal(parseMatrix3D(list[i].transform), normal);
  499. }
  500. return normal;
  501. };
  502. const checkBackface = (normal) => {
  503. let dot = viewVector[2] * normal[2];
  504. if (Math.abs(dot) < TINY_NUMBER) {
  505. dot = 0;
  506. }
  507. return dot >= 0;
  508. };
  509. const elIsBackface = (list) => {
  510. if (list.length > 1 && list[1].transformStyle === 'preserve-3d') {
  511. if (list[0].backfaceVisibility === 'hidden') {
  512. let normal = finalNormal(0, list);
  513. if (checkBackface(normal)) return true;
  514. }
  515. else {
  516. if (list[1].backfaceVisibility === 'hidden') {
  517. if (list[0].transform === 'none') {
  518. let normal = finalNormal(1, list);
  519. if (checkBackface(normal)) return true;
  520. }
  521. }
  522. let normal = finalNormal(0, list);
  523. return isElementOrthogonalWithView(normal);
  524. }
  525. }
  526. else {
  527. for (let i = 0; i < list.length; i++) {
  528. if (i > 0 && list[i].transformStyle === 'preserve-3d') {
  529. continue;
  530. }
  531. if (list[i].backfaceVisibility === 'hidden' && list[i].transform.startsWith('matrix3d')) {
  532. let normal = findNormal(parseMatrix3D(list[i].transform));
  533. if (checkBackface(normal)) return true;
  534. }
  535. }
  536. }
  537. return false;
  538. };
  539. const extractTransformInfoFromElements = (elem, list = []) => {
  540. const info = extractTransformInfo(elem);
  541. if (info) {
  542. list.push(info);
  543. }
  544. const parent = $elements.getParent(elem);
  545. if ($document.isDocument(parent) || parent === null) return list;
  546. return extractTransformInfoFromElements(parent, list);
  547. };
  548. const isElementOrthogonalWithView = (normal) => {
  549. const dot = viewVector[2] * normal[2];
  550. return Math.abs(dot) < TINY_NUMBER;
  551. };
  552. const toUnitVector = (v) => {
  553. const length = Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
  554. return [v[0] / length, v[1] / length, v[2] / length];
  555. };
  556. const findNormal = (matrix, normal = defaultNormal) => {
  557. const m = matrix;
  558. const v = normal;
  559. const computedNormal = [
  560. m[0] * v[0] + m[4] * v[1] + m[8] * v[2],
  561. m[1] * v[0] + m[5] * v[1] + m[9] * v[2],
  562. m[2] * v[0] + m[6] * v[1] + m[10] * v[2],
  563. ];
  564. return toUnitVector(computedNormal);
  565. };
  566. const is3DMatrixScaledTo0 = (m3d) => {
  567. const xAxisScaledTo0 = m3d[0] === 0 && m3d[4] === 0 && m3d[8] === 0;
  568. const yAxisScaledTo0 = m3d[1] === 0 && m3d[5] === 0 && m3d[9] === 0;
  569. const zAxisScaledTo0 = m3d[2] === 0 && m3d[6] === 0 && m3d[10] === 0;
  570. if (xAxisScaledTo0 || yAxisScaledTo0 || zAxisScaledTo0) return true;
  571. return false;
  572. };
  573. const isTransformedToZero = ({ transform }) => {
  574. if (transform === 'none') return false;
  575. if (transform.startsWith('matrix3d')) {
  576. const matrix3d = parseMatrix3D(transform);
  577. if (is3DMatrixScaledTo0(matrix3d)) return true;
  578. const normal = findNormal(matrix3d);
  579. return isElementOrthogonalWithView(normal);
  580. }
  581. const m = parseMatrix2D(transform);
  582. if (is2DMatrixScaledTo0(m)) return true;
  583. return false;
  584. };
  585. const parseMatrix2D = (transform) => {
  586. return transform.match(numberRegex).map((n) => parseFloat(n));
  587. };
  588. const is2DMatrixScaledTo0 = (m) => {
  589. const xAxisScaledTo0 = m[0] === 0 && m[2] === 0;
  590. const yAxisScaledTo0 = m[1] === 0 && m[3] === 0;
  591. if (xAxisScaledTo0 || yAxisScaledTo0) return true;
  592. return false;
  593. };
  594. const elIsTransformedToZero = (list) => {
  595. if (list.some((info) => info.transformStyle === 'preserve-3d')) {
  596. const normal = finalNormal(0, list);
  597. return isElementOrthogonalWithView(normal);
  598. }
  599. return !!_.find(list, (info) => isTransformedToZero(info));
  600. };
  601. const detectVisibility = (elem) => {
  602. const list = extractTransformInfoFromElements(elem);
  603. if (existsInvisibleBackface(list)) return elIsBackface(list) ? 'backface' : 'visible';
  604. return elIsTransformedToZero(list) ? 'transformed' : 'visible';
  605. };
  606. return {
  607. detectVisibility
  608. }
  609. })();
  610.  
  611. const $coordinates = (() => {
  612. const getElementAtPointFromViewport = (doc, x, y) => $elements.elementFromPoint(doc, x, y);
  613. const isAutIframe = (win) => {
  614. const parent = win.parent;
  615. return $window.isWindow(parent) && !$elements.getNativeProp(parent, 'frameElement');
  616. };
  617. const getFirstValidSizedRect = (el) => {
  618. return _.find(el.getClientRects(), (rect) => rect.width && rect.height) || el.getBoundingClientRect();
  619. };
  620.  
  621. const getCoordsByPosition = (left, top, xPosition = 'center', yPosition = 'center') => {
  622. const getLeft = () => {
  623. switch (xPosition) {
  624. case 'left': return Math.ceil(left);
  625. case 'center': return Math.floor(left);
  626. case 'right': return Math.floor(left) - 1;
  627. }
  628. };
  629. const getTop = () => {
  630. switch (yPosition) {
  631. case 'top': return Math.ceil(top);
  632. case 'center': return Math.floor(top);
  633. case 'bottom': return Math.floor(top) - 1;
  634. }
  635. };
  636. return {
  637. x: getLeft(),
  638. y: getTop(),
  639. };
  640. };
  641.  
  642. const getCenterCoordinates = (rect) => {
  643. const x = rect.left + (rect.width / 2);
  644. const y = rect.top + (rect.height / 2);
  645. return getCoordsByPosition(x, y, 'center', 'center');
  646. };
  647.  
  648. const getElementPositioning = (el) => {
  649. let autFrame;
  650. const win = $window.getWindowByElement(el);
  651. const rect = getFirstValidSizedRect(el);
  652. const getRectFromAutIframe = (rect) => {
  653. let x = 0;
  654. let y = 0;
  655. let curWindow = win;
  656. let frame;
  657. while ($window.isWindow(curWindow) && !isAutIframe(curWindow) && curWindow.parent !== curWindow) {
  658. frame = $elements.getNativeProp(curWindow, 'frameElement');
  659. if (curWindow && frame) {
  660. const frameRect = frame.getBoundingClientRect();
  661. x += frameRect.left;
  662. y += frameRect.top;
  663. }
  664. curWindow = curWindow.parent;
  665. }
  666. autFrame = curWindow;
  667. return {
  668. left: x + rect.left,
  669. top: y + rect.top,
  670. right: x + rect.right,
  671. bottom: y + rect.top,
  672. width: rect.width,
  673. height: rect.height,
  674. };
  675. };
  676. const rectFromAut = getRectFromAutIframe(rect);
  677. const rectFromAutCenter = getCenterCoordinates(rectFromAut);
  678. const rectCenter = getCenterCoordinates(rect);
  679. const topCenter = Math.ceil(rectCenter.y);
  680. const leftCenter = Math.ceil(rectCenter.x);
  681. return {
  682. scrollTop: el.scrollTop,
  683. scrollLeft: el.scrollLeft,
  684. width: rect.width,
  685. height: rect.height,
  686. fromElViewport: {
  687. doc: win.document,
  688. top: rect.top,
  689. left: rect.left,
  690. right: rect.right,
  691. bottom: rect.bottom,
  692. topCenter,
  693. leftCenter,
  694. },
  695. fromElWindow: {
  696. top: Math.ceil(rect.top + win.scrollY),
  697. left: rect.left + win.scrollX,
  698. topCenter: Math.ceil(topCenter + win.scrollY),
  699. leftCenter: leftCenter + win.scrollX,
  700. },
  701. fromAutWindow: {
  702. top: Math.ceil(rectFromAut.top + autFrame.scrollY),
  703. left: rectFromAut.left + autFrame.scrollX,
  704. topCenter: Math.ceil(rectFromAutCenter.y + autFrame.scrollY),
  705. leftCenter: rectFromAutCenter.x + autFrame.scrollX,
  706. },
  707. };
  708. };
  709. return {
  710. getElementPositioning,
  711. getElementAtPointFromViewport
  712. }
  713. })();
  714. const {
  715. // find
  716. isAncestor,
  717. isChild,
  718. isDescendent,
  719. isUndefinedOrHTMLBodyDoc,
  720. getParent,
  721. getFirstParentWithTagName,
  722. getAllParents,
  723.  
  724. // elementHelpers
  725. isElement,
  726. isBody,
  727. isHTML,
  728. isOption,
  729. isOptgroup,
  730.  
  731. // complexElements
  732. elOrAncestorIsFixedOrSticky,
  733. isFocusable,
  734.  
  735. // detached
  736. isDetached,
  737.  
  738.  
  739. // utils
  740. stringify: stringifyElement
  741. } = $elements;
  742.  
  743.  
  744. const isZeroLengthAndTransformNone = (width, height, transform) => (width <= 0 && transform === 'none') || (height <= 0 && transform === 'none');
  745. const isZeroLengthAndOverflowHidden = (width, height, overflowHidden) => (width <= 0 && overflowHidden) || (height <= 0 && overflowHidden);
  746. const elOffsetWidth = elem => elem.offsetWidth;
  747.  
  748. const elOffsetHeight = elem => elem.offsetHeight;
  749.  
  750. const elHasNoOffsetWidthOrHeight = elem => (elOffsetWidth(elem) <= 0) || (elOffsetHeight(elem) <= 0);
  751. const elHasVisibilityHidden = elem => getComputedStyle(elem).getPropertyValue('visibility') === 'hidden';
  752. const elHasVisibilityCollapse = elem => getComputedStyle(elem).getPropertyValue('visibility') === 'collapse';
  753. const elHasVisibilityHiddenOrCollapse = ($el) => elHasVisibilityHidden($el) || elHasVisibilityCollapse($el);
  754. const elHasOpacityZero = elem => getComputedStyle(elem).getPropertyValue('opacity') === '0';
  755. const elHasDisplayNone = elem => getComputedStyle(elem).getPropertyValue('display') === 'none';
  756. const elHasDisplayInline = elem => getComputedStyle(elem).getPropertyValue('display') === 'inline';
  757. const elHasOverflowHidden = elem => {
  758. const style = getComputedStyle(elem);
  759. const cssOverflow = [
  760. style.getPropertyValue('overflow'),
  761. style.getPropertyValue('overflow-y'),
  762. style.getPropertyValue('overflow-x')
  763. ];
  764. return cssOverflow.includes('hidden');
  765. };
  766. const elHasPositionRelative = elem => getComputedStyle(elem).getPropertyValue('position') === 'relative';
  767. const elHasPositionAbsolute = elem => getComputedStyle(elem).getPropertyValue('position') === 'absolute';
  768. const ensureEl = (el, methodName) => {
  769. if (!isElement(el)) {
  770. throw new Error(`\`${methodName}\` failed because it requires a DOM element. The subject received was: \`${el}\``);
  771. }
  772. };
  773. const elHasNoEffectiveWidthOrHeight = (el) => {
  774. const style = getComputedStyle(el);
  775. const transform = style.getPropertyValue('transform');
  776. const width = elOffsetWidth(el);
  777. const height = elOffsetHeight(el);
  778. const overflowHidden = elHasOverflowHidden(el);
  779. return isZeroLengthAndTransformNone(width, height, transform) || isZeroLengthAndOverflowHidden(width, height, overflowHidden) || (el.getClientRects().length <= 0);
  780. };
  781. const elDescendentsHavePositionFixedOrAbsolute = function (parent, child) {
  782. const parents = getAllParents(child, parent);
  783. const arr = [...parents, child];
  784. return arr.some(elem => fixedOrAbsoluteRe.test(getComputedStyle(elem).getPropertyValue('position')))
  785. // const $els = $jquery.wrap(parents).add(child);
  786. // return _.some($els.get(), (el) => {
  787. // return fixedOrAbsoluteRe.test($jquery.wrap(el).css('position'));
  788. // });
  789. };
  790. const elIsHiddenByAncestors = (elem, checkOpacity, origEl = elem) => {
  791. const parent = getParent(elem);
  792. if (isUndefinedOrHTMLBodyDoc(parent)) return false;
  793. if (elHasOpacityZero(parent) && checkOpacity) return true;
  794. if (elHasOverflowHidden(parent) && elHasNoEffectiveWidthOrHeight(parent)) return !elDescendentsHavePositionFixedOrAbsolute(parent, origEl);
  795. return elIsHiddenByAncestors(parent, checkOpacity, origEl);
  796. };
  797. const elAtCenterPoint = elem => {
  798. const doc = $document.getDocumentFromElement(elem);
  799. const elProps = $coordinates.getElementPositioning(elem);
  800. const { topCenter, leftCenter } = elProps.fromElViewport;
  801. const el = $coordinates.getElementAtPointFromViewport(doc, leftCenter, topCenter);
  802. if (el) return el
  803. };
  804. const elIsNotElementFromPoint = elem => {
  805. const elAtPoint = elAtCenterPoint(elem);
  806. if (isDescendent(elem, elAtPoint)) return false;
  807. if ((getComputedStyle(elem).getPropertyValue('pointer-events') === 'none' || getComputedStyle(elem.parentElement).getPropertyValue('pointer-events') === 'none') &&
  808. (elAtPoint && isAncestor(elem, elAtPoint))) return false;
  809. return true;
  810. };
  811. const elHasClippableOverflow = elem => {
  812. const style = getComputedStyle(elem)
  813. return OVERFLOW_PROPS.includes(style.getPropertyValue('overflow')) || OVERFLOW_PROPS.includes(style.getPropertyValue('overflow-y')) || OVERFLOW_PROPS.includes(style.getPropertyValue('overflow-x'));
  814. };
  815. const canClipContent = (elem, ancestor) => {
  816. if (!elHasClippableOverflow(ancestor)) return false;
  817. const offsetParent = elem.offsetParent;
  818. if (!elHasPositionRelative(elem) && isAncestor(ancestor, offsetParent)) return false;
  819. if (elHasPositionAbsolute(offsetParent) && isChild(ancestor, offsetParent)) return false;
  820. return true;
  821. };
  822. const elIsOutOfBoundsOfAncestorsOverflow = (elem, ancestor = getParent(elem)) => {
  823. if (isUndefinedOrHTMLBodyDoc(ancestor)) return false;
  824. const elProps = $coordinates.getElementPositioning(elem);
  825. if (canClipContent(elem, ancestor)) {
  826. const ancestorProps = $coordinates.getElementPositioning(ancestor);
  827. if ((elProps.fromElWindow.left > (ancestorProps.width + ancestorProps.fromElWindow.left)) ||
  828. ((elProps.fromElWindow.left + elProps.width) < ancestorProps.fromElWindow.left) ||
  829. (elProps.fromElWindow.top > (ancestorProps.height + ancestorProps.fromElWindow.top)) ||
  830. ((elProps.fromElWindow.top + elProps.height) < ancestorProps.fromElWindow.top)) return true;
  831. }
  832. return elIsOutOfBoundsOfAncestorsOverflow(elem, getParent(ancestor));
  833. };
  834. const isHiddenByAncestors = (elem, methodName = 'isHiddenByAncestors()', options = { checkOpacity: true }) => {
  835. ensureEl(elem, methodName);
  836. if (elIsHiddenByAncestors(elem, options.checkOpacity)) return true;
  837.  
  838. // removed because I am just trying to find out if the element is "visible" outside the viewport
  839. // if (elOrAncestorIsFixedOrSticky(elem)) return elIsNotElementFromPoint(elem);
  840. return elIsOutOfBoundsOfAncestorsOverflow(elem);
  841. };
  842. const fixedOrAbsoluteRe = /(fixed|absolute)/;
  843. const OVERFLOW_PROPS = ['hidden', 'scroll', 'auto'];
  844. const isVisible = elem => !isHidden(elem, 'isVisible()');
  845. const isHidden = (el, methodName = 'isHidden()', options = { checkOpacity: true }) => {
  846. if (isStrictlyHidden(el, methodName, options, isHidden)) return true;
  847. return isHiddenByAncestors(el, methodName, options);
  848. };
  849. const isStrictlyHidden = (elem, methodName = 'isStrictlyHidden()', options = { checkOpacity: true }, recurse) => {
  850. ensureEl(elem, methodName);
  851.  
  852. if (isBody(elem) || isHTML(elem)) return false;
  853. if (isOption(elem) || isOptgroup(elem)) {
  854. if (elHasDisplayNone(elem)) return true;
  855. const select = getFirstParentWithTagName(elem, 'select');
  856. if (select) return recurse ? recurse(select, methodName, options) : isStrictlyHidden(select, methodName, options);
  857. }
  858. if (elHasNoEffectiveWidthOrHeight(elem)) {
  859. if (elHasDisplayInline(elem)) return !elHasVisibleChild(elem);
  860. return true;
  861. }
  862. if (elHasVisibilityHiddenOrCollapse(elem)) return true;
  863. // try {
  864. if ($transform.detectVisibility(elem) !== 'visible') return true;
  865. // } catch(err){}
  866. if (elHasOpacityZero(elem) && options.checkOpacity) return true;
  867. return false;
  868. };
  869. const isW3CRendered = elem => !(parentHasDisplayNone(elem) || getComputedStyle(elem).getPropertyValue('visibility') === 'hidden');
  870. const isW3CFocusable = elem => isFocusable(elem) && isW3CRendered(elem);
  871. const elHasVisibleChild = elem => Array.from(elem.children).some(child => isVisible(child));
  872. const parentHasNoOffsetWidthOrHeightAndOverflowHidden = function ($el) {
  873. if (isUndefinedOrHTMLBodyDoc($el)) return false;
  874. if (elHasOverflowHidden($el) && elHasNoEffectiveWidthOrHeight($el)) return $el;
  875. return parentHasNoOffsetWidthOrHeightAndOverflowHidden(getParent($el));
  876. };
  877. const parentHasDisplayNone = elem => {
  878. if ($document.isDocument(elem) || elem === null) return false;
  879. if (elHasDisplayNone(elem)) return elem;
  880. return parentHasDisplayNone(getParent(elem));
  881. };
  882. const parentHasVisibilityHidden = elem => {
  883. if ($document.isDocument(elem) || elem === null) return false;
  884. if (elHasVisibilityHidden(elem)) return elem;
  885. return parentHasVisibilityHidden(getParent(elem));
  886. };
  887. const parentHasVisibilityCollapse = elem => {
  888. if ($document.isDocument(elem) || elem === null) return false;
  889. if (elHasVisibilityCollapse(elem)) return elem;
  890. return parentHasVisibilityCollapse(getParent(elem));
  891. };
  892. const parentHasOpacityZero = elem => {
  893. if ($document.isDocument(elem) || elem === null) return false;
  894. if (elHasOpacityZero(elem)) return elem;
  895. return parentHasOpacityZero(getParent(elem));
  896. };
  897. const getReasonIsHidden = (elem, options = { checkOpacity: true }) => {
  898. const node = stringifyElement(elem, 'short');
  899. let width = elOffsetWidth(elem);
  900. let height = elOffsetHeight(elem);
  901. let $parent;
  902. let parentNode;
  903. if (elHasDisplayNone(elem)) return `This element \`${node}\` is not visible because it has CSS property: \`display: none\``;
  904. if ($parent = parentHasDisplayNone(getParent(elem))) {
  905. parentNode = stringifyElement($parent, 'short');
  906. return `This element \`${node}\` is not visible because its parent \`${parentNode}\` has CSS property: \`display: none\``;
  907. }
  908. if ($parent = parentHasVisibilityHidden(getParent(elem))) {
  909. parentNode = stringifyElement($parent, 'short');
  910. return `This element \`${node}\` is not visible because its parent \`${parentNode}\` has CSS property: \`visibility: hidden\``;
  911. }
  912. if ($parent = parentHasVisibilityCollapse(getParent(elem))) {
  913. parentNode = stringifyElement($parent, 'short');
  914. return `This element \`${node}\` is not visible because its parent \`${parentNode}\` has CSS property: \`visibility: collapse\``;
  915. }
  916. if (isDetached(elem)) return `This element \`${node}\` is not visible because it is detached from the DOM`;
  917. if (elHasVisibilityHidden(elem)) return `This element \`${node}\` is not visible because it has CSS property: \`visibility: hidden\``;
  918. if (elHasVisibilityCollapse(elem)) return `This element \`${node}\` is not visible because it has CSS property: \`visibility: collapse\``;
  919. if (elHasOpacityZero(elem) && options.checkOpacity) return `This element \`${node}\` is not visible because it has CSS property: \`opacity: 0\``;
  920.  
  921. if (($parent = parentHasOpacityZero(getParent(elem))) && options.checkOpacity) {
  922. parentNode = stringifyElement($parent, 'short');
  923. return `This element \`${node}\` is not visible because its parent \`${parentNode}\` has CSS property: \`opacity: 0\``;
  924. }
  925. if (elHasNoOffsetWidthOrHeight(elem)) return `This element \`${node}\` is not visible because it has an effective width and height of: \`${width} x ${height}\` pixels.`;
  926. const transformResult = $transform.detectVisibility(elem);
  927. if (transformResult === 'transformed') return `This element \`${node}\` is not visible because it is hidden by transform.`;
  928. if (transformResult === 'backface') return `This element \`${node}\` is not visible because it is rotated and its backface is hidden.`;
  929. if ($parent = parentHasNoOffsetWidthOrHeightAndOverflowHidden(getParent(elem))) {
  930. parentNode = stringifyElement($parent, 'short');
  931. width = elOffsetWidth($parent);
  932. height = elOffsetHeight($parent);
  933. 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.`;
  934. }
  935. if (elOrAncestorIsFixedOrSticky(elem)) {
  936. if (elIsNotElementFromPoint(elem)) {
  937. const covered = stringifyElement(elAtCenterPoint(elem));
  938. 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}\``;
  939. 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()\`?`;
  940. }
  941. }
  942. else {
  943. 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\``;
  944. }
  945. return `This element \`${node}\` is not visible.`;
  946. };
  947.  
  948. Object.assign(exports, {
  949. isVisible,
  950. isHidden,
  951. isStrictlyHidden,
  952. isHiddenByAncestors,
  953. getReasonIsHidden,
  954. isW3CFocusable,
  955. isW3CRendered
  956. })
  957. })