@yandex/ui
Version:
Yandex UI components
107 lines (106 loc) • 5.96 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.LayerManager = void 0;
var tslib_1 = require("tslib");
var react_1 = tslib_1.__importStar(require("react"));
var usePreviousValue_1 = require("../usePreviousValue");
var useUniqId_1 = require("../useUniqId");
/**
* Компонент реализующий закрытие всплывающих компонентов,
* таких как `Popup`, `Modal`, `Tooltip` и `MessageBox` в нужном порядке,
* по умолчанию используется внутри `Popup`.
*
* @param {LayerManagerProps}
*/
var LayerManager = function (_a) {
var visible = _a.visible, onClose = _a.onClose, children = _a.children, essentialRefs = _a.essentialRefs;
var id = useUniqId_1.useUniqId('layer');
var prevVisible = usePreviousValue_1.usePreviousValue(visible);
var prevOnClose = usePreviousValue_1.usePreviousValue(onClose);
var mouseDownRef = react_1.useRef(null);
var onDocumentKeyUp = react_1.useCallback(function (event) {
var key = event.key;
// @fixme: ISL-9529: keyboard.ts: использовать библиотеку для клавиатурных событий
if (key === 'Escape' || key === 'Esc') {
var _a = tslib_1.__read(exports.LayerManager.stack[exports.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 = react_1.useCallback(function (event) {
mouseDownRef.current = event.target;
}, []);
var onDocumentClick = react_1.useCallback(function (event) {
var _a = tslib_1.__read(exports.LayerManager.stack[exports.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
}, []);
react_1.useEffect(function () {
if (onClose !== prevOnClose && onClose !== undefined) {
exports.LayerManager.stack.forEach(function (_a, i) {
var _b = tslib_1.__read(_a, 2), layerOnClose = _b[1];
if (layerOnClose === prevOnClose) {
exports.LayerManager.stack[i][1] = onClose;
}
});
}
if (visible === prevVisible || onClose === undefined) {
return;
}
if (visible) {
exports.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]);
react_1.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_1.default.createElement(react_1.default.Fragment, null, children);
};
exports.LayerManager = LayerManager;
exports.LayerManager.stack = [];
exports.LayerManager.displayName = 'LayerManager';
function removeLayerById(id) {
exports.LayerManager.stack = exports.LayerManager.stack.filter(function (_a) {
var _b = tslib_1.__read(_a, 1), layerId = _b[0];
return layerId !== id;
});
}