floating-ui.dom.esm.js 27 KB

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