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.

916 lines
31 KiB

  1. import { rectToClientRect, detectOverflow as detectOverflow$1, offset as offset$1, autoPlacement as autoPlacement$1, shift as shift$1, flip as flip$1, size as size$1, hide as hide$1, arrow as arrow$1, inline as inline$1, limitShift as limitShift$1, computePosition as computePosition$1 } from '@floating-ui/core';
  2. /**
  3. * Custom positioning reference element.
  4. * @see https://floating-ui.com/docs/virtual-elements
  5. */
  6. const min = Math.min;
  7. const max = Math.max;
  8. const round = Math.round;
  9. const floor = Math.floor;
  10. const createCoords = v => ({
  11. x: v,
  12. y: v
  13. });
  14. function hasWindow() {
  15. return typeof window !== 'undefined';
  16. }
  17. function getNodeName(node) {
  18. if (isNode(node)) {
  19. return (node.nodeName || '').toLowerCase();
  20. }
  21. // Mocked nodes in testing environments may not be instances of Node. By
  22. // returning `#document` an infinite loop won't occur.
  23. // https://github.com/floating-ui/floating-ui/issues/2317
  24. return '#document';
  25. }
  26. function getWindow(node) {
  27. var _node$ownerDocument;
  28. return (node == null || (_node$ownerDocument = node.ownerDocument) == null ? void 0 : _node$ownerDocument.defaultView) || window;
  29. }
  30. function getDocumentElement(node) {
  31. var _ref;
  32. return (_ref = (isNode(node) ? node.ownerDocument : node.document) || window.document) == null ? void 0 : _ref.documentElement;
  33. }
  34. function isNode(value) {
  35. if (!hasWindow()) {
  36. return false;
  37. }
  38. return value instanceof Node || value instanceof getWindow(value).Node;
  39. }
  40. function isElement(value) {
  41. if (!hasWindow()) {
  42. return false;
  43. }
  44. return value instanceof Element || value instanceof getWindow(value).Element;
  45. }
  46. function isHTMLElement(value) {
  47. if (!hasWindow()) {
  48. return false;
  49. }
  50. return value instanceof HTMLElement || value instanceof getWindow(value).HTMLElement;
  51. }
  52. function isShadowRoot(value) {
  53. if (!hasWindow() || typeof ShadowRoot === 'undefined') {
  54. return false;
  55. }
  56. return value instanceof ShadowRoot || value instanceof getWindow(value).ShadowRoot;
  57. }
  58. function isOverflowElement(element) {
  59. const {
  60. overflow,
  61. overflowX,
  62. overflowY,
  63. display
  64. } = getComputedStyle(element);
  65. return /auto|scroll|overlay|hidden|clip/.test(overflow + overflowY + overflowX) && !['inline', 'contents'].includes(display);
  66. }
  67. function isTableElement(element) {
  68. return ['table', 'td', 'th'].includes(getNodeName(element));
  69. }
  70. function isTopLayer(element) {
  71. return [':popover-open', ':modal'].some(selector => {
  72. try {
  73. return element.matches(selector);
  74. } catch (e) {
  75. return false;
  76. }
  77. });
  78. }
  79. function isContainingBlock(elementOrCss) {
  80. const webkit = isWebKit();
  81. const css = isElement(elementOrCss) ? getComputedStyle(elementOrCss) : elementOrCss;
  82. // https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#identifying_the_containing_block
  83. // https://drafts.csswg.org/css-transforms-2/#individual-transforms
  84. return ['transform', 'translate', 'scale', 'rotate', 'perspective'].some(value => css[value] ? css[value] !== 'none' : false) || (css.containerType ? css.containerType !== 'normal' : false) || !webkit && (css.backdropFilter ? css.backdropFilter !== 'none' : false) || !webkit && (css.filter ? css.filter !== 'none' : false) || ['transform', 'translate', 'scale', 'rotate', 'perspective', 'filter'].some(value => (css.willChange || '').includes(value)) || ['paint', 'layout', 'strict', 'content'].some(value => (css.contain || '').includes(value));
  85. }
  86. function getContainingBlock(element) {
  87. let currentNode = getParentNode(element);
  88. while (isHTMLElement(currentNode) && !isLastTraversableNode(currentNode)) {
  89. if (isContainingBlock(currentNode)) {
  90. return currentNode;
  91. } else if (isTopLayer(currentNode)) {
  92. return null;
  93. }
  94. currentNode = getParentNode(currentNode);
  95. }
  96. return null;
  97. }
  98. function isWebKit() {
  99. if (typeof CSS === 'undefined' || !CSS.supports) return false;
  100. return CSS.supports('-webkit-backdrop-filter', 'none');
  101. }
  102. function isLastTraversableNode(node) {
  103. return ['html', 'body', '#document'].includes(getNodeName(node));
  104. }
  105. function getComputedStyle(element) {
  106. return getWindow(element).getComputedStyle(element);
  107. }
  108. function getNodeScroll(element) {
  109. if (isElement(element)) {
  110. return {
  111. scrollLeft: element.scrollLeft,
  112. scrollTop: element.scrollTop
  113. };
  114. }
  115. return {
  116. scrollLeft: element.scrollX,
  117. scrollTop: element.scrollY
  118. };
  119. }
  120. function getParentNode(node) {
  121. if (getNodeName(node) === 'html') {
  122. return node;
  123. }
  124. const result =
  125. // Step into the shadow DOM of the parent of a slotted node.
  126. node.assignedSlot ||
  127. // DOM Element detected.
  128. node.parentNode ||
  129. // ShadowRoot detected.
  130. isShadowRoot(node) && node.host ||
  131. // Fallback.
  132. getDocumentElement(node);
  133. return isShadowRoot(result) ? result.host : result;
  134. }
  135. function getNearestOverflowAncestor(node) {
  136. const parentNode = getParentNode(node);
  137. if (isLastTraversableNode(parentNode)) {
  138. return node.ownerDocument ? node.ownerDocument.body : node.body;
  139. }
  140. if (isHTMLElement(parentNode) && isOverflowElement(parentNode)) {
  141. return parentNode;
  142. }
  143. return getNearestOverflowAncestor(parentNode);
  144. }
  145. function getOverflowAncestors(node, list, traverseIframes) {
  146. var _node$ownerDocument2;
  147. if (list === void 0) {
  148. list = [];
  149. }
  150. if (traverseIframes === void 0) {
  151. traverseIframes = true;
  152. }
  153. const scrollableAncestor = getNearestOverflowAncestor(node);
  154. const isBody = scrollableAncestor === ((_node$ownerDocument2 = node.ownerDocument) == null ? void 0 : _node$ownerDocument2.body);
  155. const win = getWindow(scrollableAncestor);
  156. if (isBody) {
  157. const frameElement = getFrameElement(win);
  158. return list.concat(win, win.visualViewport || [], isOverflowElement(scrollableAncestor) ? scrollableAncestor : [], frameElement && traverseIframes ? getOverflowAncestors(frameElement) : []);
  159. }
  160. return list.concat(scrollableAncestor, getOverflowAncestors(scrollableAncestor, [], traverseIframes));
  161. }
  162. function getFrameElement(win) {
  163. return win.parent && Object.getPrototypeOf(win.parent) ? win.frameElement : null;
  164. }
  165. function getCssDimensions(element) {
  166. const css = getComputedStyle(element);
  167. // In testing environments, the `width` and `height` properties are empty
  168. // strings for SVG elements, returning NaN. Fallback to `0` in this case.
  169. let width = parseFloat(css.width) || 0;
  170. let height = parseFloat(css.height) || 0;
  171. const hasOffset = isHTMLElement(element);
  172. const offsetWidth = hasOffset ? element.offsetWidth : width;
  173. const offsetHeight = hasOffset ? element.offsetHeight : height;
  174. const shouldFallback = round(width) !== offsetWidth || round(height) !== offsetHeight;
  175. if (shouldFallback) {
  176. width = offsetWidth;
  177. height = offsetHeight;
  178. }
  179. return {
  180. width,
  181. height,
  182. $: shouldFallback
  183. };
  184. }
  185. function unwrapElement(element) {
  186. return !isElement(element) ? element.contextElement : element;
  187. }
  188. function getScale(element) {
  189. const domElement = unwrapElement(element);
  190. if (!isHTMLElement(domElement)) {
  191. return createCoords(1);
  192. }
  193. const rect = domElement.getBoundingClientRect();
  194. const {
  195. width,
  196. height,
  197. $
  198. } = getCssDimensions(domElement);
  199. let x = ($ ? round(rect.width) : rect.width) / width;
  200. let y = ($ ? round(rect.height) : rect.height) / height;
  201. // 0, NaN, or Infinity should always fallback to 1.
  202. if (!x || !Number.isFinite(x)) {
  203. x = 1;
  204. }
  205. if (!y || !Number.isFinite(y)) {
  206. y = 1;
  207. }
  208. return {
  209. x,
  210. y
  211. };
  212. }
  213. const noOffsets = /*#__PURE__*/createCoords(0);
  214. function getVisualOffsets(element) {
  215. const win = getWindow(element);
  216. if (!isWebKit() || !win.visualViewport) {
  217. return noOffsets;
  218. }
  219. return {
  220. x: win.visualViewport.offsetLeft,
  221. y: win.visualViewport.offsetTop
  222. };
  223. }
  224. function shouldAddVisualOffsets(element, isFixed, floatingOffsetParent) {
  225. if (isFixed === void 0) {
  226. isFixed = false;
  227. }
  228. if (!floatingOffsetParent || isFixed && floatingOffsetParent !== getWindow(element)) {
  229. return false;
  230. }
  231. return isFixed;
  232. }
  233. function getBoundingClientRect(element, includeScale, isFixedStrategy, offsetParent) {
  234. if (includeScale === void 0) {
  235. includeScale = false;
  236. }
  237. if (isFixedStrategy === void 0) {
  238. isFixedStrategy = false;
  239. }
  240. const clientRect = element.getBoundingClientRect();
  241. const domElement = unwrapElement(element);
  242. let scale = createCoords(1);
  243. if (includeScale) {
  244. if (offsetParent) {
  245. if (isElement(offsetParent)) {
  246. scale = getScale(offsetParent);
  247. }
  248. } else {
  249. scale = getScale(element);
  250. }
  251. }
  252. const visualOffsets = shouldAddVisualOffsets(domElement, isFixedStrategy, offsetParent) ? getVisualOffsets(domElement) : createCoords(0);
  253. let x = (clientRect.left + visualOffsets.x) / scale.x;
  254. let y = (clientRect.top + visualOffsets.y) / scale.y;
  255. let width = clientRect.width / scale.x;
  256. let height = clientRect.height / scale.y;
  257. if (domElement) {
  258. const win = getWindow(domElement);
  259. const offsetWin = offsetParent && isElement(offsetParent) ? getWindow(offsetParent) : offsetParent;
  260. let currentWin = win;
  261. let currentIFrame = getFrameElement(currentWin);
  262. while (currentIFrame && offsetParent && offsetWin !== currentWin) {
  263. const iframeScale = getScale(currentIFrame);
  264. const iframeRect = currentIFrame.getBoundingClientRect();
  265. const css = getComputedStyle(currentIFrame);
  266. const left = iframeRect.left + (currentIFrame.clientLeft + parseFloat(css.paddingLeft)) * iframeScale.x;
  267. const top = iframeRect.top + (currentIFrame.clientTop + parseFloat(css.paddingTop)) * iframeScale.y;
  268. x *= iframeScale.x;
  269. y *= iframeScale.y;
  270. width *= iframeScale.x;
  271. height *= iframeScale.y;
  272. x += left;
  273. y += top;
  274. currentWin = getWindow(currentIFrame);
  275. currentIFrame = getFrameElement(currentWin);
  276. }
  277. }
  278. return rectToClientRect({
  279. width,
  280. height,
  281. x,
  282. y
  283. });
  284. }
  285. // If <html> has a CSS width greater than the viewport, then this will be
  286. // incorrect for RTL.
  287. function getWindowScrollBarX(element, rect) {
  288. const leftScroll = getNodeScroll(element).scrollLeft;
  289. if (!rect) {
  290. return getBoundingClientRect(getDocumentElement(element)).left + leftScroll;
  291. }
  292. return rect.left + leftScroll;
  293. }
  294. function getHTMLOffset(documentElement, scroll, ignoreScrollbarX) {
  295. if (ignoreScrollbarX === void 0) {
  296. ignoreScrollbarX = false;
  297. }
  298. const htmlRect = documentElement.getBoundingClientRect();
  299. const x = htmlRect.left + scroll.scrollLeft - (ignoreScrollbarX ? 0 :
  300. // RTL <body> scrollbar.
  301. getWindowScrollBarX(documentElement, htmlRect));
  302. const y = htmlRect.top + scroll.scrollTop;
  303. return {
  304. x,
  305. y
  306. };
  307. }
  308. function convertOffsetParentRelativeRectToViewportRelativeRect(_ref) {
  309. let {
  310. elements,
  311. rect,
  312. offsetParent,
  313. strategy
  314. } = _ref;
  315. const isFixed = strategy === 'fixed';
  316. const documentElement = getDocumentElement(offsetParent);
  317. const topLayer = elements ? isTopLayer(elements.floating) : false;
  318. if (offsetParent === documentElement || topLayer && isFixed) {
  319. return rect;
  320. }
  321. let scroll = {
  322. scrollLeft: 0,
  323. scrollTop: 0
  324. };
  325. let scale = createCoords(1);
  326. const offsets = createCoords(0);
  327. const isOffsetParentAnElement = isHTMLElement(offsetParent);
  328. if (isOffsetParentAnElement || !isOffsetParentAnElement && !isFixed) {
  329. if (getNodeName(offsetParent) !== 'body' || isOverflowElement(documentElement)) {
  330. scroll = getNodeScroll(offsetParent);
  331. }
  332. if (isHTMLElement(offsetParent)) {
  333. const offsetRect = getBoundingClientRect(offsetParent);
  334. scale = getScale(offsetParent);
  335. offsets.x = offsetRect.x + offsetParent.clientLeft;
  336. offsets.y = offsetRect.y + offsetParent.clientTop;
  337. }
  338. }
  339. const htmlOffset = documentElement && !isOffsetParentAnElement && !isFixed ? getHTMLOffset(documentElement, scroll, true) : createCoords(0);
  340. return {
  341. width: rect.width * scale.x,
  342. height: rect.height * scale.y,
  343. x: rect.x * scale.x - scroll.scrollLeft * scale.x + offsets.x + htmlOffset.x,
  344. y: rect.y * scale.y - scroll.scrollTop * scale.y + offsets.y + htmlOffset.y
  345. };
  346. }
  347. function getClientRects(element) {
  348. return Array.from(element.getClientRects());
  349. }
  350. // Gets the entire size of the scrollable document area, even extending outside
  351. // of the `<html>` and `<body>` rect bounds if horizontally scrollable.
  352. function getDocumentRect(element) {
  353. const html = getDocumentElement(element);
  354. const scroll = getNodeScroll(element);
  355. const body = element.ownerDocument.body;
  356. const width = max(html.scrollWidth, html.clientWidth, body.scrollWidth, body.clientWidth);
  357. const height = max(html.scrollHeight, html.clientHeight, body.scrollHeight, body.clientHeight);
  358. let x = -scroll.scrollLeft + getWindowScrollBarX(element);
  359. const y = -scroll.scrollTop;
  360. if (getComputedStyle(body).direction === 'rtl') {
  361. x += max(html.clientWidth, body.clientWidth) - width;
  362. }
  363. return {
  364. width,
  365. height,
  366. x,
  367. y
  368. };
  369. }
  370. function getViewportRect(element, strategy) {
  371. const win = getWindow(element);
  372. const html = getDocumentElement(element);
  373. const visualViewport = win.visualViewport;
  374. let width = html.clientWidth;
  375. let height = html.clientHeight;
  376. let x = 0;
  377. let y = 0;
  378. if (visualViewport) {
  379. width = visualViewport.width;
  380. height = visualViewport.height;
  381. const visualViewportBased = isWebKit();
  382. if (!visualViewportBased || visualViewportBased && strategy === 'fixed') {
  383. x = visualViewport.offsetLeft;
  384. y = visualViewport.offsetTop;
  385. }
  386. }
  387. return {
  388. width,
  389. height,
  390. x,
  391. y
  392. };
  393. }
  394. // Returns the inner client rect, subtracting scrollbars if present.
  395. function getInnerBoundingClientRect(element, strategy) {
  396. const clientRect = getBoundingClientRect(element, true, strategy === 'fixed');
  397. const top = clientRect.top + element.clientTop;
  398. const left = clientRect.left + element.clientLeft;
  399. const scale = isHTMLElement(element) ? getScale(element) : createCoords(1);
  400. const width = element.clientWidth * scale.x;
  401. const height = element.clientHeight * scale.y;
  402. const x = left * scale.x;
  403. const y = top * scale.y;
  404. return {
  405. width,
  406. height,
  407. x,
  408. y
  409. };
  410. }
  411. function getClientRectFromClippingAncestor(element, clippingAncestor, strategy) {
  412. let rect;
  413. if (clippingAncestor === 'viewport') {
  414. rect = getViewportRect(element, strategy);
  415. } else if (clippingAncestor === 'document') {
  416. rect = getDocumentRect(getDocumentElement(element));
  417. } else if (isElement(clippingAncestor)) {
  418. rect = getInnerBoundingClientRect(clippingAncestor, strategy);
  419. } else {
  420. const visualOffsets = getVisualOffsets(element);
  421. rect = {
  422. x: clippingAncestor.x - visualOffsets.x,
  423. y: clippingAncestor.y - visualOffsets.y,
  424. width: clippingAncestor.width,
  425. height: clippingAncestor.height
  426. };
  427. }
  428. return rectToClientRect(rect);
  429. }
  430. function hasFixedPositionAncestor(element, stopNode) {
  431. const parentNode = getParentNode(element);
  432. if (parentNode === stopNode || !isElement(parentNode) || isLastTraversableNode(parentNode)) {
  433. return false;
  434. }
  435. return getComputedStyle(parentNode).position === 'fixed' || hasFixedPositionAncestor(parentNode, stopNode);
  436. }
  437. // A "clipping ancestor" is an `overflow` element with the characteristic of
  438. // clipping (or hiding) child elements. This returns all clipping ancestors
  439. // of the given element up the tree.
  440. function getClippingElementAncestors(element, cache) {
  441. const cachedResult = cache.get(element);
  442. if (cachedResult) {
  443. return cachedResult;
  444. }
  445. let result = getOverflowAncestors(element, [], false).filter(el => isElement(el) && getNodeName(el) !== 'body');
  446. let currentContainingBlockComputedStyle = null;
  447. const elementIsFixed = getComputedStyle(element).position === 'fixed';
  448. let currentNode = elementIsFixed ? getParentNode(element) : element;
  449. // https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#identifying_the_containing_block
  450. while (isElement(currentNode) && !isLastTraversableNode(currentNode)) {
  451. const computedStyle = getComputedStyle(currentNode);
  452. const currentNodeIsContaining = isContainingBlock(currentNode);
  453. if (!currentNodeIsContaining && computedStyle.position === 'fixed') {
  454. currentContainingBlockComputedStyle = null;
  455. }
  456. const shouldDropCurrentNode = elementIsFixed ? !currentNodeIsContaining && !currentContainingBlockComputedStyle : !currentNodeIsContaining && computedStyle.position === 'static' && !!currentContainingBlockComputedStyle && ['absolute', 'fixed'].includes(currentContainingBlockComputedStyle.position) || isOverflowElement(currentNode) && !currentNodeIsContaining && hasFixedPositionAncestor(element, currentNode);
  457. if (shouldDropCurrentNode) {
  458. // Drop non-containing blocks.
  459. result = result.filter(ancestor => ancestor !== currentNode);
  460. } else {
  461. // Record last containing block for next iteration.
  462. currentContainingBlockComputedStyle = computedStyle;
  463. }
  464. currentNode = getParentNode(currentNode);
  465. }
  466. cache.set(element, result);
  467. return result;
  468. }
  469. // Gets the maximum area that the element is visible in due to any number of
  470. // clipping ancestors.
  471. function getClippingRect(_ref) {
  472. let {
  473. element,
  474. boundary,
  475. rootBoundary,
  476. strategy
  477. } = _ref;
  478. const elementClippingAncestors = boundary === 'clippingAncestors' ? isTopLayer(element) ? [] : getClippingElementAncestors(element, this._c) : [].concat(boundary);
  479. const clippingAncestors = [...elementClippingAncestors, rootBoundary];
  480. const firstClippingAncestor = clippingAncestors[0];
  481. const clippingRect = clippingAncestors.reduce((accRect, clippingAncestor) => {
  482. const rect = getClientRectFromClippingAncestor(element, clippingAncestor, strategy);
  483. accRect.top = max(rect.top, accRect.top);
  484. accRect.right = min(rect.right, accRect.right);
  485. accRect.bottom = min(rect.bottom, accRect.bottom);
  486. accRect.left = max(rect.left, accRect.left);
  487. return accRect;
  488. }, getClientRectFromClippingAncestor(element, firstClippingAncestor, strategy));
  489. return {
  490. width: clippingRect.right - clippingRect.left,
  491. height: clippingRect.bottom - clippingRect.top,
  492. x: clippingRect.left,
  493. y: clippingRect.top
  494. };
  495. }
  496. function getDimensions(element) {
  497. const {
  498. width,
  499. height
  500. } = getCssDimensions(element);
  501. return {
  502. width,
  503. height
  504. };
  505. }
  506. function getRectRelativeToOffsetParent(element, offsetParent, strategy) {
  507. const isOffsetParentAnElement = isHTMLElement(offsetParent);
  508. const documentElement = getDocumentElement(offsetParent);
  509. const isFixed = strategy === 'fixed';
  510. const rect = getBoundingClientRect(element, true, isFixed, offsetParent);
  511. let scroll = {
  512. scrollLeft: 0,
  513. scrollTop: 0
  514. };
  515. const offsets = createCoords(0);
  516. if (isOffsetParentAnElement || !isOffsetParentAnElement && !isFixed) {
  517. if (getNodeName(offsetParent) !== 'body' || isOverflowElement(documentElement)) {
  518. scroll = getNodeScroll(offsetParent);
  519. }
  520. if (isOffsetParentAnElement) {
  521. const offsetRect = getBoundingClientRect(offsetParent, true, isFixed, offsetParent);
  522. offsets.x = offsetRect.x + offsetParent.clientLeft;
  523. offsets.y = offsetRect.y + offsetParent.clientTop;
  524. } else if (documentElement) {
  525. // If the <body> scrollbar appears on the left (e.g. RTL systems). Use
  526. // Firefox with layout.scrollbar.side = 3 in about:config to test this.
  527. offsets.x = getWindowScrollBarX(documentElement);
  528. }
  529. }
  530. const htmlOffset = documentElement && !isOffsetParentAnElement && !isFixed ? getHTMLOffset(documentElement, scroll) : createCoords(0);
  531. const x = rect.left + scroll.scrollLeft - offsets.x - htmlOffset.x;
  532. const y = rect.top + scroll.scrollTop - offsets.y - htmlOffset.y;
  533. return {
  534. x,
  535. y,
  536. width: rect.width,
  537. height: rect.height
  538. };
  539. }
  540. function isStaticPositioned(element) {
  541. return getComputedStyle(element).position === 'static';
  542. }
  543. function getTrueOffsetParent(element, polyfill) {
  544. if (!isHTMLElement(element) || getComputedStyle(element).position === 'fixed') {
  545. return null;
  546. }
  547. if (polyfill) {
  548. return polyfill(element);
  549. }
  550. let rawOffsetParent = element.offsetParent;
  551. // Firefox returns the <html> element as the offsetParent if it's non-static,
  552. // while Chrome and Safari return the <body> element. The <body> element must
  553. // be used to perform the correct calculations even if the <html> element is
  554. // non-static.
  555. if (getDocumentElement(element) === rawOffsetParent) {
  556. rawOffsetParent = rawOffsetParent.ownerDocument.body;
  557. }
  558. return rawOffsetParent;
  559. }
  560. // Gets the closest ancestor positioned element. Handles some edge cases,
  561. // such as table ancestors and cross browser bugs.
  562. function getOffsetParent(element, polyfill) {
  563. const win = getWindow(element);
  564. if (isTopLayer(element)) {
  565. return win;
  566. }
  567. if (!isHTMLElement(element)) {
  568. let svgOffsetParent = getParentNode(element);
  569. while (svgOffsetParent && !isLastTraversableNode(svgOffsetParent)) {
  570. if (isElement(svgOffsetParent) && !isStaticPositioned(svgOffsetParent)) {
  571. return svgOffsetParent;
  572. }
  573. svgOffsetParent = getParentNode(svgOffsetParent);
  574. }
  575. return win;
  576. }
  577. let offsetParent = getTrueOffsetParent(element, polyfill);
  578. while (offsetParent && isTableElement(offsetParent) && isStaticPositioned(offsetParent)) {
  579. offsetParent = getTrueOffsetParent(offsetParent, polyfill);
  580. }
  581. if (offsetParent && isLastTraversableNode(offsetParent) && isStaticPositioned(offsetParent) && !isContainingBlock(offsetParent)) {
  582. return win;
  583. }
  584. return offsetParent || getContainingBlock(element) || win;
  585. }
  586. const getElementRects = async function (data) {
  587. const getOffsetParentFn = this.getOffsetParent || getOffsetParent;
  588. const getDimensionsFn = this.getDimensions;
  589. const floatingDimensions = await getDimensionsFn(data.floating);
  590. return {
  591. reference: getRectRelativeToOffsetParent(data.reference, await getOffsetParentFn(data.floating), data.strategy),
  592. floating: {
  593. x: 0,
  594. y: 0,
  595. width: floatingDimensions.width,
  596. height: floatingDimensions.height
  597. }
  598. };
  599. };
  600. function isRTL(element) {
  601. return getComputedStyle(element).direction === 'rtl';
  602. }
  603. const platform = {
  604. convertOffsetParentRelativeRectToViewportRelativeRect,
  605. getDocumentElement,
  606. getClippingRect,
  607. getOffsetParent,
  608. getElementRects,
  609. getClientRects,
  610. getDimensions,
  611. getScale,
  612. isElement,
  613. isRTL
  614. };
  615. function rectsAreEqual(a, b) {
  616. return a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height;
  617. }
  618. // https://samthor.au/2021/observing-dom/
  619. function observeMove(element, onMove) {
  620. let io = null;
  621. let timeoutId;
  622. const root = getDocumentElement(element);
  623. function cleanup() {
  624. var _io;
  625. clearTimeout(timeoutId);
  626. (_io = io) == null || _io.disconnect();
  627. io = null;
  628. }
  629. function refresh(skip, threshold) {
  630. if (skip === void 0) {
  631. skip = false;
  632. }
  633. if (threshold === void 0) {
  634. threshold = 1;
  635. }
  636. cleanup();
  637. const elementRectForRootMargin = element.getBoundingClientRect();
  638. const {
  639. left,
  640. top,
  641. width,
  642. height
  643. } = elementRectForRootMargin;
  644. if (!skip) {
  645. onMove();
  646. }
  647. if (!width || !height) {
  648. return;
  649. }
  650. const insetTop = floor(top);
  651. const insetRight = floor(root.clientWidth - (left + width));
  652. const insetBottom = floor(root.clientHeight - (top + height));
  653. const insetLeft = floor(left);
  654. const rootMargin = -insetTop + "px " + -insetRight + "px " + -insetBottom + "px " + -insetLeft + "px";
  655. const options = {
  656. rootMargin,
  657. threshold: max(0, min(1, threshold)) || 1
  658. };
  659. let isFirstUpdate = true;
  660. function handleObserve(entries) {
  661. const ratio = entries[0].intersectionRatio;
  662. if (ratio !== threshold) {
  663. if (!isFirstUpdate) {
  664. return refresh();
  665. }
  666. if (!ratio) {
  667. // If the reference is clipped, the ratio is 0. Throttle the refresh
  668. // to prevent an infinite loop of updates.
  669. timeoutId = setTimeout(() => {
  670. refresh(false, 1e-7);
  671. }, 1000);
  672. } else {
  673. refresh(false, ratio);
  674. }
  675. }
  676. if (ratio === 1 && !rectsAreEqual(elementRectForRootMargin, element.getBoundingClientRect())) {
  677. // It's possible that even though the ratio is reported as 1, the
  678. // element is not actually fully within the IntersectionObserver's root
  679. // area anymore. This can happen under performance constraints. This may
  680. // be a bug in the browser's IntersectionObserver implementation. To
  681. // work around this, we compare the element's bounding rect now with
  682. // what it was at the time we created the IntersectionObserver. If they
  683. // are not equal then the element moved, so we refresh.
  684. refresh();
  685. }
  686. isFirstUpdate = false;
  687. }
  688. // Older browsers don't support a `document` as the root and will throw an
  689. // error.
  690. try {
  691. io = new IntersectionObserver(handleObserve, {
  692. ...options,
  693. // Handle <iframe>s
  694. root: root.ownerDocument
  695. });
  696. } catch (e) {
  697. io = new IntersectionObserver(handleObserve, options);
  698. }
  699. io.observe(element);
  700. }
  701. refresh(true);
  702. return cleanup;
  703. }
  704. /**
  705. * Automatically updates the position of the floating element when necessary.
  706. * Should only be called when the floating element is mounted on the DOM or
  707. * visible on the screen.
  708. * @returns cleanup function that should be invoked when the floating element is
  709. * removed from the DOM or hidden from the screen.
  710. * @see https://floating-ui.com/docs/autoUpdate
  711. */
  712. function autoUpdate(reference, floating, update, options) {
  713. if (options === void 0) {
  714. options = {};
  715. }
  716. const {
  717. ancestorScroll = true,
  718. ancestorResize = true,
  719. elementResize = typeof ResizeObserver === 'function',
  720. layoutShift = typeof IntersectionObserver === 'function',
  721. animationFrame = false
  722. } = options;
  723. const referenceEl = unwrapElement(reference);
  724. const ancestors = ancestorScroll || ancestorResize ? [...(referenceEl ? getOverflowAncestors(referenceEl) : []), ...getOverflowAncestors(floating)] : [];
  725. ancestors.forEach(ancestor => {
  726. ancestorScroll && ancestor.addEventListener('scroll', update, {
  727. passive: true
  728. });
  729. ancestorResize && ancestor.addEventListener('resize', update);
  730. });
  731. const cleanupIo = referenceEl && layoutShift ? observeMove(referenceEl, update) : null;
  732. let reobserveFrame = -1;
  733. let resizeObserver = null;
  734. if (elementResize) {
  735. resizeObserver = new ResizeObserver(_ref => {
  736. let [firstEntry] = _ref;
  737. if (firstEntry && firstEntry.target === referenceEl && resizeObserver) {
  738. // Prevent update loops when using the `size` middleware.
  739. // https://github.com/floating-ui/floating-ui/issues/1740
  740. resizeObserver.unobserve(floating);
  741. cancelAnimationFrame(reobserveFrame);
  742. reobserveFrame = requestAnimationFrame(() => {
  743. var _resizeObserver;
  744. (_resizeObserver = resizeObserver) == null || _resizeObserver.observe(floating);
  745. });
  746. }
  747. update();
  748. });
  749. if (referenceEl && !animationFrame) {
  750. resizeObserver.observe(referenceEl);
  751. }
  752. resizeObserver.observe(floating);
  753. }
  754. let frameId;
  755. let prevRefRect = animationFrame ? getBoundingClientRect(reference) : null;
  756. if (animationFrame) {
  757. frameLoop();
  758. }
  759. function frameLoop() {
  760. const nextRefRect = getBoundingClientRect(reference);
  761. if (prevRefRect && !rectsAreEqual(prevRefRect, nextRefRect)) {
  762. update();
  763. }
  764. prevRefRect = nextRefRect;
  765. frameId = requestAnimationFrame(frameLoop);
  766. }
  767. update();
  768. return () => {
  769. var _resizeObserver2;
  770. ancestors.forEach(ancestor => {
  771. ancestorScroll && ancestor.removeEventListener('scroll', update);
  772. ancestorResize && ancestor.removeEventListener('resize', update);
  773. });
  774. cleanupIo == null || cleanupIo();
  775. (_resizeObserver2 = resizeObserver) == null || _resizeObserver2.disconnect();
  776. resizeObserver = null;
  777. if (animationFrame) {
  778. cancelAnimationFrame(frameId);
  779. }
  780. };
  781. }
  782. /**
  783. * Resolves with an object of overflow side offsets that determine how much the
  784. * element is overflowing a given clipping boundary on each side.
  785. * - positive = overflowing the boundary by that number of pixels
  786. * - negative = how many pixels left before it will overflow
  787. * - 0 = lies flush with the boundary
  788. * @see https://floating-ui.com/docs/detectOverflow
  789. */
  790. const detectOverflow = detectOverflow$1;
  791. /**
  792. * Modifies the placement by translating the floating element along the
  793. * specified axes.
  794. * A number (shorthand for `mainAxis` or distance), or an axes configuration
  795. * object may be passed.
  796. * @see https://floating-ui.com/docs/offset
  797. */
  798. const offset = offset$1;
  799. /**
  800. * Optimizes the visibility of the floating element by choosing the placement
  801. * that has the most space available automatically, without needing to specify a
  802. * preferred placement. Alternative to `flip`.
  803. * @see https://floating-ui.com/docs/autoPlacement
  804. */
  805. const autoPlacement = autoPlacement$1;
  806. /**
  807. * Optimizes the visibility of the floating element by shifting it in order to
  808. * keep it in view when it will overflow the clipping boundary.
  809. * @see https://floating-ui.com/docs/shift
  810. */
  811. const shift = shift$1;
  812. /**
  813. * Optimizes the visibility of the floating element by flipping the `placement`
  814. * in order to keep it in view when the preferred placement(s) will overflow the
  815. * clipping boundary. Alternative to `autoPlacement`.
  816. * @see https://floating-ui.com/docs/flip
  817. */
  818. const flip = flip$1;
  819. /**
  820. * Provides data that allows you to change the size of the floating element —
  821. * for instance, prevent it from overflowing the clipping boundary or match the
  822. * width of the reference element.
  823. * @see https://floating-ui.com/docs/size
  824. */
  825. const size = size$1;
  826. /**
  827. * Provides data to hide the floating element in applicable situations, such as
  828. * when it is not in the same clipping context as the reference element.
  829. * @see https://floating-ui.com/docs/hide
  830. */
  831. const hide = hide$1;
  832. /**
  833. * Provides data to position an inner element of the floating element so that it
  834. * appears centered to the reference element.
  835. * @see https://floating-ui.com/docs/arrow
  836. */
  837. const arrow = arrow$1;
  838. /**
  839. * Provides improved positioning for inline reference elements that can span
  840. * over multiple lines, such as hyperlinks or range selections.
  841. * @see https://floating-ui.com/docs/inline
  842. */
  843. const inline = inline$1;
  844. /**
  845. * Built-in `limiter` that will stop `shift()` at a certain point.
  846. */
  847. const limitShift = limitShift$1;
  848. /**
  849. * Computes the `x` and `y` coordinates that will place the floating element
  850. * next to a given reference element.
  851. */
  852. const computePosition = (reference, floating, options) => {
  853. // This caches the expensive `getClippingElementAncestors` function so that
  854. // multiple lifecycle resets re-use the same result. It only lives for a
  855. // single call. If other functions become expensive, we can add them as well.
  856. const cache = new Map();
  857. const mergedOptions = {
  858. platform,
  859. ...options
  860. };
  861. const platformWithCache = {
  862. ...mergedOptions.platform,
  863. _c: cache
  864. };
  865. return computePosition$1(reference, floating, {
  866. ...mergedOptions,
  867. platform: platformWithCache
  868. });
  869. };
  870. export { arrow, autoPlacement, autoUpdate, computePosition, detectOverflow, flip, getOverflowAncestors, hide, inline, limitShift, offset, platform, shift, size };