floating-ui.dom.browser.mjs 30 KB

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