UNPKG

@yandex/ui

Version:

Yandex UI components

103 lines (102 loc) 5.59 kB
import { __read } from "tslib"; import React, { useEffect, useCallback, useRef } from 'react'; import { usePreviousValue } from '../usePreviousValue'; import { useUniqId } from '../useUniqId'; /** * Компонент реализующий закрытие всплывающих компонентов, * таких как `Popup`, `Modal`, `Tooltip` и `MessageBox` в нужном порядке, * по умолчанию используется внутри `Popup`. * * @param {LayerManagerProps} */ export var LayerManager = function (_a) { var visible = _a.visible, onClose = _a.onClose, children = _a.children, essentialRefs = _a.essentialRefs; var id = useUniqId('layer'); var prevVisible = usePreviousValue(visible); var prevOnClose = usePreviousValue(onClose); var mouseDownRef = useRef(null); var onDocumentKeyUp = useCallback(function (event) { var key = event.key; // @fixme: ISL-9529: keyboard.ts: использовать библиотеку для клавиатурных событий if (key === 'Escape' || key === 'Esc') { var _a = __read(LayerManager.stack[LayerManager.stack.length - 1] || [], 2), layerId = _a[0], layerOnClose = _a[1]; // Дополнительно проверяем id слоя, чтобы не вызывать layerOnClose n-раз. if (layerId === id && layerOnClose !== undefined) { layerOnClose(event, 'esc'); } } // eslint-disable-next-line react-hooks/exhaustive-deps }, []); var onDocumentMouseDown = useCallback(function (event) { mouseDownRef.current = event.target; }, []); var onDocumentClick = useCallback(function (event) { var _a = __read(LayerManager.stack[LayerManager.stack.length - 1] || [], 3), layerId = _a[0], layerOnClose = _a[1], refs = _a[2]; // Убеждаемся, что элемент, который был нажат, совпадает с последним // при срабатывании события mousedown. Это предотвращает закрытие диалогового окна // перетаскиванием курсора (например, выделением текста внутри диалогового окна // и отпусканием мыши за его пределами). if (mouseDownRef.current !== event.target) { return; } // Дополнительно проверяем id слоя, чтобы не вызывать layerOnClose n-раз. if (layerId === id && layerOnClose !== undefined && refs !== undefined) { var isEssentionalClick = refs .filter(function (ref) { return ref.current !== null; }) .some(function (ref) { return ref.current.contains(event.target); }); if (!isEssentionalClick) { layerOnClose(event, 'click'); } } // eslint-disable-next-line react-hooks/exhaustive-deps }, []); useEffect(function () { if (onClose !== prevOnClose && onClose !== undefined) { LayerManager.stack.forEach(function (_a, i) { var _b = __read(_a, 2), layerOnClose = _b[1]; if (layerOnClose === prevOnClose) { LayerManager.stack[i][1] = onClose; } }); } if (visible === prevVisible || onClose === undefined) { return; } if (visible) { LayerManager.stack.push([id, onClose, essentialRefs]); document.addEventListener('keyup', onDocumentKeyUp); document.addEventListener('mousedown', onDocumentMouseDown, true); document.addEventListener('click', onDocumentClick, true); } else { // Т.к. onCloseHandlers у нас не является стейтом компонента, // то удаление обработчика может произойти раньше, чем его вызов, // поэтому используем raf для удаления в следующем тике. requestAnimationFrame(function () { return removeLayerById(id); }); document.removeEventListener('keyup', onDocumentKeyUp); document.removeEventListener('mousedown', onDocumentMouseDown, true); document.removeEventListener('click', onDocumentClick, true); } // Не добавляем essentialRefs в зависимости, // т.к. они нужны единожды при добавлении в стек. // eslint-disable-next-line react-hooks/exhaustive-deps }, [prevVisible, visible, onClose, prevOnClose]); useEffect(function () { return function () { requestAnimationFrame(function () { return removeLayerById(id); }); document.removeEventListener('keyup', onDocumentKeyUp); document.removeEventListener('mousedown', onDocumentMouseDown, true); document.removeEventListener('click', onDocumentClick, true); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return React.createElement(React.Fragment, null, children); }; LayerManager.stack = []; LayerManager.displayName = 'LayerManager'; function removeLayerById(id) { LayerManager.stack = LayerManager.stack.filter(function (_a) { var _b = __read(_a, 1), layerId = _b[0]; return layerId !== id; }); }