floating-ui.dom.browser.mjs 32 KB

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