You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

93 lines
2.7 KiB

2 months ago
  1. // @flow
  2. import getWindow from './getWindow';
  3. import getNodeName from './getNodeName';
  4. import getComputedStyle from './getComputedStyle';
  5. import { isHTMLElement, isShadowRoot } from './instanceOf';
  6. import isTableElement from './isTableElement';
  7. import getParentNode from './getParentNode';
  8. import getUAString from '../utils/userAgent';
  9. function getTrueOffsetParent(element: Element): ?Element {
  10. if (
  11. !isHTMLElement(element) ||
  12. // https://github.com/popperjs/popper-core/issues/837
  13. getComputedStyle(element).position === 'fixed'
  14. ) {
  15. return null;
  16. }
  17. return element.offsetParent;
  18. }
  19. // `.offsetParent` reports `null` for fixed elements, while absolute elements
  20. // return the containing block
  21. function getContainingBlock(element: Element) {
  22. const isFirefox = /firefox/i.test(getUAString());
  23. const isIE = /Trident/i.test(getUAString());
  24. if (isIE && isHTMLElement(element)) {
  25. // In IE 9, 10 and 11 fixed elements containing block is always established by the viewport
  26. const elementCss = getComputedStyle(element);
  27. if (elementCss.position === 'fixed') {
  28. return null;
  29. }
  30. }
  31. let currentNode = getParentNode(element);
  32. if (isShadowRoot(currentNode)) {
  33. currentNode = currentNode.host;
  34. }
  35. while (
  36. isHTMLElement(currentNode) &&
  37. ['html', 'body'].indexOf(getNodeName(currentNode)) < 0
  38. ) {
  39. const css = getComputedStyle(currentNode);
  40. // This is non-exhaustive but covers the most common CSS properties that
  41. // create a containing block.
  42. // https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#identifying_the_containing_block
  43. if (
  44. css.transform !== 'none' ||
  45. css.perspective !== 'none' ||
  46. css.contain === 'paint' ||
  47. ['transform', 'perspective'].indexOf(css.willChange) !== -1 ||
  48. (isFirefox && css.willChange === 'filter') ||
  49. (isFirefox && css.filter && css.filter !== 'none')
  50. ) {
  51. return currentNode;
  52. } else {
  53. currentNode = currentNode.parentNode;
  54. }
  55. }
  56. return null;
  57. }
  58. // Gets the closest ancestor positioned element. Handles some edge cases,
  59. // such as table ancestors and cross browser bugs.
  60. export default function getOffsetParent(element: Element) {
  61. const window = getWindow(element);
  62. let offsetParent = getTrueOffsetParent(element);
  63. while (
  64. offsetParent &&
  65. isTableElement(offsetParent) &&
  66. getComputedStyle(offsetParent).position === 'static'
  67. ) {
  68. offsetParent = getTrueOffsetParent(offsetParent);
  69. }
  70. if (
  71. offsetParent &&
  72. (getNodeName(offsetParent) === 'html' ||
  73. (getNodeName(offsetParent) === 'body' &&
  74. getComputedStyle(offsetParent).position === 'static'))
  75. ) {
  76. return window;
  77. }
  78. return offsetParent || getContainingBlock(element) || window;
  79. }