UNPKG

@floating-ui/react-dom

Version:
423 lines (401 loc) 13 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@floating-ui/dom'), require('react'), require('react-dom')) : typeof define === 'function' && define.amd ? define(['exports', '@floating-ui/dom', 'react', 'react-dom'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.FloatingUIReactDOM = {}, global.FloatingUIDOM, global.React, global.ReactDOM)); })(this, (function (exports, dom, React, ReactDOM) { 'use strict'; function _interopNamespaceDefault(e) { var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } }); } }); } n.default = e; return Object.freeze(n); } var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React); var ReactDOM__namespace = /*#__PURE__*/_interopNamespaceDefault(ReactDOM); var isClient = typeof document !== 'undefined'; var noop = function noop() {}; var index = isClient ? React.useLayoutEffect : noop; // Fork of `fast-deep-equal` that only does the comparisons we need and compares // functions function deepEqual(a, b) { if (a === b) { return true; } if (typeof a !== typeof b) { return false; } if (typeof a === 'function' && a.toString() === b.toString()) { return true; } let length; let i; let keys; if (a && b && typeof a === 'object') { if (Array.isArray(a)) { length = a.length; if (length !== b.length) return false; for (i = length; i-- !== 0;) { if (!deepEqual(a[i], b[i])) { return false; } } return true; } keys = Object.keys(a); length = keys.length; if (length !== Object.keys(b).length) { return false; } for (i = length; i-- !== 0;) { if (!{}.hasOwnProperty.call(b, keys[i])) { return false; } } for (i = length; i-- !== 0;) { const key = keys[i]; if (key === '_owner' && a.$$typeof) { continue; } if (!deepEqual(a[key], b[key])) { return false; } } return true; } return a !== a && b !== b; } function getDPR(element) { if (typeof window === 'undefined') { return 1; } const win = element.ownerDocument.defaultView || window; return win.devicePixelRatio || 1; } function roundByDPR(element, value) { const dpr = getDPR(element); return Math.round(value * dpr) / dpr; } function useLatestRef(value) { const ref = React__namespace.useRef(value); index(() => { ref.current = value; }); return ref; } /** * Provides data to position a floating element. * @see https://floating-ui.com/docs/useFloating */ function useFloating(options) { if (options === void 0) { options = {}; } const { placement = 'bottom', strategy = 'absolute', middleware = [], platform, elements: { reference: externalReference, floating: externalFloating } = {}, transform = true, whileElementsMounted, open } = options; const [data, setData] = React__namespace.useState({ x: 0, y: 0, strategy, placement, middlewareData: {}, isPositioned: false }); const [latestMiddleware, setLatestMiddleware] = React__namespace.useState(middleware); if (!deepEqual(latestMiddleware, middleware)) { setLatestMiddleware(middleware); } const [_reference, _setReference] = React__namespace.useState(null); const [_floating, _setFloating] = React__namespace.useState(null); const setReference = React__namespace.useCallback(node => { if (node !== referenceRef.current) { referenceRef.current = node; _setReference(node); } }, []); const setFloating = React__namespace.useCallback(node => { if (node !== floatingRef.current) { floatingRef.current = node; _setFloating(node); } }, []); const referenceEl = externalReference || _reference; const floatingEl = externalFloating || _floating; const referenceRef = React__namespace.useRef(null); const floatingRef = React__namespace.useRef(null); const dataRef = React__namespace.useRef(data); const hasWhileElementsMounted = whileElementsMounted != null; const whileElementsMountedRef = useLatestRef(whileElementsMounted); const platformRef = useLatestRef(platform); const openRef = useLatestRef(open); const update = React__namespace.useCallback(() => { if (!referenceRef.current || !floatingRef.current) { return; } const config = { placement, strategy, middleware: latestMiddleware }; if (platformRef.current) { config.platform = platformRef.current; } dom.computePosition(referenceRef.current, floatingRef.current, config).then(data => { const fullData = { ...data, // The floating element's position may be recomputed while it's closed // but still mounted (such as when transitioning out). To ensure // `isPositioned` will be `false` initially on the next open, avoid // setting it to `true` when `open === false` (must be specified). isPositioned: openRef.current !== false }; if (isMountedRef.current && !deepEqual(dataRef.current, fullData)) { dataRef.current = fullData; ReactDOM__namespace.flushSync(() => { setData(fullData); }); } }); }, [latestMiddleware, placement, strategy, platformRef, openRef]); index(() => { if (open === false && dataRef.current.isPositioned) { dataRef.current.isPositioned = false; setData(data => ({ ...data, isPositioned: false })); } }, [open]); const isMountedRef = React__namespace.useRef(false); index(() => { isMountedRef.current = true; return () => { isMountedRef.current = false; }; }, []); index(() => { if (referenceEl) referenceRef.current = referenceEl; if (floatingEl) floatingRef.current = floatingEl; if (referenceEl && floatingEl) { if (whileElementsMountedRef.current) { return whileElementsMountedRef.current(referenceEl, floatingEl, update); } update(); } }, [referenceEl, floatingEl, update, whileElementsMountedRef, hasWhileElementsMounted]); const refs = React__namespace.useMemo(() => ({ reference: referenceRef, floating: floatingRef, setReference, setFloating }), [setReference, setFloating]); const elements = React__namespace.useMemo(() => ({ reference: referenceEl, floating: floatingEl }), [referenceEl, floatingEl]); const floatingStyles = React__namespace.useMemo(() => { const initialStyles = { position: strategy, left: 0, top: 0 }; if (!elements.floating) { return initialStyles; } const x = roundByDPR(elements.floating, data.x); const y = roundByDPR(elements.floating, data.y); if (transform) { return { ...initialStyles, transform: "translate(" + x + "px, " + y + "px)", ...(getDPR(elements.floating) >= 1.5 && { willChange: 'transform' }) }; } return { position: strategy, left: x, top: y }; }, [strategy, transform, elements.floating, data.x, data.y]); return React__namespace.useMemo(() => ({ ...data, update, refs, elements, floatingStyles }), [data, update, refs, elements, floatingStyles]); } /** * Provides data to position an inner element of the floating element so that it * appears centered to the reference element. * This wraps the core `arrow` middleware to allow React refs as the element. * @see https://floating-ui.com/docs/arrow */ const arrow$1 = options => { function isRef(value) { return {}.hasOwnProperty.call(value, 'current'); } return { name: 'arrow', options, fn(state) { const { element, padding } = typeof options === 'function' ? options(state) : options; if (element && isRef(element)) { if (element.current != null) { return dom.arrow({ element: element.current, padding }).fn(state); } return {}; } if (element) { return dom.arrow({ element, padding }).fn(state); } return {}; } }; }; /** * Modifies the placement by translating the floating element along the * specified axes. * A number (shorthand for `mainAxis` or distance), or an axes configuration * object may be passed. * @see https://floating-ui.com/docs/offset */ const offset = (options, deps) => ({ ...dom.offset(options), options: [options, deps] }); /** * Optimizes the visibility of the floating element by shifting it in order to * keep it in view when it will overflow the clipping boundary. * @see https://floating-ui.com/docs/shift */ const shift = (options, deps) => ({ ...dom.shift(options), options: [options, deps] }); /** * Built-in `limiter` that will stop `shift()` at a certain point. */ const limitShift = (options, deps) => ({ ...dom.limitShift(options), options: [options, deps] }); /** * Optimizes the visibility of the floating element by flipping the `placement` * in order to keep it in view when the preferred placement(s) will overflow the * clipping boundary. Alternative to `autoPlacement`. * @see https://floating-ui.com/docs/flip */ const flip = (options, deps) => ({ ...dom.flip(options), options: [options, deps] }); /** * Provides data that allows you to change the size of the floating element — * for instance, prevent it from overflowing the clipping boundary or match the * width of the reference element. * @see https://floating-ui.com/docs/size */ const size = (options, deps) => ({ ...dom.size(options), options: [options, deps] }); /** * Optimizes the visibility of the floating element by choosing the placement * that has the most space available automatically, without needing to specify a * preferred placement. Alternative to `flip`. * @see https://floating-ui.com/docs/autoPlacement */ const autoPlacement = (options, deps) => ({ ...dom.autoPlacement(options), options: [options, deps] }); /** * Provides data to hide the floating element in applicable situations, such as * when it is not in the same clipping context as the reference element. * @see https://floating-ui.com/docs/hide */ const hide = (options, deps) => ({ ...dom.hide(options), options: [options, deps] }); /** * Provides improved positioning for inline reference elements that can span * over multiple lines, such as hyperlinks or range selections. * @see https://floating-ui.com/docs/inline */ const inline = (options, deps) => ({ ...dom.inline(options), options: [options, deps] }); /** * Provides data to position an inner element of the floating element so that it * appears centered to the reference element. * This wraps the core `arrow` middleware to allow React refs as the element. * @see https://floating-ui.com/docs/arrow */ const arrow = (options, deps) => ({ ...arrow$1(options), options: [options, deps] }); Object.defineProperty(exports, "autoUpdate", { enumerable: true, get: function () { return dom.autoUpdate; } }); Object.defineProperty(exports, "computePosition", { enumerable: true, get: function () { return dom.computePosition; } }); Object.defineProperty(exports, "detectOverflow", { enumerable: true, get: function () { return dom.detectOverflow; } }); Object.defineProperty(exports, "getOverflowAncestors", { enumerable: true, get: function () { return dom.getOverflowAncestors; } }); Object.defineProperty(exports, "platform", { enumerable: true, get: function () { return dom.platform; } }); exports.arrow = arrow; exports.autoPlacement = autoPlacement; exports.flip = flip; exports.hide = hide; exports.inline = inline; exports.limitShift = limitShift; exports.offset = offset; exports.shift = shift; exports.size = size; exports.useFloating = useFloating; }));