floating-ui.core.mjs 34 KB

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