floating-ui.react-dom.mjs 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. import { arrow as arrow$1, computePosition } from '@floating-ui/dom';
  2. export { autoPlacement, autoUpdate, computePosition, detectOverflow, flip, getOverflowAncestors, hide, inline, limitShift, offset, platform, shift, size } from '@floating-ui/dom';
  3. import * as React from 'react';
  4. import { useLayoutEffect, useEffect } from 'react';
  5. import * as ReactDOM from 'react-dom';
  6. /**
  7. * Provides data to position an inner element of the floating element so that it
  8. * appears centered to the reference element.
  9. * This wraps the core `arrow` middleware to allow React refs as the element.
  10. * @see https://floating-ui.com/docs/arrow
  11. */
  12. const arrow = options => {
  13. function isRef(value) {
  14. return {}.hasOwnProperty.call(value, 'current');
  15. }
  16. return {
  17. name: 'arrow',
  18. options,
  19. fn(state) {
  20. const {
  21. element,
  22. padding
  23. } = typeof options === 'function' ? options(state) : options;
  24. if (element && isRef(element)) {
  25. if (element.current != null) {
  26. return arrow$1({
  27. element: element.current,
  28. padding
  29. }).fn(state);
  30. }
  31. return {};
  32. }
  33. if (element) {
  34. return arrow$1({
  35. element,
  36. padding
  37. }).fn(state);
  38. }
  39. return {};
  40. }
  41. };
  42. };
  43. var index = typeof document !== 'undefined' ? useLayoutEffect : useEffect;
  44. // Fork of `fast-deep-equal` that only does the comparisons we need and compares
  45. // functions
  46. function deepEqual(a, b) {
  47. if (a === b) {
  48. return true;
  49. }
  50. if (typeof a !== typeof b) {
  51. return false;
  52. }
  53. if (typeof a === 'function' && a.toString() === b.toString()) {
  54. return true;
  55. }
  56. let length;
  57. let i;
  58. let keys;
  59. if (a && b && typeof a === 'object') {
  60. if (Array.isArray(a)) {
  61. length = a.length;
  62. if (length !== b.length) return false;
  63. for (i = length; i-- !== 0;) {
  64. if (!deepEqual(a[i], b[i])) {
  65. return false;
  66. }
  67. }
  68. return true;
  69. }
  70. keys = Object.keys(a);
  71. length = keys.length;
  72. if (length !== Object.keys(b).length) {
  73. return false;
  74. }
  75. for (i = length; i-- !== 0;) {
  76. if (!{}.hasOwnProperty.call(b, keys[i])) {
  77. return false;
  78. }
  79. }
  80. for (i = length; i-- !== 0;) {
  81. const key = keys[i];
  82. if (key === '_owner' && a.$$typeof) {
  83. continue;
  84. }
  85. if (!deepEqual(a[key], b[key])) {
  86. return false;
  87. }
  88. }
  89. return true;
  90. }
  91. // biome-ignore lint/suspicious/noSelfCompare: in source
  92. return a !== a && b !== b;
  93. }
  94. function getDPR(element) {
  95. if (typeof window === 'undefined') {
  96. return 1;
  97. }
  98. const win = element.ownerDocument.defaultView || window;
  99. return win.devicePixelRatio || 1;
  100. }
  101. function roundByDPR(element, value) {
  102. const dpr = getDPR(element);
  103. return Math.round(value * dpr) / dpr;
  104. }
  105. function useLatestRef(value) {
  106. const ref = React.useRef(value);
  107. index(() => {
  108. ref.current = value;
  109. });
  110. return ref;
  111. }
  112. /**
  113. * Provides data to position a floating element.
  114. * @see https://floating-ui.com/docs/useFloating
  115. */
  116. function useFloating(options) {
  117. if (options === void 0) {
  118. options = {};
  119. }
  120. const {
  121. placement = 'bottom',
  122. strategy = 'absolute',
  123. middleware = [],
  124. platform,
  125. elements: {
  126. reference: externalReference,
  127. floating: externalFloating
  128. } = {},
  129. transform = true,
  130. whileElementsMounted,
  131. open
  132. } = options;
  133. const [data, setData] = React.useState({
  134. x: 0,
  135. y: 0,
  136. strategy,
  137. placement,
  138. middlewareData: {},
  139. isPositioned: false
  140. });
  141. const [latestMiddleware, setLatestMiddleware] = React.useState(middleware);
  142. if (!deepEqual(latestMiddleware, middleware)) {
  143. setLatestMiddleware(middleware);
  144. }
  145. const [_reference, _setReference] = React.useState(null);
  146. const [_floating, _setFloating] = React.useState(null);
  147. const setReference = React.useCallback(node => {
  148. if (node !== referenceRef.current) {
  149. referenceRef.current = node;
  150. _setReference(node);
  151. }
  152. }, []);
  153. const setFloating = React.useCallback(node => {
  154. if (node !== floatingRef.current) {
  155. floatingRef.current = node;
  156. _setFloating(node);
  157. }
  158. }, []);
  159. const referenceEl = externalReference || _reference;
  160. const floatingEl = externalFloating || _floating;
  161. const referenceRef = React.useRef(null);
  162. const floatingRef = React.useRef(null);
  163. const dataRef = React.useRef(data);
  164. const hasWhileElementsMounted = whileElementsMounted != null;
  165. const whileElementsMountedRef = useLatestRef(whileElementsMounted);
  166. const platformRef = useLatestRef(platform);
  167. const update = React.useCallback(() => {
  168. if (!referenceRef.current || !floatingRef.current) {
  169. return;
  170. }
  171. const config = {
  172. placement,
  173. strategy,
  174. middleware: latestMiddleware
  175. };
  176. if (platformRef.current) {
  177. config.platform = platformRef.current;
  178. }
  179. computePosition(referenceRef.current, floatingRef.current, config).then(data => {
  180. const fullData = {
  181. ...data,
  182. isPositioned: true
  183. };
  184. if (isMountedRef.current && !deepEqual(dataRef.current, fullData)) {
  185. dataRef.current = fullData;
  186. ReactDOM.flushSync(() => {
  187. setData(fullData);
  188. });
  189. }
  190. });
  191. }, [latestMiddleware, placement, strategy, platformRef]);
  192. index(() => {
  193. if (open === false && dataRef.current.isPositioned) {
  194. dataRef.current.isPositioned = false;
  195. setData(data => ({
  196. ...data,
  197. isPositioned: false
  198. }));
  199. }
  200. }, [open]);
  201. const isMountedRef = React.useRef(false);
  202. index(() => {
  203. isMountedRef.current = true;
  204. return () => {
  205. isMountedRef.current = false;
  206. };
  207. }, []);
  208. // biome-ignore lint/correctness/useExhaustiveDependencies: `hasWhileElementsMounted` is intentionally included.
  209. index(() => {
  210. if (referenceEl) referenceRef.current = referenceEl;
  211. if (floatingEl) floatingRef.current = floatingEl;
  212. if (referenceEl && floatingEl) {
  213. if (whileElementsMountedRef.current) {
  214. return whileElementsMountedRef.current(referenceEl, floatingEl, update);
  215. }
  216. update();
  217. }
  218. }, [referenceEl, floatingEl, update, whileElementsMountedRef, hasWhileElementsMounted]);
  219. const refs = React.useMemo(() => ({
  220. reference: referenceRef,
  221. floating: floatingRef,
  222. setReference,
  223. setFloating
  224. }), [setReference, setFloating]);
  225. const elements = React.useMemo(() => ({
  226. reference: referenceEl,
  227. floating: floatingEl
  228. }), [referenceEl, floatingEl]);
  229. const floatingStyles = React.useMemo(() => {
  230. const initialStyles = {
  231. position: strategy,
  232. left: 0,
  233. top: 0
  234. };
  235. if (!elements.floating) {
  236. return initialStyles;
  237. }
  238. const x = roundByDPR(elements.floating, data.x);
  239. const y = roundByDPR(elements.floating, data.y);
  240. if (transform) {
  241. return {
  242. ...initialStyles,
  243. transform: "translate(" + x + "px, " + y + "px)",
  244. ...(getDPR(elements.floating) >= 1.5 && {
  245. willChange: 'transform'
  246. })
  247. };
  248. }
  249. return {
  250. position: strategy,
  251. left: x,
  252. top: y
  253. };
  254. }, [strategy, transform, elements.floating, data.x, data.y]);
  255. return React.useMemo(() => ({
  256. ...data,
  257. update,
  258. refs,
  259. elements,
  260. floatingStyles
  261. }), [data, update, refs, elements, floatingStyles]);
  262. }
  263. export { arrow, useFloating };