floating-ui.core.esm.js 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041
  1. import { getSideAxis, getAlignmentAxis, getAxisLength, getSide, getAlignment, evaluate, getPaddingObject, rectToClientRect, min, clamp, placements, getAlignmentSides, getOppositeAlignmentPlacement, getOppositePlacement, getExpandedPlacements, getOppositeAxisPlacements, sides, max, getOppositeAxis } from '@floating-ui/utils';
  2. export { rectToClientRect } from '@floating-ui/utils';
  3. function computeCoordsFromPlacement(_ref, placement, rtl) {
  4. let {
  5. reference,
  6. floating
  7. } = _ref;
  8. const sideAxis = getSideAxis(placement);
  9. const alignmentAxis = getAlignmentAxis(placement);
  10. const alignLength = getAxisLength(alignmentAxis);
  11. const side = getSide(placement);
  12. const isVertical = sideAxis === 'y';
  13. const commonX = reference.x + reference.width / 2 - floating.width / 2;
  14. const commonY = reference.y + reference.height / 2 - floating.height / 2;
  15. const commonAlign = reference[alignLength] / 2 - floating[alignLength] / 2;
  16. let coords;
  17. switch (side) {
  18. case 'top':
  19. coords = {
  20. x: commonX,
  21. y: reference.y - floating.height
  22. };
  23. break;
  24. case 'bottom':
  25. coords = {
  26. x: commonX,
  27. y: reference.y + reference.height
  28. };
  29. break;
  30. case 'right':
  31. coords = {
  32. x: reference.x + reference.width,
  33. y: commonY
  34. };
  35. break;
  36. case 'left':
  37. coords = {
  38. x: reference.x - floating.width,
  39. y: commonY
  40. };
  41. break;
  42. default:
  43. coords = {
  44. x: reference.x,
  45. y: reference.y
  46. };
  47. }
  48. switch (getAlignment(placement)) {
  49. case 'start':
  50. coords[alignmentAxis] -= commonAlign * (rtl && isVertical ? -1 : 1);
  51. break;
  52. case 'end':
  53. coords[alignmentAxis] += commonAlign * (rtl && isVertical ? -1 : 1);
  54. break;
  55. }
  56. return coords;
  57. }
  58. /**
  59. * Computes the `x` and `y` coordinates that will place the floating element
  60. * next to a given reference element.
  61. *
  62. * This export does not have any `platform` interface logic. You will need to
  63. * write one for the platform you are using Floating UI with.
  64. */
  65. const computePosition = async (reference, floating, config) => {
  66. const {
  67. placement = 'bottom',
  68. strategy = 'absolute',
  69. middleware = [],
  70. platform
  71. } = config;
  72. const validMiddleware = middleware.filter(Boolean);
  73. const rtl = await (platform.isRTL == null ? void 0 : platform.isRTL(floating));
  74. let rects = await platform.getElementRects({
  75. reference,
  76. floating,
  77. strategy
  78. });
  79. let {
  80. x,
  81. y
  82. } = computeCoordsFromPlacement(rects, placement, rtl);
  83. let statefulPlacement = placement;
  84. let middlewareData = {};
  85. let resetCount = 0;
  86. for (let i = 0; i < validMiddleware.length; i++) {
  87. const {
  88. name,
  89. fn
  90. } = validMiddleware[i];
  91. const {
  92. x: nextX,
  93. y: nextY,
  94. data,
  95. reset
  96. } = await fn({
  97. x,
  98. y,
  99. initialPlacement: placement,
  100. placement: statefulPlacement,
  101. strategy,
  102. middlewareData,
  103. rects,
  104. platform,
  105. elements: {
  106. reference,
  107. floating
  108. }
  109. });
  110. x = nextX != null ? nextX : x;
  111. y = nextY != null ? nextY : y;
  112. middlewareData = {
  113. ...middlewareData,
  114. [name]: {
  115. ...middlewareData[name],
  116. ...data
  117. }
  118. };
  119. if (reset && resetCount <= 50) {
  120. resetCount++;
  121. if (typeof reset === 'object') {
  122. if (reset.placement) {
  123. statefulPlacement = reset.placement;
  124. }
  125. if (reset.rects) {
  126. rects = reset.rects === true ? await platform.getElementRects({
  127. reference,
  128. floating,
  129. strategy
  130. }) : reset.rects;
  131. }
  132. ({
  133. x,
  134. y
  135. } = computeCoordsFromPlacement(rects, statefulPlacement, rtl));
  136. }
  137. i = -1;
  138. }
  139. }
  140. return {
  141. x,
  142. y,
  143. placement: statefulPlacement,
  144. strategy,
  145. middlewareData
  146. };
  147. };
  148. /**
  149. * Resolves with an object of overflow side offsets that determine how much the
  150. * element is overflowing a given clipping boundary on each side.
  151. * - positive = overflowing the boundary by that number of pixels
  152. * - negative = how many pixels left before it will overflow
  153. * - 0 = lies flush with the boundary
  154. * @see https://floating-ui.com/docs/detectOverflow
  155. */
  156. async function detectOverflow(state, options) {
  157. var _await$platform$isEle;
  158. if (options === void 0) {
  159. options = {};
  160. }
  161. const {
  162. x,
  163. y,
  164. platform,
  165. rects,
  166. elements,
  167. strategy
  168. } = state;
  169. const {
  170. boundary = 'clippingAncestors',
  171. rootBoundary = 'viewport',
  172. elementContext = 'floating',
  173. altBoundary = false,
  174. padding = 0
  175. } = evaluate(options, state);
  176. const paddingObject = getPaddingObject(padding);
  177. const altContext = elementContext === 'floating' ? 'reference' : 'floating';
  178. const element = elements[altBoundary ? altContext : elementContext];
  179. const clippingClientRect = rectToClientRect(await platform.getClippingRect({
  180. element: ((_await$platform$isEle = await (platform.isElement == null ? void 0 : platform.isElement(element))) != null ? _await$platform$isEle : true) ? element : element.contextElement || (await (platform.getDocumentElement == null ? void 0 : platform.getDocumentElement(elements.floating))),
  181. boundary,
  182. rootBoundary,
  183. strategy
  184. }));
  185. const rect = elementContext === 'floating' ? {
  186. x,
  187. y,
  188. width: rects.floating.width,
  189. height: rects.floating.height
  190. } : rects.reference;
  191. const offsetParent = await (platform.getOffsetParent == null ? void 0 : platform.getOffsetParent(elements.floating));
  192. const offsetScale = (await (platform.isElement == null ? void 0 : platform.isElement(offsetParent))) ? (await (platform.getScale == null ? void 0 : platform.getScale(offsetParent))) || {
  193. x: 1,
  194. y: 1
  195. } : {
  196. x: 1,
  197. y: 1
  198. };
  199. const elementClientRect = rectToClientRect(platform.convertOffsetParentRelativeRectToViewportRelativeRect ? await platform.convertOffsetParentRelativeRectToViewportRelativeRect({
  200. elements,
  201. rect,
  202. offsetParent,
  203. strategy
  204. }) : rect);
  205. return {
  206. top: (clippingClientRect.top - elementClientRect.top + paddingObject.top) / offsetScale.y,
  207. bottom: (elementClientRect.bottom - clippingClientRect.bottom + paddingObject.bottom) / offsetScale.y,
  208. left: (clippingClientRect.left - elementClientRect.left + paddingObject.left) / offsetScale.x,
  209. right: (elementClientRect.right - clippingClientRect.right + paddingObject.right) / offsetScale.x
  210. };
  211. }
  212. /**
  213. * Provides data to position an inner element of the floating element so that it
  214. * appears centered to the reference element.
  215. * @see https://floating-ui.com/docs/arrow
  216. */
  217. const arrow = options => ({
  218. name: 'arrow',
  219. options,
  220. async fn(state) {
  221. const {
  222. x,
  223. y,
  224. placement,
  225. rects,
  226. platform,
  227. elements,
  228. middlewareData
  229. } = state;
  230. // Since `element` is required, we don't Partial<> the type.
  231. const {
  232. element,
  233. padding = 0
  234. } = evaluate(options, state) || {};
  235. if (element == null) {
  236. return {};
  237. }
  238. const paddingObject = getPaddingObject(padding);
  239. const coords = {
  240. x,
  241. y
  242. };
  243. const axis = getAlignmentAxis(placement);
  244. const length = getAxisLength(axis);
  245. const arrowDimensions = await platform.getDimensions(element);
  246. const isYAxis = axis === 'y';
  247. const minProp = isYAxis ? 'top' : 'left';
  248. const maxProp = isYAxis ? 'bottom' : 'right';
  249. const clientProp = isYAxis ? 'clientHeight' : 'clientWidth';
  250. const endDiff = rects.reference[length] + rects.reference[axis] - coords[axis] - rects.floating[length];
  251. const startDiff = coords[axis] - rects.reference[axis];
  252. const arrowOffsetParent = await (platform.getOffsetParent == null ? void 0 : platform.getOffsetParent(element));
  253. let clientSize = arrowOffsetParent ? arrowOffsetParent[clientProp] : 0;
  254. // DOM platform can return `window` as the `offsetParent`.
  255. if (!clientSize || !(await (platform.isElement == null ? void 0 : platform.isElement(arrowOffsetParent)))) {
  256. clientSize = elements.floating[clientProp] || rects.floating[length];
  257. }
  258. const centerToReference = endDiff / 2 - startDiff / 2;
  259. // If the padding is large enough that it causes the arrow to no longer be
  260. // centered, modify the padding so that it is centered.
  261. const largestPossiblePadding = clientSize / 2 - arrowDimensions[length] / 2 - 1;
  262. const minPadding = min(paddingObject[minProp], largestPossiblePadding);
  263. const maxPadding = min(paddingObject[maxProp], largestPossiblePadding);
  264. // Make sure the arrow doesn't overflow the floating element if the center
  265. // point is outside the floating element's bounds.
  266. const min$1 = minPadding;
  267. const max = clientSize - arrowDimensions[length] - maxPadding;
  268. const center = clientSize / 2 - arrowDimensions[length] / 2 + centerToReference;
  269. const offset = clamp(min$1, center, max);
  270. // If the reference is small enough that the arrow's padding causes it to
  271. // to point to nothing for an aligned placement, adjust the offset of the
  272. // floating element itself. To ensure `shift()` continues to take action,
  273. // a single reset is performed when this is true.
  274. const shouldAddOffset = !middlewareData.arrow && getAlignment(placement) != null && center !== offset && rects.reference[length] / 2 - (center < min$1 ? minPadding : maxPadding) - arrowDimensions[length] / 2 < 0;
  275. const alignmentOffset = shouldAddOffset ? center < min$1 ? center - min$1 : center - max : 0;
  276. return {
  277. [axis]: coords[axis] + alignmentOffset,
  278. data: {
  279. [axis]: offset,
  280. centerOffset: center - offset - alignmentOffset,
  281. ...(shouldAddOffset && {
  282. alignmentOffset
  283. })
  284. },
  285. reset: shouldAddOffset
  286. };
  287. }
  288. });
  289. function getPlacementList(alignment, autoAlignment, allowedPlacements) {
  290. const allowedPlacementsSortedByAlignment = alignment ? [...allowedPlacements.filter(placement => getAlignment(placement) === alignment), ...allowedPlacements.filter(placement => getAlignment(placement) !== alignment)] : allowedPlacements.filter(placement => getSide(placement) === placement);
  291. return allowedPlacementsSortedByAlignment.filter(placement => {
  292. if (alignment) {
  293. return getAlignment(placement) === alignment || (autoAlignment ? getOppositeAlignmentPlacement(placement) !== placement : false);
  294. }
  295. return true;
  296. });
  297. }
  298. /**
  299. * Optimizes the visibility of the floating element by choosing the placement
  300. * that has the most space available automatically, without needing to specify a
  301. * preferred placement. Alternative to `flip`.
  302. * @see https://floating-ui.com/docs/autoPlacement
  303. */
  304. const autoPlacement = function (options) {
  305. if (options === void 0) {
  306. options = {};
  307. }
  308. return {
  309. name: 'autoPlacement',
  310. options,
  311. async fn(state) {
  312. var _middlewareData$autoP, _middlewareData$autoP2, _placementsThatFitOnE;
  313. const {
  314. rects,
  315. middlewareData,
  316. placement,
  317. platform,
  318. elements
  319. } = state;
  320. const {
  321. crossAxis = false,
  322. alignment,
  323. allowedPlacements = placements,
  324. autoAlignment = true,
  325. ...detectOverflowOptions
  326. } = evaluate(options, state);
  327. const placements$1 = alignment !== undefined || allowedPlacements === placements ? getPlacementList(alignment || null, autoAlignment, allowedPlacements) : allowedPlacements;
  328. const overflow = await detectOverflow(state, detectOverflowOptions);
  329. const currentIndex = ((_middlewareData$autoP = middlewareData.autoPlacement) == null ? void 0 : _middlewareData$autoP.index) || 0;
  330. const currentPlacement = placements$1[currentIndex];
  331. if (currentPlacement == null) {
  332. return {};
  333. }
  334. const alignmentSides = getAlignmentSides(currentPlacement, rects, await (platform.isRTL == null ? void 0 : platform.isRTL(elements.floating)));
  335. // Make `computeCoords` start from the right place.
  336. if (placement !== currentPlacement) {
  337. return {
  338. reset: {
  339. placement: placements$1[0]
  340. }
  341. };
  342. }
  343. const currentOverflows = [overflow[getSide(currentPlacement)], overflow[alignmentSides[0]], overflow[alignmentSides[1]]];
  344. const allOverflows = [...(((_middlewareData$autoP2 = middlewareData.autoPlacement) == null ? void 0 : _middlewareData$autoP2.overflows) || []), {
  345. placement: currentPlacement,
  346. overflows: currentOverflows
  347. }];
  348. const nextPlacement = placements$1[currentIndex + 1];
  349. // There are more placements to check.
  350. if (nextPlacement) {
  351. return {
  352. data: {
  353. index: currentIndex + 1,
  354. overflows: allOverflows
  355. },
  356. reset: {
  357. placement: nextPlacement
  358. }
  359. };
  360. }
  361. const placementsSortedByMostSpace = allOverflows.map(d => {
  362. const alignment = getAlignment(d.placement);
  363. return [d.placement, alignment && crossAxis ?
  364. // Check along the mainAxis and main crossAxis side.
  365. d.overflows.slice(0, 2).reduce((acc, v) => acc + v, 0) :
  366. // Check only the mainAxis.
  367. d.overflows[0], d.overflows];
  368. }).sort((a, b) => a[1] - b[1]);
  369. const placementsThatFitOnEachSide = placementsSortedByMostSpace.filter(d => d[2].slice(0,
  370. // Aligned placements should not check their opposite crossAxis
  371. // side.
  372. getAlignment(d[0]) ? 2 : 3).every(v => v <= 0));
  373. const resetPlacement = ((_placementsThatFitOnE = placementsThatFitOnEachSide[0]) == null ? void 0 : _placementsThatFitOnE[0]) || placementsSortedByMostSpace[0][0];
  374. if (resetPlacement !== placement) {
  375. return {
  376. data: {
  377. index: currentIndex + 1,
  378. overflows: allOverflows
  379. },
  380. reset: {
  381. placement: resetPlacement
  382. }
  383. };
  384. }
  385. return {};
  386. }
  387. };
  388. };
  389. /**
  390. * Optimizes the visibility of the floating element by flipping the `placement`
  391. * in order to keep it in view when the preferred placement(s) will overflow the
  392. * clipping boundary. Alternative to `autoPlacement`.
  393. * @see https://floating-ui.com/docs/flip
  394. */
  395. const flip = function (options) {
  396. if (options === void 0) {
  397. options = {};
  398. }
  399. return {
  400. name: 'flip',
  401. options,
  402. async fn(state) {
  403. var _middlewareData$arrow, _middlewareData$flip;
  404. const {
  405. placement,
  406. middlewareData,
  407. rects,
  408. initialPlacement,
  409. platform,
  410. elements
  411. } = state;
  412. const {
  413. mainAxis: checkMainAxis = true,
  414. crossAxis: checkCrossAxis = true,
  415. fallbackPlacements: specifiedFallbackPlacements,
  416. fallbackStrategy = 'bestFit',
  417. fallbackAxisSideDirection = 'none',
  418. flipAlignment = true,
  419. ...detectOverflowOptions
  420. } = evaluate(options, state);
  421. // If a reset by the arrow was caused due to an alignment offset being
  422. // added, we should skip any logic now since `flip()` has already done its
  423. // work.
  424. // https://github.com/floating-ui/floating-ui/issues/2549#issuecomment-1719601643
  425. if ((_middlewareData$arrow = middlewareData.arrow) != null && _middlewareData$arrow.alignmentOffset) {
  426. return {};
  427. }
  428. const side = getSide(placement);
  429. const initialSideAxis = getSideAxis(initialPlacement);
  430. const isBasePlacement = getSide(initialPlacement) === initialPlacement;
  431. const rtl = await (platform.isRTL == null ? void 0 : platform.isRTL(elements.floating));
  432. const fallbackPlacements = specifiedFallbackPlacements || (isBasePlacement || !flipAlignment ? [getOppositePlacement(initialPlacement)] : getExpandedPlacements(initialPlacement));
  433. const hasFallbackAxisSideDirection = fallbackAxisSideDirection !== 'none';
  434. if (!specifiedFallbackPlacements && hasFallbackAxisSideDirection) {
  435. fallbackPlacements.push(...getOppositeAxisPlacements(initialPlacement, flipAlignment, fallbackAxisSideDirection, rtl));
  436. }
  437. const placements = [initialPlacement, ...fallbackPlacements];
  438. const overflow = await detectOverflow(state, detectOverflowOptions);
  439. const overflows = [];
  440. let overflowsData = ((_middlewareData$flip = middlewareData.flip) == null ? void 0 : _middlewareData$flip.overflows) || [];
  441. if (checkMainAxis) {
  442. overflows.push(overflow[side]);
  443. }
  444. if (checkCrossAxis) {
  445. const sides = getAlignmentSides(placement, rects, rtl);
  446. overflows.push(overflow[sides[0]], overflow[sides[1]]);
  447. }
  448. overflowsData = [...overflowsData, {
  449. placement,
  450. overflows
  451. }];
  452. // One or more sides is overflowing.
  453. if (!overflows.every(side => side <= 0)) {
  454. var _middlewareData$flip2, _overflowsData$filter;
  455. const nextIndex = (((_middlewareData$flip2 = middlewareData.flip) == null ? void 0 : _middlewareData$flip2.index) || 0) + 1;
  456. const nextPlacement = placements[nextIndex];
  457. if (nextPlacement) {
  458. // Try next placement and re-run the lifecycle.
  459. return {
  460. data: {
  461. index: nextIndex,
  462. overflows: overflowsData
  463. },
  464. reset: {
  465. placement: nextPlacement
  466. }
  467. };
  468. }
  469. // First, find the candidates that fit on the mainAxis side of overflow,
  470. // then find the placement that fits the best on the main crossAxis side.
  471. let resetPlacement = (_overflowsData$filter = overflowsData.filter(d => d.overflows[0] <= 0).sort((a, b) => a.overflows[1] - b.overflows[1])[0]) == null ? void 0 : _overflowsData$filter.placement;
  472. // Otherwise fallback.
  473. if (!resetPlacement) {
  474. switch (fallbackStrategy) {
  475. case 'bestFit':
  476. {
  477. var _overflowsData$filter2;
  478. const placement = (_overflowsData$filter2 = overflowsData.filter(d => {
  479. if (hasFallbackAxisSideDirection) {
  480. const currentSideAxis = getSideAxis(d.placement);
  481. return currentSideAxis === initialSideAxis ||
  482. // Create a bias to the `y` side axis due to horizontal
  483. // reading directions favoring greater width.
  484. currentSideAxis === 'y';
  485. }
  486. return true;
  487. }).map(d => [d.placement, d.overflows.filter(overflow => overflow > 0).reduce((acc, overflow) => acc + overflow, 0)]).sort((a, b) => a[1] - b[1])[0]) == null ? void 0 : _overflowsData$filter2[0];
  488. if (placement) {
  489. resetPlacement = placement;
  490. }
  491. break;
  492. }
  493. case 'initialPlacement':
  494. resetPlacement = initialPlacement;
  495. break;
  496. }
  497. }
  498. if (placement !== resetPlacement) {
  499. return {
  500. reset: {
  501. placement: resetPlacement
  502. }
  503. };
  504. }
  505. }
  506. return {};
  507. }
  508. };
  509. };
  510. function getSideOffsets(overflow, rect) {
  511. return {
  512. top: overflow.top - rect.height,
  513. right: overflow.right - rect.width,
  514. bottom: overflow.bottom - rect.height,
  515. left: overflow.left - rect.width
  516. };
  517. }
  518. function isAnySideFullyClipped(overflow) {
  519. return sides.some(side => overflow[side] >= 0);
  520. }
  521. /**
  522. * Provides data to hide the floating element in applicable situations, such as
  523. * when it is not in the same clipping context as the reference element.
  524. * @see https://floating-ui.com/docs/hide
  525. */
  526. const hide = function (options) {
  527. if (options === void 0) {
  528. options = {};
  529. }
  530. return {
  531. name: 'hide',
  532. options,
  533. async fn(state) {
  534. const {
  535. rects
  536. } = state;
  537. const {
  538. strategy = 'referenceHidden',
  539. ...detectOverflowOptions
  540. } = evaluate(options, state);
  541. switch (strategy) {
  542. case 'referenceHidden':
  543. {
  544. const overflow = await detectOverflow(state, {
  545. ...detectOverflowOptions,
  546. elementContext: 'reference'
  547. });
  548. const offsets = getSideOffsets(overflow, rects.reference);
  549. return {
  550. data: {
  551. referenceHiddenOffsets: offsets,
  552. referenceHidden: isAnySideFullyClipped(offsets)
  553. }
  554. };
  555. }
  556. case 'escaped':
  557. {
  558. const overflow = await detectOverflow(state, {
  559. ...detectOverflowOptions,
  560. altBoundary: true
  561. });
  562. const offsets = getSideOffsets(overflow, rects.floating);
  563. return {
  564. data: {
  565. escapedOffsets: offsets,
  566. escaped: isAnySideFullyClipped(offsets)
  567. }
  568. };
  569. }
  570. default:
  571. {
  572. return {};
  573. }
  574. }
  575. }
  576. };
  577. };
  578. function getBoundingRect(rects) {
  579. const minX = min(...rects.map(rect => rect.left));
  580. const minY = min(...rects.map(rect => rect.top));
  581. const maxX = max(...rects.map(rect => rect.right));
  582. const maxY = max(...rects.map(rect => rect.bottom));
  583. return {
  584. x: minX,
  585. y: minY,
  586. width: maxX - minX,
  587. height: maxY - minY
  588. };
  589. }
  590. function getRectsByLine(rects) {
  591. const sortedRects = rects.slice().sort((a, b) => a.y - b.y);
  592. const groups = [];
  593. let prevRect = null;
  594. for (let i = 0; i < sortedRects.length; i++) {
  595. const rect = sortedRects[i];
  596. if (!prevRect || rect.y - prevRect.y > prevRect.height / 2) {
  597. groups.push([rect]);
  598. } else {
  599. groups[groups.length - 1].push(rect);
  600. }
  601. prevRect = rect;
  602. }
  603. return groups.map(rect => rectToClientRect(getBoundingRect(rect)));
  604. }
  605. /**
  606. * Provides improved positioning for inline reference elements that can span
  607. * over multiple lines, such as hyperlinks or range selections.
  608. * @see https://floating-ui.com/docs/inline
  609. */
  610. const inline = function (options) {
  611. if (options === void 0) {
  612. options = {};
  613. }
  614. return {
  615. name: 'inline',
  616. options,
  617. async fn(state) {
  618. const {
  619. placement,
  620. elements,
  621. rects,
  622. platform,
  623. strategy
  624. } = state;
  625. // A MouseEvent's client{X,Y} coords can be up to 2 pixels off a
  626. // ClientRect's bounds, despite the event listener being triggered. A
  627. // padding of 2 seems to handle this issue.
  628. const {
  629. padding = 2,
  630. x,
  631. y
  632. } = evaluate(options, state);
  633. const nativeClientRects = Array.from((await (platform.getClientRects == null ? void 0 : platform.getClientRects(elements.reference))) || []);
  634. const clientRects = getRectsByLine(nativeClientRects);
  635. const fallback = rectToClientRect(getBoundingRect(nativeClientRects));
  636. const paddingObject = getPaddingObject(padding);
  637. function getBoundingClientRect() {
  638. // There are two rects and they are disjoined.
  639. if (clientRects.length === 2 && clientRects[0].left > clientRects[1].right && x != null && y != null) {
  640. // Find the first rect in which the point is fully inside.
  641. return clientRects.find(rect => x > rect.left - paddingObject.left && x < rect.right + paddingObject.right && y > rect.top - paddingObject.top && y < rect.bottom + paddingObject.bottom) || fallback;
  642. }
  643. // There are 2 or more connected rects.
  644. if (clientRects.length >= 2) {
  645. if (getSideAxis(placement) === 'y') {
  646. const firstRect = clientRects[0];
  647. const lastRect = clientRects[clientRects.length - 1];
  648. const isTop = getSide(placement) === 'top';
  649. const top = firstRect.top;
  650. const bottom = lastRect.bottom;
  651. const left = isTop ? firstRect.left : lastRect.left;
  652. const right = isTop ? firstRect.right : lastRect.right;
  653. const width = right - left;
  654. const height = bottom - top;
  655. return {
  656. top,
  657. bottom,
  658. left,
  659. right,
  660. width,
  661. height,
  662. x: left,
  663. y: top
  664. };
  665. }
  666. const isLeftSide = getSide(placement) === 'left';
  667. const maxRight = max(...clientRects.map(rect => rect.right));
  668. const minLeft = min(...clientRects.map(rect => rect.left));
  669. const measureRects = clientRects.filter(rect => isLeftSide ? rect.left === minLeft : rect.right === maxRight);
  670. const top = measureRects[0].top;
  671. const bottom = measureRects[measureRects.length - 1].bottom;
  672. const left = minLeft;
  673. const right = maxRight;
  674. const width = right - left;
  675. const height = bottom - top;
  676. return {
  677. top,
  678. bottom,
  679. left,
  680. right,
  681. width,
  682. height,
  683. x: left,
  684. y: top
  685. };
  686. }
  687. return fallback;
  688. }
  689. const resetRects = await platform.getElementRects({
  690. reference: {
  691. getBoundingClientRect
  692. },
  693. floating: elements.floating,
  694. strategy
  695. });
  696. if (rects.reference.x !== resetRects.reference.x || rects.reference.y !== resetRects.reference.y || rects.reference.width !== resetRects.reference.width || rects.reference.height !== resetRects.reference.height) {
  697. return {
  698. reset: {
  699. rects: resetRects
  700. }
  701. };
  702. }
  703. return {};
  704. }
  705. };
  706. };
  707. // For type backwards-compatibility, the `OffsetOptions` type was also
  708. // Derivable.
  709. async function convertValueToCoords(state, options) {
  710. const {
  711. placement,
  712. platform,
  713. elements
  714. } = state;
  715. const rtl = await (platform.isRTL == null ? void 0 : platform.isRTL(elements.floating));
  716. const side = getSide(placement);
  717. const alignment = getAlignment(placement);
  718. const isVertical = getSideAxis(placement) === 'y';
  719. const mainAxisMulti = ['left', 'top'].includes(side) ? -1 : 1;
  720. const crossAxisMulti = rtl && isVertical ? -1 : 1;
  721. const rawValue = evaluate(options, state);
  722. // eslint-disable-next-line prefer-const
  723. let {
  724. mainAxis,
  725. crossAxis,
  726. alignmentAxis
  727. } = typeof rawValue === 'number' ? {
  728. mainAxis: rawValue,
  729. crossAxis: 0,
  730. alignmentAxis: null
  731. } : {
  732. mainAxis: rawValue.mainAxis || 0,
  733. crossAxis: rawValue.crossAxis || 0,
  734. alignmentAxis: rawValue.alignmentAxis
  735. };
  736. if (alignment && typeof alignmentAxis === 'number') {
  737. crossAxis = alignment === 'end' ? alignmentAxis * -1 : alignmentAxis;
  738. }
  739. return isVertical ? {
  740. x: crossAxis * crossAxisMulti,
  741. y: mainAxis * mainAxisMulti
  742. } : {
  743. x: mainAxis * mainAxisMulti,
  744. y: crossAxis * crossAxisMulti
  745. };
  746. }
  747. /**
  748. * Modifies the placement by translating the floating element along the
  749. * specified axes.
  750. * A number (shorthand for `mainAxis` or distance), or an axes configuration
  751. * object may be passed.
  752. * @see https://floating-ui.com/docs/offset
  753. */
  754. const offset = function (options) {
  755. if (options === void 0) {
  756. options = 0;
  757. }
  758. return {
  759. name: 'offset',
  760. options,
  761. async fn(state) {
  762. var _middlewareData$offse, _middlewareData$arrow;
  763. const {
  764. x,
  765. y,
  766. placement,
  767. middlewareData
  768. } = state;
  769. const diffCoords = await convertValueToCoords(state, options);
  770. // If the placement is the same and the arrow caused an alignment offset
  771. // then we don't need to change the positioning coordinates.
  772. if (placement === ((_middlewareData$offse = middlewareData.offset) == null ? void 0 : _middlewareData$offse.placement) && (_middlewareData$arrow = middlewareData.arrow) != null && _middlewareData$arrow.alignmentOffset) {
  773. return {};
  774. }
  775. return {
  776. x: x + diffCoords.x,
  777. y: y + diffCoords.y,
  778. data: {
  779. ...diffCoords,
  780. placement
  781. }
  782. };
  783. }
  784. };
  785. };
  786. /**
  787. * Optimizes the visibility of the floating element by shifting it in order to
  788. * keep it in view when it will overflow the clipping boundary.
  789. * @see https://floating-ui.com/docs/shift
  790. */
  791. const shift = function (options) {
  792. if (options === void 0) {
  793. options = {};
  794. }
  795. return {
  796. name: 'shift',
  797. options,
  798. async fn(state) {
  799. const {
  800. x,
  801. y,
  802. placement
  803. } = state;
  804. const {
  805. mainAxis: checkMainAxis = true,
  806. crossAxis: checkCrossAxis = false,
  807. limiter = {
  808. fn: _ref => {
  809. let {
  810. x,
  811. y
  812. } = _ref;
  813. return {
  814. x,
  815. y
  816. };
  817. }
  818. },
  819. ...detectOverflowOptions
  820. } = evaluate(options, state);
  821. const coords = {
  822. x,
  823. y
  824. };
  825. const overflow = await detectOverflow(state, detectOverflowOptions);
  826. const crossAxis = getSideAxis(getSide(placement));
  827. const mainAxis = getOppositeAxis(crossAxis);
  828. let mainAxisCoord = coords[mainAxis];
  829. let crossAxisCoord = coords[crossAxis];
  830. if (checkMainAxis) {
  831. const minSide = mainAxis === 'y' ? 'top' : 'left';
  832. const maxSide = mainAxis === 'y' ? 'bottom' : 'right';
  833. const min = mainAxisCoord + overflow[minSide];
  834. const max = mainAxisCoord - overflow[maxSide];
  835. mainAxisCoord = clamp(min, mainAxisCoord, max);
  836. }
  837. if (checkCrossAxis) {
  838. const minSide = crossAxis === 'y' ? 'top' : 'left';
  839. const maxSide = crossAxis === 'y' ? 'bottom' : 'right';
  840. const min = crossAxisCoord + overflow[minSide];
  841. const max = crossAxisCoord - overflow[maxSide];
  842. crossAxisCoord = clamp(min, crossAxisCoord, max);
  843. }
  844. const limitedCoords = limiter.fn({
  845. ...state,
  846. [mainAxis]: mainAxisCoord,
  847. [crossAxis]: crossAxisCoord
  848. });
  849. return {
  850. ...limitedCoords,
  851. data: {
  852. x: limitedCoords.x - x,
  853. y: limitedCoords.y - y,
  854. enabled: {
  855. [mainAxis]: checkMainAxis,
  856. [crossAxis]: checkCrossAxis
  857. }
  858. }
  859. };
  860. }
  861. };
  862. };
  863. /**
  864. * Built-in `limiter` that will stop `shift()` at a certain point.
  865. */
  866. const limitShift = function (options) {
  867. if (options === void 0) {
  868. options = {};
  869. }
  870. return {
  871. options,
  872. fn(state) {
  873. const {
  874. x,
  875. y,
  876. placement,
  877. rects,
  878. middlewareData
  879. } = state;
  880. const {
  881. offset = 0,
  882. mainAxis: checkMainAxis = true,
  883. crossAxis: checkCrossAxis = true
  884. } = evaluate(options, state);
  885. const coords = {
  886. x,
  887. y
  888. };
  889. const crossAxis = getSideAxis(placement);
  890. const mainAxis = getOppositeAxis(crossAxis);
  891. let mainAxisCoord = coords[mainAxis];
  892. let crossAxisCoord = coords[crossAxis];
  893. const rawOffset = evaluate(offset, state);
  894. const computedOffset = typeof rawOffset === 'number' ? {
  895. mainAxis: rawOffset,
  896. crossAxis: 0
  897. } : {
  898. mainAxis: 0,
  899. crossAxis: 0,
  900. ...rawOffset
  901. };
  902. if (checkMainAxis) {
  903. const len = mainAxis === 'y' ? 'height' : 'width';
  904. const limitMin = rects.reference[mainAxis] - rects.floating[len] + computedOffset.mainAxis;
  905. const limitMax = rects.reference[mainAxis] + rects.reference[len] - computedOffset.mainAxis;
  906. if (mainAxisCoord < limitMin) {
  907. mainAxisCoord = limitMin;
  908. } else if (mainAxisCoord > limitMax) {
  909. mainAxisCoord = limitMax;
  910. }
  911. }
  912. if (checkCrossAxis) {
  913. var _middlewareData$offse, _middlewareData$offse2;
  914. const len = mainAxis === 'y' ? 'width' : 'height';
  915. const isOriginSide = ['top', 'left'].includes(getSide(placement));
  916. const limitMin = rects.reference[crossAxis] - rects.floating[len] + (isOriginSide ? ((_middlewareData$offse = middlewareData.offset) == null ? void 0 : _middlewareData$offse[crossAxis]) || 0 : 0) + (isOriginSide ? 0 : computedOffset.crossAxis);
  917. const limitMax = rects.reference[crossAxis] + rects.reference[len] + (isOriginSide ? 0 : ((_middlewareData$offse2 = middlewareData.offset) == null ? void 0 : _middlewareData$offse2[crossAxis]) || 0) - (isOriginSide ? computedOffset.crossAxis : 0);
  918. if (crossAxisCoord < limitMin) {
  919. crossAxisCoord = limitMin;
  920. } else if (crossAxisCoord > limitMax) {
  921. crossAxisCoord = limitMax;
  922. }
  923. }
  924. return {
  925. [mainAxis]: mainAxisCoord,
  926. [crossAxis]: crossAxisCoord
  927. };
  928. }
  929. };
  930. };
  931. /**
  932. * Provides data that allows you to change the size of the floating element —
  933. * for instance, prevent it from overflowing the clipping boundary or match the
  934. * width of the reference element.
  935. * @see https://floating-ui.com/docs/size
  936. */
  937. const size = function (options) {
  938. if (options === void 0) {
  939. options = {};
  940. }
  941. return {
  942. name: 'size',
  943. options,
  944. async fn(state) {
  945. var _state$middlewareData, _state$middlewareData2;
  946. const {
  947. placement,
  948. rects,
  949. platform,
  950. elements
  951. } = state;
  952. const {
  953. apply = () => {},
  954. ...detectOverflowOptions
  955. } = evaluate(options, state);
  956. const overflow = await detectOverflow(state, detectOverflowOptions);
  957. const side = getSide(placement);
  958. const alignment = getAlignment(placement);
  959. const isYAxis = getSideAxis(placement) === 'y';
  960. const {
  961. width,
  962. height
  963. } = rects.floating;
  964. let heightSide;
  965. let widthSide;
  966. if (side === 'top' || side === 'bottom') {
  967. heightSide = side;
  968. widthSide = alignment === ((await (platform.isRTL == null ? void 0 : platform.isRTL(elements.floating))) ? 'start' : 'end') ? 'left' : 'right';
  969. } else {
  970. widthSide = side;
  971. heightSide = alignment === 'end' ? 'top' : 'bottom';
  972. }
  973. const maximumClippingHeight = height - overflow.top - overflow.bottom;
  974. const maximumClippingWidth = width - overflow.left - overflow.right;
  975. const overflowAvailableHeight = min(height - overflow[heightSide], maximumClippingHeight);
  976. const overflowAvailableWidth = min(width - overflow[widthSide], maximumClippingWidth);
  977. const noShift = !state.middlewareData.shift;
  978. let availableHeight = overflowAvailableHeight;
  979. let availableWidth = overflowAvailableWidth;
  980. if ((_state$middlewareData = state.middlewareData.shift) != null && _state$middlewareData.enabled.x) {
  981. availableWidth = maximumClippingWidth;
  982. }
  983. if ((_state$middlewareData2 = state.middlewareData.shift) != null && _state$middlewareData2.enabled.y) {
  984. availableHeight = maximumClippingHeight;
  985. }
  986. if (noShift && !alignment) {
  987. const xMin = max(overflow.left, 0);
  988. const xMax = max(overflow.right, 0);
  989. const yMin = max(overflow.top, 0);
  990. const yMax = max(overflow.bottom, 0);
  991. if (isYAxis) {
  992. availableWidth = width - 2 * (xMin !== 0 || xMax !== 0 ? xMin + xMax : max(overflow.left, overflow.right));
  993. } else {
  994. availableHeight = height - 2 * (yMin !== 0 || yMax !== 0 ? yMin + yMax : max(overflow.top, overflow.bottom));
  995. }
  996. }
  997. await apply({
  998. ...state,
  999. availableWidth,
  1000. availableHeight
  1001. });
  1002. const nextDimensions = await platform.getDimensions(elements.floating);
  1003. if (width !== nextDimensions.width || height !== nextDimensions.height) {
  1004. return {
  1005. reset: {
  1006. rects: true
  1007. }
  1008. };
  1009. }
  1010. return {};
  1011. }
  1012. };
  1013. };
  1014. export { arrow, autoPlacement, computePosition, detectOverflow, flip, hide, inline, limitShift, offset, shift, size };