floating-ui.dom.umd.js 35 KB

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