floating-ui.dom.browser.mjs 32 KB

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