floating-ui.dom.mjs 27 KB

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