@yandex/ui
Version:
Yandex UI components
103 lines (102 loc) • 5.59 kB
JavaScript
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;
});
}