floating-ui.dom.browser.mjs 27 KB

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