floating-ui.core.esm.js 33 KB

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