react-elegant-ui
Version:
Elegant UI components, made by BEM best practices for react
153 lines (151 loc) • 6.51 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.LayerManager = void 0;
var _react = _interopRequireWildcard(require("react"));
var _useImmutableCallback = require("../../hooks/useImmutableCallback");
var _usePrevious = require("../../hooks/usePrevious");
var _useUniqueId = require("../../hooks/useUniqueId");
var _keyboard = require("../../lib/keyboard");
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
// Imported from yandex-ui. Source: https://github.com/bem/yandex-ui/
// TODO: replace global object to context
var LayerRegistry = {
stack: []
};
function removeLayerById(layerId) {
LayerRegistry.stack = LayerRegistry.stack.filter(function (_a) {
var id = _a.id;
return id !== layerId;
});
}
/**
* Component to manage layers of pop-up components like `Popup` or `Modal`
*
* It allow close elements in that order what it did open
*
* @param {LayerManagerProps}
*/
var LayerManager = function (_a) {
var visible = _a.visible,
onClose = _a.onClose,
children = _a.children,
essentialRefs = _a.essentialRefs;
var id = (0, _useUniqueId.useUniqueId)('layer');
var prevOnClose = (0, _usePrevious.usePrevious)(onClose);
var mouseDownRef = (0, _react.useRef)(null);
// Collect ShadowRoot nodes from essential refs
var shadowRoots = (0, _react.useMemo)(function () {
return essentialRefs.reduce(function (acc, ref) {
var node = ref.current;
// Push to acc unique shadow roots
if (node !== null) {
var root = node.getRootNode();
if (root instanceof ShadowRoot && acc.indexOf(root) === -1) {
acc.push(root);
}
}
return acc;
}, []);
}, [essentialRefs]);
var isEssentialShadowRootHost = (0, _react.useCallback)(function (node) {
return node === null ? false : shadowRoots.some(function (root) {
return root.host === node;
});
}, [shadowRoots]);
var onDocumentKeyUp = (0, _useImmutableCallback.useImmutableCallback)(function (event) {
if ((0, _keyboard.isKeyCode)(event.code, _keyboard.Keys.ESC)) {
var _a = LayerRegistry.stack[LayerRegistry.stack.length - 1] || {},
layerId = _a.id,
layerOnClose = _a.onClose;
// Check id cuz we just take last item and should verify
if (layerId === id && layerOnClose !== undefined) {
layerOnClose(event, 'esc');
}
}
}, [id]);
// Remember mouse down target
var onDocumentMouseDown = (0, _useImmutableCallback.useImmutableCallback)(function (event) {
// Skip click on ShadowRoot. It will handle in next callback
if (isEssentialShadowRootHost(event.target)) return;
mouseDownRef.current = event.target;
}, [isEssentialShadowRootHost]);
var onDocumentClick = (0, _useImmutableCallback.useImmutableCallback)(function (event) {
var _a = LayerRegistry.stack[LayerRegistry.stack.length - 1] || {},
layerId = _a.id,
layerOnClose = _a.onClose,
refs = _a.essentialRefs;
// Skip click on ShadowRoot. It will handle in next callback
if (isEssentialShadowRootHost(event.target)) return;
// Check that target is same as in last mouse down event
// It need to prevent close by dragging the cursor (for example while select text)
if (mouseDownRef.current !== event.target) return;
// Check id cuz we just take last item and should verify
if (layerId === id && layerOnClose !== undefined && refs !== undefined) {
var isEssentionalClick = refs.some(function (ref) {
return ref.current !== null && ref.current instanceof HTMLElement && ref.current.contains(event.target);
});
if (!isEssentionalClick) {
layerOnClose(event, 'click');
}
}
}, [id, isEssentialShadowRootHost]);
// Toggle event handlers
(0, _react.useEffect)(function () {
// Skip invisible
if (!visible) return;
// Global events
document.addEventListener('keyup', onDocumentKeyUp);
document.addEventListener('mousedown', onDocumentMouseDown, true);
document.addEventListener('click', onDocumentClick, true);
// Events on ShadowRoot nodes
shadowRoots.forEach(function (root) {
root.addEventListener('mousedown', onDocumentMouseDown, true);
root.addEventListener('click', onDocumentClick, true);
});
return function () {
// Global events
document.removeEventListener('keyup', onDocumentKeyUp);
document.removeEventListener('mousedown', onDocumentMouseDown, true);
document.removeEventListener('click', onDocumentClick, true);
// Events on ShadowRoot nodes
shadowRoots.forEach(function (root) {
root.removeEventListener('mousedown', onDocumentMouseDown, true);
root.removeEventListener('click', onDocumentClick, true);
});
};
}, [visible, onDocumentKeyUp, onDocumentMouseDown, onDocumentClick, shadowRoots]);
// Update stack
(0, _react.useEffect)(function () {
var cleanup = function () {
return removeLayerById(id);
};
if (visible) {
LayerRegistry.stack.push({
id: id,
onClose: onClose,
essentialRefs: essentialRefs
});
} else {
cleanup();
}
return cleanup;
// must update only by change `visible`
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [visible]);
// Update callback in stack
(0, _react.useEffect)(function () {
if (onClose !== prevOnClose) {
LayerRegistry.stack.forEach(function (layer) {
if (layer.onClose === prevOnClose) {
layer.onClose = onClose;
}
});
}
}, [onClose, prevOnClose]);
return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, children);
};
exports.LayerManager = LayerManager;
LayerManager.displayName = 'LayerManager';