floating-ui.dom.esm.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633
  1. import { rectToClientRect, computePosition as computePosition$1 } from '@floating-ui/core';
  2. export { arrow, autoPlacement, detectOverflow, flip, hide, inline, limitShift, offset, shift, size } from '@floating-ui/core';
  3. function isWindow(value) {
  4. return value && value.document && value.location && value.alert && value.setInterval;
  5. }
  6. function getWindow(node) {
  7. if (node == null) {
  8. return window;
  9. }
  10. if (!isWindow(node)) {
  11. const ownerDocument = node.ownerDocument;
  12. return ownerDocument ? ownerDocument.defaultView || window : window;
  13. }
  14. return node;
  15. }
  16. function getComputedStyle(element) {
  17. return getWindow(element).getComputedStyle(element);
  18. }
  19. function getNodeName(node) {
  20. return isWindow(node) ? '' : node ? (node.nodeName || '').toLowerCase() : '';
  21. }
  22. function getUAString() {
  23. const uaData = navigator.userAgentData;
  24. if (uaData != null && uaData.brands) {
  25. return uaData.brands.map(item => item.brand + "/" + item.version).join(' ');
  26. }
  27. return navigator.userAgent;
  28. }
  29. function isHTMLElement(value) {
  30. return value instanceof getWindow(value).HTMLElement;
  31. }
  32. function isElement(value) {
  33. return value instanceof getWindow(value).Element;
  34. }
  35. function isNode(value) {
  36. return value instanceof getWindow(value).Node;
  37. }
  38. function isShadowRoot(node) {
  39. // Browsers without `ShadowRoot` support
  40. if (typeof ShadowRoot === 'undefined') {
  41. return false;
  42. }
  43. const OwnElement = getWindow(node).ShadowRoot;
  44. return node instanceof OwnElement || node instanceof ShadowRoot;
  45. }
  46. function isOverflowElement(element) {
  47. // Firefox wants us to check `-x` and `-y` variations as well
  48. const {
  49. overflow,
  50. overflowX,
  51. overflowY,
  52. display
  53. } = getComputedStyle(element);
  54. return /auto|scroll|overlay|hidden/.test(overflow + overflowY + overflowX) && !['inline', 'contents'].includes(display);
  55. }
  56. function isTableElement(element) {
  57. return ['table', 'td', 'th'].includes(getNodeName(element));
  58. }
  59. function isContainingBlock(element) {
  60. // TODO: Try and use feature detection here instead
  61. const isFirefox = /firefox/i.test(getUAString());
  62. const css = getComputedStyle(element); // This is non-exhaustive but covers the most common CSS properties that
  63. // create a containing block.
  64. // https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#identifying_the_containing_block
  65. return css.transform !== 'none' || css.perspective !== 'none' || isFirefox && css.willChange === 'filter' || isFirefox && (css.filter ? css.filter !== 'none' : false) || ['transform', 'perspective'].some(value => css.willChange.includes(value)) || ['paint', 'layout', 'strict', 'content'].some( // TS 4.1 compat
  66. value => {
  67. const contain = css.contain;
  68. return contain != null ? contain.includes(value) : false;
  69. });
  70. }
  71. function isLayoutViewport() {
  72. // Not Safari
  73. return !/^((?!chrome|android).)*safari/i.test(getUAString()); // Feature detection for this fails in various ways
  74. // • Always-visible scrollbar or not
  75. // • Width of <html>, etc.
  76. // const vV = win.visualViewport;
  77. // return vV ? Math.abs(win.innerWidth / vV.scale - vV.width) < 0.5 : true;
  78. }
  79. function isLastTraversableNode(node) {
  80. return ['html', 'body', '#document'].includes(getNodeName(node));
  81. }
  82. const min = Math.min;
  83. const max = Math.max;
  84. const round = Math.round;
  85. function getBoundingClientRect(element, includeScale, isFixedStrategy) {
  86. var _win$visualViewport$o, _win$visualViewport, _win$visualViewport$o2, _win$visualViewport2;
  87. if (includeScale === void 0) {
  88. includeScale = false;
  89. }
  90. if (isFixedStrategy === void 0) {
  91. isFixedStrategy = false;
  92. }
  93. const clientRect = element.getBoundingClientRect();
  94. let scaleX = 1;
  95. let scaleY = 1;
  96. if (includeScale && isHTMLElement(element)) {
  97. scaleX = element.offsetWidth > 0 ? round(clientRect.width) / element.offsetWidth || 1 : 1;
  98. scaleY = element.offsetHeight > 0 ? round(clientRect.height) / element.offsetHeight || 1 : 1;
  99. }
  100. const win = isElement(element) ? getWindow(element) : window;
  101. const addVisualOffsets = !isLayoutViewport() && isFixedStrategy;
  102. const x = (clientRect.left + (addVisualOffsets ? (_win$visualViewport$o = (_win$visualViewport = win.visualViewport) == null ? void 0 : _win$visualViewport.offsetLeft) != null ? _win$visualViewport$o : 0 : 0)) / scaleX;
  103. const y = (clientRect.top + (addVisualOffsets ? (_win$visualViewport$o2 = (_win$visualViewport2 = win.visualViewport) == null ? void 0 : _win$visualViewport2.offsetTop) != null ? _win$visualViewport$o2 : 0 : 0)) / scaleY;
  104. const width = clientRect.width / scaleX;
  105. const height = clientRect.height / scaleY;
  106. return {
  107. width,
  108. height,
  109. top: y,
  110. right: x + width,
  111. bottom: y + height,
  112. left: x,
  113. x,
  114. y
  115. };
  116. }
  117. function getDocumentElement(node) {
  118. return ((isNode(node) ? node.ownerDocument : node.document) || window.document).documentElement;
  119. }
  120. function getNodeScroll(element) {
  121. if (isElement(element)) {
  122. return {
  123. scrollLeft: element.scrollLeft,
  124. scrollTop: element.scrollTop
  125. };
  126. }
  127. return {
  128. scrollLeft: element.pageXOffset,
  129. scrollTop: element.pageYOffset
  130. };
  131. }
  132. function getWindowScrollBarX(element) {
  133. // If <html> has a CSS width greater than the viewport, then this will be
  134. // incorrect for RTL.
  135. return getBoundingClientRect(getDocumentElement(element)).left + getNodeScroll(element).scrollLeft;
  136. }
  137. function isScaled(element) {
  138. const rect = getBoundingClientRect(element);
  139. return round(rect.width) !== element.offsetWidth || round(rect.height) !== element.offsetHeight;
  140. }
  141. function getRectRelativeToOffsetParent(element, offsetParent, strategy) {
  142. const isOffsetParentAnElement = isHTMLElement(offsetParent);
  143. const documentElement = getDocumentElement(offsetParent);
  144. const rect = getBoundingClientRect(element, // @ts-ignore - checked above (TS 4.1 compat)
  145. isOffsetParentAnElement && isScaled(offsetParent), strategy === 'fixed');
  146. let scroll = {
  147. scrollLeft: 0,
  148. scrollTop: 0
  149. };
  150. const offsets = {
  151. x: 0,
  152. y: 0
  153. };
  154. if (isOffsetParentAnElement || !isOffsetParentAnElement && strategy !== 'fixed') {
  155. if (getNodeName(offsetParent) !== 'body' || isOverflowElement(documentElement)) {
  156. scroll = getNodeScroll(offsetParent);
  157. }
  158. if (isHTMLElement(offsetParent)) {
  159. const offsetRect = getBoundingClientRect(offsetParent, true);
  160. offsets.x = offsetRect.x + offsetParent.clientLeft;
  161. offsets.y = offsetRect.y + offsetParent.clientTop;
  162. } else if (documentElement) {
  163. offsets.x = getWindowScrollBarX(documentElement);
  164. }
  165. }
  166. return {
  167. x: rect.left + scroll.scrollLeft - offsets.x,
  168. y: rect.top + scroll.scrollTop - offsets.y,
  169. width: rect.width,
  170. height: rect.height
  171. };
  172. }
  173. function getParentNode(node) {
  174. if (getNodeName(node) === 'html') {
  175. return node;
  176. }
  177. return (// this is a quicker (but less type safe) way to save quite some bytes from the bundle
  178. // @ts-ignore
  179. node.assignedSlot || // step into the shadow DOM of the parent of a slotted node
  180. node.parentNode || ( // DOM Element detected
  181. isShadowRoot(node) ? node.host : null) || // ShadowRoot detected
  182. getDocumentElement(node) // fallback
  183. );
  184. }
  185. function getTrueOffsetParent(element) {
  186. if (!isHTMLElement(element) || getComputedStyle(element).position === 'fixed') {
  187. return null;
  188. }
  189. return element.offsetParent;
  190. }
  191. function getContainingBlock(element) {
  192. let currentNode = getParentNode(element);
  193. if (isShadowRoot(currentNode)) {
  194. currentNode = currentNode.host;
  195. }
  196. while (isHTMLElement(currentNode) && !isLastTraversableNode(currentNode)) {
  197. if (isContainingBlock(currentNode)) {
  198. return currentNode;
  199. } else {
  200. const parent = currentNode.parentNode;
  201. currentNode = isShadowRoot(parent) ? parent.host : parent;
  202. }
  203. }
  204. return null;
  205. } // Gets the closest ancestor positioned element. Handles some edge cases,
  206. // such as table ancestors and cross browser bugs.
  207. function getOffsetParent(element) {
  208. const window = getWindow(element);
  209. let offsetParent = getTrueOffsetParent(element);
  210. while (offsetParent && isTableElement(offsetParent) && getComputedStyle(offsetParent).position === 'static') {
  211. offsetParent = getTrueOffsetParent(offsetParent);
  212. }
  213. if (offsetParent && (getNodeName(offsetParent) === 'html' || getNodeName(offsetParent) === 'body' && getComputedStyle(offsetParent).position === 'static' && !isContainingBlock(offsetParent))) {
  214. return window;
  215. }
  216. return offsetParent || getContainingBlock(element) || window;
  217. }
  218. function getDimensions(element) {
  219. if (isHTMLElement(element)) {
  220. return {
  221. width: element.offsetWidth,
  222. height: element.offsetHeight
  223. };
  224. }
  225. const rect = getBoundingClientRect(element);
  226. return {
  227. width: rect.width,
  228. height: rect.height
  229. };
  230. }
  231. function convertOffsetParentRelativeRectToViewportRelativeRect(_ref) {
  232. let {
  233. rect,
  234. offsetParent,
  235. strategy
  236. } = _ref;
  237. const isOffsetParentAnElement = isHTMLElement(offsetParent);
  238. const documentElement = getDocumentElement(offsetParent);
  239. if (offsetParent === documentElement) {
  240. return rect;
  241. }
  242. let scroll = {
  243. scrollLeft: 0,
  244. scrollTop: 0
  245. };
  246. const offsets = {
  247. x: 0,
  248. y: 0
  249. };
  250. if (isOffsetParentAnElement || !isOffsetParentAnElement && strategy !== 'fixed') {
  251. if (getNodeName(offsetParent) !== 'body' || isOverflowElement(documentElement)) {
  252. scroll = getNodeScroll(offsetParent);
  253. }
  254. if (isHTMLElement(offsetParent)) {
  255. const offsetRect = getBoundingClientRect(offsetParent, true);
  256. offsets.x = offsetRect.x + offsetParent.clientLeft;
  257. offsets.y = offsetRect.y + offsetParent.clientTop;
  258. } // This doesn't appear to be need to be negated.
  259. // else if (documentElement) {
  260. // offsets.x = getWindowScrollBarX(documentElement);
  261. // }
  262. }
  263. return { ...rect,
  264. x: rect.x - scroll.scrollLeft + offsets.x,
  265. y: rect.y - scroll.scrollTop + offsets.y
  266. };
  267. }
  268. function getViewportRect(element, strategy) {
  269. const win = getWindow(element);
  270. const html = getDocumentElement(element);
  271. const visualViewport = win.visualViewport;
  272. let width = html.clientWidth;
  273. let height = html.clientHeight;
  274. let x = 0;
  275. let y = 0;
  276. if (visualViewport) {
  277. width = visualViewport.width;
  278. height = visualViewport.height;
  279. const layoutViewport = isLayoutViewport();
  280. if (layoutViewport || !layoutViewport && strategy === 'fixed') {
  281. x = visualViewport.offsetLeft;
  282. y = visualViewport.offsetTop;
  283. }
  284. }
  285. return {
  286. width,
  287. height,
  288. x,
  289. y
  290. };
  291. }
  292. // of the `<html>` and `<body>` rect bounds if horizontally scrollable
  293. function getDocumentRect(element) {
  294. var _element$ownerDocumen;
  295. const html = getDocumentElement(element);
  296. const scroll = getNodeScroll(element);
  297. const body = (_element$ownerDocumen = element.ownerDocument) == null ? void 0 : _element$ownerDocumen.body;
  298. const width = max(html.scrollWidth, html.clientWidth, body ? body.scrollWidth : 0, body ? body.clientWidth : 0);
  299. const height = max(html.scrollHeight, html.clientHeight, body ? body.scrollHeight : 0, body ? body.clientHeight : 0);
  300. let x = -scroll.scrollLeft + getWindowScrollBarX(element);
  301. const y = -scroll.scrollTop;
  302. if (getComputedStyle(body || html).direction === 'rtl') {
  303. x += max(html.clientWidth, body ? body.clientWidth : 0) - width;
  304. }
  305. return {
  306. width,
  307. height,
  308. x,
  309. y
  310. };
  311. }
  312. function getNearestOverflowAncestor(node) {
  313. const parentNode = getParentNode(node);
  314. if (isLastTraversableNode(parentNode)) {
  315. // @ts-ignore assume body is always available
  316. return node.ownerDocument.body;
  317. }
  318. if (isHTMLElement(parentNode) && isOverflowElement(parentNode)) {
  319. return parentNode;
  320. }
  321. return getNearestOverflowAncestor(parentNode);
  322. }
  323. function getOverflowAncestors(node, list) {
  324. var _node$ownerDocument;
  325. if (list === void 0) {
  326. list = [];
  327. }
  328. const scrollableAncestor = getNearestOverflowAncestor(node);
  329. const isBody = scrollableAncestor === ((_node$ownerDocument = node.ownerDocument) == null ? void 0 : _node$ownerDocument.body);
  330. const win = getWindow(scrollableAncestor);
  331. const target = isBody ? [win].concat(win.visualViewport || [], isOverflowElement(scrollableAncestor) ? scrollableAncestor : []) : scrollableAncestor;
  332. const updatedList = list.concat(target);
  333. return isBody ? updatedList : // @ts-ignore: isBody tells us target will be an HTMLElement here
  334. updatedList.concat(getOverflowAncestors(target));
  335. }
  336. function contains(parent, child) {
  337. const rootNode = child.getRootNode == null ? void 0 : child.getRootNode(); // First, attempt with faster native method
  338. if (parent.contains(child)) {
  339. return true;
  340. } // then fallback to custom implementation with Shadow DOM support
  341. else if (rootNode && isShadowRoot(rootNode)) {
  342. let next = child;
  343. do {
  344. // use `===` replace node.isSameNode()
  345. if (next && parent === next) {
  346. return true;
  347. } // @ts-ignore: need a better way to handle this...
  348. next = next.parentNode || next.host;
  349. } while (next);
  350. }
  351. return false;
  352. }
  353. function getNearestParentCapableOfEscapingClipping(element, clippingAncestors) {
  354. let currentNode = element;
  355. while (currentNode && !isLastTraversableNode(currentNode) && // @ts-expect-error
  356. !clippingAncestors.includes(currentNode)) {
  357. if (isElement(currentNode) && ['absolute', 'fixed'].includes(getComputedStyle(currentNode).position)) {
  358. break;
  359. }
  360. const parentNode = getParentNode(currentNode);
  361. currentNode = isShadowRoot(parentNode) ? parentNode.host : parentNode;
  362. }
  363. return currentNode;
  364. }
  365. function getInnerBoundingClientRect(element, strategy) {
  366. const clientRect = getBoundingClientRect(element, false, strategy === 'fixed');
  367. const top = clientRect.top + element.clientTop;
  368. const left = clientRect.left + element.clientLeft;
  369. return {
  370. top,
  371. left,
  372. x: left,
  373. y: top,
  374. right: left + element.clientWidth,
  375. bottom: top + element.clientHeight,
  376. width: element.clientWidth,
  377. height: element.clientHeight
  378. };
  379. }
  380. function getClientRectFromClippingAncestor(element, clippingParent, strategy) {
  381. if (clippingParent === 'viewport') {
  382. return rectToClientRect(getViewportRect(element, strategy));
  383. }
  384. if (isElement(clippingParent)) {
  385. return getInnerBoundingClientRect(clippingParent, strategy);
  386. }
  387. return rectToClientRect(getDocumentRect(getDocumentElement(element)));
  388. } // A "clipping ancestor" is an overflowable container with the characteristic of
  389. // clipping (or hiding) overflowing elements with a position different from
  390. // `initial`
  391. function getClippingAncestors(element) {
  392. const clippingAncestors = getOverflowAncestors(element);
  393. const nearestEscapableParent = getNearestParentCapableOfEscapingClipping(element, clippingAncestors);
  394. let clipperElement = null;
  395. if (nearestEscapableParent && isHTMLElement(nearestEscapableParent)) {
  396. const offsetParent = getOffsetParent(nearestEscapableParent);
  397. if (isOverflowElement(nearestEscapableParent)) {
  398. clipperElement = nearestEscapableParent;
  399. } else if (isHTMLElement(offsetParent)) {
  400. clipperElement = offsetParent;
  401. }
  402. }
  403. if (!isElement(clipperElement)) {
  404. return [];
  405. } // @ts-ignore isElement check ensures we return Array<Element>
  406. return clippingAncestors.filter(clippingAncestors => clipperElement && isElement(clippingAncestors) && contains(clippingAncestors, clipperElement) && getNodeName(clippingAncestors) !== 'body');
  407. } // Gets the maximum area that the element is visible in due to any number of
  408. // clipping ancestors
  409. function getClippingRect(_ref) {
  410. let {
  411. element,
  412. boundary,
  413. rootBoundary,
  414. strategy
  415. } = _ref;
  416. const mainClippingAncestors = boundary === 'clippingAncestors' ? getClippingAncestors(element) : [].concat(boundary);
  417. const clippingAncestors = [...mainClippingAncestors, rootBoundary];
  418. const firstClippingAncestor = clippingAncestors[0];
  419. const clippingRect = clippingAncestors.reduce((accRect, clippingAncestor) => {
  420. const rect = getClientRectFromClippingAncestor(element, clippingAncestor, strategy);
  421. accRect.top = max(rect.top, accRect.top);
  422. accRect.right = min(rect.right, accRect.right);
  423. accRect.bottom = min(rect.bottom, accRect.bottom);
  424. accRect.left = max(rect.left, accRect.left);
  425. return accRect;
  426. }, getClientRectFromClippingAncestor(element, firstClippingAncestor, strategy));
  427. return {
  428. width: clippingRect.right - clippingRect.left,
  429. height: clippingRect.bottom - clippingRect.top,
  430. x: clippingRect.left,
  431. y: clippingRect.top
  432. };
  433. }
  434. const platform = {
  435. getClippingRect,
  436. convertOffsetParentRelativeRectToViewportRelativeRect,
  437. isElement,
  438. getDimensions,
  439. getOffsetParent,
  440. getDocumentElement,
  441. getElementRects: _ref => {
  442. let {
  443. reference,
  444. floating,
  445. strategy
  446. } = _ref;
  447. return {
  448. reference: getRectRelativeToOffsetParent(reference, getOffsetParent(floating), strategy),
  449. floating: { ...getDimensions(floating),
  450. x: 0,
  451. y: 0
  452. }
  453. };
  454. },
  455. getClientRects: element => Array.from(element.getClientRects()),
  456. isRTL: element => getComputedStyle(element).direction === 'rtl'
  457. };
  458. /**
  459. * Automatically updates the position of the floating element when necessary.
  460. * @see https://floating-ui.com/docs/autoUpdate
  461. */
  462. function autoUpdate(reference, floating, update, options) {
  463. if (options === void 0) {
  464. options = {};
  465. }
  466. const {
  467. ancestorScroll: _ancestorScroll = true,
  468. ancestorResize = true,
  469. elementResize = true,
  470. animationFrame = false
  471. } = options;
  472. const ancestorScroll = _ancestorScroll && !animationFrame;
  473. const ancestors = ancestorScroll || ancestorResize ? [...(isElement(reference) ? getOverflowAncestors(reference) : []), ...getOverflowAncestors(floating)] : [];
  474. ancestors.forEach(ancestor => {
  475. ancestorScroll && ancestor.addEventListener('scroll', update, {
  476. passive: true
  477. });
  478. ancestorResize && ancestor.addEventListener('resize', update);
  479. });
  480. let observer = null;
  481. if (elementResize) {
  482. let initialUpdate = true;
  483. observer = new ResizeObserver(() => {
  484. if (!initialUpdate) {
  485. update();
  486. }
  487. initialUpdate = false;
  488. });
  489. isElement(reference) && !animationFrame && observer.observe(reference);
  490. observer.observe(floating);
  491. }
  492. let frameId;
  493. let prevRefRect = animationFrame ? getBoundingClientRect(reference) : null;
  494. if (animationFrame) {
  495. frameLoop();
  496. }
  497. function frameLoop() {
  498. const nextRefRect = getBoundingClientRect(reference);
  499. if (prevRefRect && (nextRefRect.x !== prevRefRect.x || nextRefRect.y !== prevRefRect.y || nextRefRect.width !== prevRefRect.width || nextRefRect.height !== prevRefRect.height)) {
  500. update();
  501. }
  502. prevRefRect = nextRefRect;
  503. frameId = requestAnimationFrame(frameLoop);
  504. }
  505. update();
  506. return () => {
  507. var _observer;
  508. ancestors.forEach(ancestor => {
  509. ancestorScroll && ancestor.removeEventListener('scroll', update);
  510. ancestorResize && ancestor.removeEventListener('resize', update);
  511. });
  512. (_observer = observer) == null ? void 0 : _observer.disconnect();
  513. observer = null;
  514. if (animationFrame) {
  515. cancelAnimationFrame(frameId);
  516. }
  517. };
  518. }
  519. /**
  520. * Computes the `x` and `y` coordinates that will place the floating element
  521. * next to a reference element when it is given a certain CSS positioning
  522. * strategy.
  523. */
  524. const computePosition = (reference, floating, options) => computePosition$1(reference, floating, {
  525. platform,
  526. ...options
  527. });
  528. export { autoUpdate, computePosition, getOverflowAncestors, platform };