floating-ui.core.esm.js 35 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046
  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. var _overflowsData$;
  459. const ignoreCrossAxisOverflow = checkCrossAxis === 'alignment' ? initialSideAxis !== getSideAxis(nextPlacement) : false;
  460. const hasInitialMainAxisOverflow = ((_overflowsData$ = overflowsData[0]) == null ? void 0 : _overflowsData$.overflows[0]) > 0;
  461. if (!ignoreCrossAxisOverflow || hasInitialMainAxisOverflow) {
  462. // Try next placement and re-run the lifecycle.
  463. return {
  464. data: {
  465. index: nextIndex,
  466. overflows: overflowsData
  467. },
  468. reset: {
  469. placement: nextPlacement
  470. }
  471. };
  472. }
  473. }
  474. // First, find the candidates that fit on the mainAxis side of overflow,
  475. // then find the placement that fits the best on the main crossAxis side.
  476. 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;
  477. // Otherwise fallback.
  478. if (!resetPlacement) {
  479. switch (fallbackStrategy) {
  480. case 'bestFit':
  481. {
  482. var _overflowsData$filter2;
  483. const placement = (_overflowsData$filter2 = overflowsData.filter(d => {
  484. if (hasFallbackAxisSideDirection) {
  485. const currentSideAxis = getSideAxis(d.placement);
  486. return currentSideAxis === initialSideAxis ||
  487. // Create a bias to the `y` side axis due to horizontal
  488. // reading directions favoring greater width.
  489. currentSideAxis === 'y';
  490. }
  491. return true;
  492. }).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];
  493. if (placement) {
  494. resetPlacement = placement;
  495. }
  496. break;
  497. }
  498. case 'initialPlacement':
  499. resetPlacement = initialPlacement;
  500. break;
  501. }
  502. }
  503. if (placement !== resetPlacement) {
  504. return {
  505. reset: {
  506. placement: resetPlacement
  507. }
  508. };
  509. }
  510. }
  511. return {};
  512. }
  513. };
  514. };
  515. function getSideOffsets(overflow, rect) {
  516. return {
  517. top: overflow.top - rect.height,
  518. right: overflow.right - rect.width,
  519. bottom: overflow.bottom - rect.height,
  520. left: overflow.left - rect.width
  521. };
  522. }
  523. function isAnySideFullyClipped(overflow) {
  524. return sides.some(side => overflow[side] >= 0);
  525. }
  526. /**
  527. * Provides data to hide the floating element in applicable situations, such as
  528. * when it is not in the same clipping context as the reference element.
  529. * @see https://floating-ui.com/docs/hide
  530. */
  531. const hide = function (options) {
  532. if (options === void 0) {
  533. options = {};
  534. }
  535. return {
  536. name: 'hide',
  537. options,
  538. async fn(state) {
  539. const {
  540. rects
  541. } = state;
  542. const {
  543. strategy = 'referenceHidden',
  544. ...detectOverflowOptions
  545. } = evaluate(options, state);
  546. switch (strategy) {
  547. case 'referenceHidden':
  548. {
  549. const overflow = await detectOverflow(state, {
  550. ...detectOverflowOptions,
  551. elementContext: 'reference'
  552. });
  553. const offsets = getSideOffsets(overflow, rects.reference);
  554. return {
  555. data: {
  556. referenceHiddenOffsets: offsets,
  557. referenceHidden: isAnySideFullyClipped(offsets)
  558. }
  559. };
  560. }
  561. case 'escaped':
  562. {
  563. const overflow = await detectOverflow(state, {
  564. ...detectOverflowOptions,
  565. altBoundary: true
  566. });
  567. const offsets = getSideOffsets(overflow, rects.floating);
  568. return {
  569. data: {
  570. escapedOffsets: offsets,
  571. escaped: isAnySideFullyClipped(offsets)
  572. }
  573. };
  574. }
  575. default:
  576. {
  577. return {};
  578. }
  579. }
  580. }
  581. };
  582. };
  583. function getBoundingRect(rects) {
  584. const minX = min(...rects.map(rect => rect.left));
  585. const minY = min(...rects.map(rect => rect.top));
  586. const maxX = max(...rects.map(rect => rect.right));
  587. const maxY = max(...rects.map(rect => rect.bottom));
  588. return {
  589. x: minX,
  590. y: minY,
  591. width: maxX - minX,
  592. height: maxY - minY
  593. };
  594. }
  595. function getRectsByLine(rects) {
  596. const sortedRects = rects.slice().sort((a, b) => a.y - b.y);
  597. const groups = [];
  598. let prevRect = null;
  599. for (let i = 0; i < sortedRects.length; i++) {
  600. const rect = sortedRects[i];
  601. if (!prevRect || rect.y - prevRect.y > prevRect.height / 2) {
  602. groups.push([rect]);
  603. } else {
  604. groups[groups.length - 1].push(rect);
  605. }
  606. prevRect = rect;
  607. }
  608. return groups.map(rect => rectToClientRect(getBoundingRect(rect)));
  609. }
  610. /**
  611. * Provides improved positioning for inline reference elements that can span
  612. * over multiple lines, such as hyperlinks or range selections.
  613. * @see https://floating-ui.com/docs/inline
  614. */
  615. const inline = function (options) {
  616. if (options === void 0) {
  617. options = {};
  618. }
  619. return {
  620. name: 'inline',
  621. options,
  622. async fn(state) {
  623. const {
  624. placement,
  625. elements,
  626. rects,
  627. platform,
  628. strategy
  629. } = state;
  630. // A MouseEvent's client{X,Y} coords can be up to 2 pixels off a
  631. // ClientRect's bounds, despite the event listener being triggered. A
  632. // padding of 2 seems to handle this issue.
  633. const {
  634. padding = 2,
  635. x,
  636. y
  637. } = evaluate(options, state);
  638. const nativeClientRects = Array.from((await (platform.getClientRects == null ? void 0 : platform.getClientRects(elements.reference))) || []);
  639. const clientRects = getRectsByLine(nativeClientRects);
  640. const fallback = rectToClientRect(getBoundingRect(nativeClientRects));
  641. const paddingObject = getPaddingObject(padding);
  642. function getBoundingClientRect() {
  643. // There are two rects and they are disjoined.
  644. if (clientRects.length === 2 && clientRects[0].left > clientRects[1].right && x != null && y != null) {
  645. // Find the first rect in which the point is fully inside.
  646. 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;
  647. }
  648. // There are 2 or more connected rects.
  649. if (clientRects.length >= 2) {
  650. if (getSideAxis(placement) === 'y') {
  651. const firstRect = clientRects[0];
  652. const lastRect = clientRects[clientRects.length - 1];
  653. const isTop = getSide(placement) === 'top';
  654. const top = firstRect.top;
  655. const bottom = lastRect.bottom;
  656. const left = isTop ? firstRect.left : lastRect.left;
  657. const right = isTop ? firstRect.right : lastRect.right;
  658. const width = right - left;
  659. const height = bottom - top;
  660. return {
  661. top,
  662. bottom,
  663. left,
  664. right,
  665. width,
  666. height,
  667. x: left,
  668. y: top
  669. };
  670. }
  671. const isLeftSide = getSide(placement) === 'left';
  672. const maxRight = max(...clientRects.map(rect => rect.right));
  673. const minLeft = min(...clientRects.map(rect => rect.left));
  674. const measureRects = clientRects.filter(rect => isLeftSide ? rect.left === minLeft : rect.right === maxRight);
  675. const top = measureRects[0].top;
  676. const bottom = measureRects[measureRects.length - 1].bottom;
  677. const left = minLeft;
  678. const right = maxRight;
  679. const width = right - left;
  680. const height = bottom - top;
  681. return {
  682. top,
  683. bottom,
  684. left,
  685. right,
  686. width,
  687. height,
  688. x: left,
  689. y: top
  690. };
  691. }
  692. return fallback;
  693. }
  694. const resetRects = await platform.getElementRects({
  695. reference: {
  696. getBoundingClientRect
  697. },
  698. floating: elements.floating,
  699. strategy
  700. });
  701. 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) {
  702. return {
  703. reset: {
  704. rects: resetRects
  705. }
  706. };
  707. }
  708. return {};
  709. }
  710. };
  711. };
  712. // For type backwards-compatibility, the `OffsetOptions` type was also
  713. // Derivable.
  714. async function convertValueToCoords(state, options) {
  715. const {
  716. placement,
  717. platform,
  718. elements
  719. } = state;
  720. const rtl = await (platform.isRTL == null ? void 0 : platform.isRTL(elements.floating));
  721. const side = getSide(placement);
  722. const alignment = getAlignment(placement);
  723. const isVertical = getSideAxis(placement) === 'y';
  724. const mainAxisMulti = ['left', 'top'].includes(side) ? -1 : 1;
  725. const crossAxisMulti = rtl && isVertical ? -1 : 1;
  726. const rawValue = evaluate(options, state);
  727. // eslint-disable-next-line prefer-const
  728. let {
  729. mainAxis,
  730. crossAxis,
  731. alignmentAxis
  732. } = typeof rawValue === 'number' ? {
  733. mainAxis: rawValue,
  734. crossAxis: 0,
  735. alignmentAxis: null
  736. } : {
  737. mainAxis: rawValue.mainAxis || 0,
  738. crossAxis: rawValue.crossAxis || 0,
  739. alignmentAxis: rawValue.alignmentAxis
  740. };
  741. if (alignment && typeof alignmentAxis === 'number') {
  742. crossAxis = alignment === 'end' ? alignmentAxis * -1 : alignmentAxis;
  743. }
  744. return isVertical ? {
  745. x: crossAxis * crossAxisMulti,
  746. y: mainAxis * mainAxisMulti
  747. } : {
  748. x: mainAxis * mainAxisMulti,
  749. y: crossAxis * crossAxisMulti
  750. };
  751. }
  752. /**
  753. * Modifies the placement by translating the floating element along the
  754. * specified axes.
  755. * A number (shorthand for `mainAxis` or distance), or an axes configuration
  756. * object may be passed.
  757. * @see https://floating-ui.com/docs/offset
  758. */
  759. const offset = function (options) {
  760. if (options === void 0) {
  761. options = 0;
  762. }
  763. return {
  764. name: 'offset',
  765. options,
  766. async fn(state) {
  767. var _middlewareData$offse, _middlewareData$arrow;
  768. const {
  769. x,
  770. y,
  771. placement,
  772. middlewareData
  773. } = state;
  774. const diffCoords = await convertValueToCoords(state, options);
  775. // If the placement is the same and the arrow caused an alignment offset
  776. // then we don't need to change the positioning coordinates.
  777. if (placement === ((_middlewareData$offse = middlewareData.offset) == null ? void 0 : _middlewareData$offse.placement) && (_middlewareData$arrow = middlewareData.arrow) != null && _middlewareData$arrow.alignmentOffset) {
  778. return {};
  779. }
  780. return {
  781. x: x + diffCoords.x,
  782. y: y + diffCoords.y,
  783. data: {
  784. ...diffCoords,
  785. placement
  786. }
  787. };
  788. }
  789. };
  790. };
  791. /**
  792. * Optimizes the visibility of the floating element by shifting it in order to
  793. * keep it in view when it will overflow the clipping boundary.
  794. * @see https://floating-ui.com/docs/shift
  795. */
  796. const shift = function (options) {
  797. if (options === void 0) {
  798. options = {};
  799. }
  800. return {
  801. name: 'shift',
  802. options,
  803. async fn(state) {
  804. const {
  805. x,
  806. y,
  807. placement
  808. } = state;
  809. const {
  810. mainAxis: checkMainAxis = true,
  811. crossAxis: checkCrossAxis = false,
  812. limiter = {
  813. fn: _ref => {
  814. let {
  815. x,
  816. y
  817. } = _ref;
  818. return {
  819. x,
  820. y
  821. };
  822. }
  823. },
  824. ...detectOverflowOptions
  825. } = evaluate(options, state);
  826. const coords = {
  827. x,
  828. y
  829. };
  830. const overflow = await detectOverflow(state, detectOverflowOptions);
  831. const crossAxis = getSideAxis(getSide(placement));
  832. const mainAxis = getOppositeAxis(crossAxis);
  833. let mainAxisCoord = coords[mainAxis];
  834. let crossAxisCoord = coords[crossAxis];
  835. if (checkMainAxis) {
  836. const minSide = mainAxis === 'y' ? 'top' : 'left';
  837. const maxSide = mainAxis === 'y' ? 'bottom' : 'right';
  838. const min = mainAxisCoord + overflow[minSide];
  839. const max = mainAxisCoord - overflow[maxSide];
  840. mainAxisCoord = clamp(min, mainAxisCoord, max);
  841. }
  842. if (checkCrossAxis) {
  843. const minSide = crossAxis === 'y' ? 'top' : 'left';
  844. const maxSide = crossAxis === 'y' ? 'bottom' : 'right';
  845. const min = crossAxisCoord + overflow[minSide];
  846. const max = crossAxisCoord - overflow[maxSide];
  847. crossAxisCoord = clamp(min, crossAxisCoord, max);
  848. }
  849. const limitedCoords = limiter.fn({
  850. ...state,
  851. [mainAxis]: mainAxisCoord,
  852. [crossAxis]: crossAxisCoord
  853. });
  854. return {
  855. ...limitedCoords,
  856. data: {
  857. x: limitedCoords.x - x,
  858. y: limitedCoords.y - y,
  859. enabled: {
  860. [mainAxis]: checkMainAxis,
  861. [crossAxis]: checkCrossAxis
  862. }
  863. }
  864. };
  865. }
  866. };
  867. };
  868. /**
  869. * Built-in `limiter` that will stop `shift()` at a certain point.
  870. */
  871. const limitShift = function (options) {
  872. if (options === void 0) {
  873. options = {};
  874. }
  875. return {
  876. options,
  877. fn(state) {
  878. const {
  879. x,
  880. y,
  881. placement,
  882. rects,
  883. middlewareData
  884. } = state;
  885. const {
  886. offset = 0,
  887. mainAxis: checkMainAxis = true,
  888. crossAxis: checkCrossAxis = true
  889. } = evaluate(options, state);
  890. const coords = {
  891. x,
  892. y
  893. };
  894. const crossAxis = getSideAxis(placement);
  895. const mainAxis = getOppositeAxis(crossAxis);
  896. let mainAxisCoord = coords[mainAxis];
  897. let crossAxisCoord = coords[crossAxis];
  898. const rawOffset = evaluate(offset, state);
  899. const computedOffset = typeof rawOffset === 'number' ? {
  900. mainAxis: rawOffset,
  901. crossAxis: 0
  902. } : {
  903. mainAxis: 0,
  904. crossAxis: 0,
  905. ...rawOffset
  906. };
  907. if (checkMainAxis) {
  908. const len = mainAxis === 'y' ? 'height' : 'width';
  909. const limitMin = rects.reference[mainAxis] - rects.floating[len] + computedOffset.mainAxis;
  910. const limitMax = rects.reference[mainAxis] + rects.reference[len] - computedOffset.mainAxis;
  911. if (mainAxisCoord < limitMin) {
  912. mainAxisCoord = limitMin;
  913. } else if (mainAxisCoord > limitMax) {
  914. mainAxisCoord = limitMax;
  915. }
  916. }
  917. if (checkCrossAxis) {
  918. var _middlewareData$offse, _middlewareData$offse2;
  919. const len = mainAxis === 'y' ? 'width' : 'height';
  920. const isOriginSide = ['top', 'left'].includes(getSide(placement));
  921. 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);
  922. 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);
  923. if (crossAxisCoord < limitMin) {
  924. crossAxisCoord = limitMin;
  925. } else if (crossAxisCoord > limitMax) {
  926. crossAxisCoord = limitMax;
  927. }
  928. }
  929. return {
  930. [mainAxis]: mainAxisCoord,
  931. [crossAxis]: crossAxisCoord
  932. };
  933. }
  934. };
  935. };
  936. /**
  937. * Provides data that allows you to change the size of the floating element —
  938. * for instance, prevent it from overflowing the clipping boundary or match the
  939. * width of the reference element.
  940. * @see https://floating-ui.com/docs/size
  941. */
  942. const size = function (options) {
  943. if (options === void 0) {
  944. options = {};
  945. }
  946. return {
  947. name: 'size',
  948. options,
  949. async fn(state) {
  950. var _state$middlewareData, _state$middlewareData2;
  951. const {
  952. placement,
  953. rects,
  954. platform,
  955. elements
  956. } = state;
  957. const {
  958. apply = () => {},
  959. ...detectOverflowOptions
  960. } = evaluate(options, state);
  961. const overflow = await detectOverflow(state, detectOverflowOptions);
  962. const side = getSide(placement);
  963. const alignment = getAlignment(placement);
  964. const isYAxis = getSideAxis(placement) === 'y';
  965. const {
  966. width,
  967. height
  968. } = rects.floating;
  969. let heightSide;
  970. let widthSide;
  971. if (side === 'top' || side === 'bottom') {
  972. heightSide = side;
  973. widthSide = alignment === ((await (platform.isRTL == null ? void 0 : platform.isRTL(elements.floating))) ? 'start' : 'end') ? 'left' : 'right';
  974. } else {
  975. widthSide = side;
  976. heightSide = alignment === 'end' ? 'top' : 'bottom';
  977. }
  978. const maximumClippingHeight = height - overflow.top - overflow.bottom;
  979. const maximumClippingWidth = width - overflow.left - overflow.right;
  980. const overflowAvailableHeight = min(height - overflow[heightSide], maximumClippingHeight);
  981. const overflowAvailableWidth = min(width - overflow[widthSide], maximumClippingWidth);
  982. const noShift = !state.middlewareData.shift;
  983. let availableHeight = overflowAvailableHeight;
  984. let availableWidth = overflowAvailableWidth;
  985. if ((_state$middlewareData = state.middlewareData.shift) != null && _state$middlewareData.enabled.x) {
  986. availableWidth = maximumClippingWidth;
  987. }
  988. if ((_state$middlewareData2 = state.middlewareData.shift) != null && _state$middlewareData2.enabled.y) {
  989. availableHeight = maximumClippingHeight;
  990. }
  991. if (noShift && !alignment) {
  992. const xMin = max(overflow.left, 0);
  993. const xMax = max(overflow.right, 0);
  994. const yMin = max(overflow.top, 0);
  995. const yMax = max(overflow.bottom, 0);
  996. if (isYAxis) {
  997. availableWidth = width - 2 * (xMin !== 0 || xMax !== 0 ? xMin + xMax : max(overflow.left, overflow.right));
  998. } else {
  999. availableHeight = height - 2 * (yMin !== 0 || yMax !== 0 ? yMin + yMax : max(overflow.top, overflow.bottom));
  1000. }
  1001. }
  1002. await apply({
  1003. ...state,
  1004. availableWidth,
  1005. availableHeight
  1006. });
  1007. const nextDimensions = await platform.getDimensions(elements.floating);
  1008. if (width !== nextDimensions.width || height !== nextDimensions.height) {
  1009. return {
  1010. reset: {
  1011. rects: true
  1012. }
  1013. };
  1014. }
  1015. return {};
  1016. }
  1017. };
  1018. };
  1019. export { arrow, autoPlacement, computePosition, detectOverflow, flip, hide, inline, limitShift, offset, shift, size };