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.

935 lines
33 KiB

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