floating-ui.dom.umd.js 35 KB

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