UNPKG

@yandex/ui

Version:

Yandex UI components

126 lines (125 loc) 6.03 kB
import { __read, __spread } from "tslib"; import { useMemo, useRef, useState } from 'react'; import { isEqual } from '../lib/isEqual'; import { useIsomorphicLayoutEffect as useLayoutEffect } from '../useIsomorphicLayoutEffect'; import { createPopper } from './createPopper'; import { getElementsFromRefs } from './utils'; /** * Реакт-хук, реализующий позиционирование попапа при помощи popper. */ export function usePopper(props) { var anchorRef = props.anchorRef, _a = props.arrowMarginThreshold, arrowMarginThreshold = _a === void 0 ? 4 : _a, _b = props.placement, placement = _b === void 0 ? 'bottom' : _b, _c = props.enabled, enabled = _c === void 0 ? true : _c, _d = props.marginThreshold, marginThreshold = _d === void 0 ? 16 : _d, _e = props.modifiers, modifiers = _e === void 0 ? [] : _e, motionless = props.motionless, offset = props.offset, unsafe_tailOffset = props.unsafe_tailOffset, children = props.children, boundary = props.boundary; var placements = Array.isArray(placement) ? placement : [placement]; var popperRef = useRef(null); var prevPopperOptions = useRef(null); // Используем useState вместо useRef для установки ссылок, т.к. нам // важно выполнить обновление в момент установки, а не на следующем тике. var _f = __read(useState(), 2), popupNode = _f[0], setPopupNode = _f[1]; var _g = __read(useState(), 2), arrowNode = _g[0], setArrowNode = _g[1]; var popperOptions = useMemo(function () { var _a = __read(placements), placement = _a[0], fallbackPlacements = _a.slice(1); var popperBoundary = getElementsFromRefs(boundary); var options = { // Добавляем children в опции popper для того, // чтобы обновить координаты при изменении контента. children: children, // При инициализации указываем единственное направление, // все остальные направления применяются в модификаторе flip. placement: placement, modifiers: __spread([ { name: 'eventListeners', enabled: !motionless, }, { name: 'offset', options: { offset: offset, tailOffset: unsafe_tailOffset, }, }, { name: 'computeStyles', options: { gpuAcceleration: false, }, }, { name: 'preventOverflow', options: { // Свойство позволяет учитывать границы в overflow контейнере. altBoundary: true, boundary: popperBoundary, }, }, { name: 'arrow', enabled: Boolean(arrowNode), options: { element: arrowNode, padding: arrowMarginThreshold, }, }, { name: 'flip', options: { padding: marginThreshold, fallbackPlacements: fallbackPlacements, // Свойство позволяет учитывать границы в overflow контейнере. altBoundary: true, boundary: popperBoundary, }, }, { name: 'hide', options: { boundary: popperBoundary, }, } ], modifiers), }; // Отдаем объект из кэша если значения в опциях не изменились, // это позволяет более эффективно производить обновления и не требовать // от пользователей кэшировать устанавливаемые свойства. if (isEqual(prevPopperOptions.current, options)) { return prevPopperOptions.current || options; } return (prevPopperOptions.current = options); }, [ placements, offset, arrowNode, arrowMarginThreshold, motionless, marginThreshold, unsafe_tailOffset, modifiers, children, boundary, ]); useLayoutEffect(function () { if (popperRef.current !== null) { popperRef.current.setOptions(popperOptions); } }, [popperOptions]); useLayoutEffect(function () { // NOTE: В данный момент не реализован cleanup для случая, когда якорь был удален из документа, // т.к. мы используем ref-объект, а не прямую ссылку на DOM-элемент. if (anchorRef.current && popupNode && enabled) { popperRef.current = createPopper(anchorRef.current, popupNode, popperOptions); popperRef.current.forceUpdate(); } return function () { if (popperRef.current !== null) { popperRef.current.destroy(); popperRef.current = null; } }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [anchorRef, popupNode, enabled]); return { popper: popperRef.current, setArrowRef: setArrowNode, setPopupRef: setPopupNode, }; }