zent
Version:
一套前端设计语言和基于React的实现
179 lines (178 loc) • 7.46 kB
JavaScript
import { __assign } from "tslib";
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { useRef, useImperativeHandle, useMemo, forwardRef, useEffect, } from 'react';
import noop from '../utils/noop';
import MountElement from './MountElement';
import PurePortal from './PurePortal';
import { getNodeFromSelector, hasScrollbarY } from './util';
import memorize from '../utils/memorize-one';
import createElement from '../utils/dom/createElement';
import { setValueForStyles } from '../utils/style/CSSPropertyOperations';
import { addEventListener } from '../utils/component/event-handler';
import isBrowser from '../utils/isBrowser';
import { useIsomorphicLayoutEffect } from '../utils/hooks/useIsomorphicLayoutEffect';
import measureScrollbar from '../utils/dom/measureScrollbar';
function diffStyle(prev, next) {
var result = {};
var prevKeys = Object.keys(prev);
for (var i = 0; i < prevKeys.length; i += 1) {
var key = prevKeys[i];
if (!next[key]) {
result[key] = '';
}
}
var nextKeys = Object.keys(next);
for (var i = 0; i < prevKeys.length; i += 1) {
var key = nextKeys[i];
result[key] = next[key];
}
return result;
}
var patched = new Map();
function patchElement(parent) {
var meta = patched.get(parent);
if (meta) {
meta.count += 1;
}
else {
var _a = parent.style, overflowY = _a.overflowY, paddingRight = _a.paddingRight;
var originalPadding = getComputedStyle(parent).paddingRight;
var newPadding = parseFloat(originalPadding || '0') + measureScrollbar();
parent.style.overflowY = 'hidden';
parent.style.paddingRight = newPadding + "px";
var newMeta = {
count: 1,
overflowY: overflowY,
paddingRight: paddingRight,
};
patched.set(parent, newMeta);
}
}
function restoreElement(parent) {
var meta = patched.get(parent);
if (!meta) {
throw new Error('This looks like a bug of zent, please file an issue');
}
if (meta.count === 1) {
patched.delete(parent);
parent.style.overflowY = meta.overflowY;
parent.style.paddingRight = meta.paddingRight;
}
else {
meta.count -= 1;
}
}
export var Portal = forwardRef(function (props, ref) {
var _a = props.visible, visible = _a === void 0 ? true : _a, _b = props.layer, layer = _b === void 0 ? 'div' : _b, _c = props.selector, selector = _c === void 0 ? 'body' : _c, _d = props.useLayerForClickAway, useLayerForClickAway = _d === void 0 ? false : _d, className = props.className, style = props.style, _e = props.blockPageScroll, blockPageScroll = _e === void 0 ? false : _e, _f = props.closeOnESC, closeOnESC = _f === void 0 ? false : _f, _g = props.closeOnClickOutside, closeOnClickOutside = _g === void 0 ? false : _g, children = props.children, append = props.append;
var node = useMemo(function () { return (isBrowser ? createElement(layer) : null); }, [layer]);
var getParent = useMemo(function () { return memorize(getNodeFromSelector); }, []);
var propsRef = useRef(props);
propsRef.current = props;
var prevStyleRef = useRef(style);
var purePortalRef = useRef(null);
useImperativeHandle(ref, function () { return ({
contains: function (node) {
var purePortal = purePortalRef.current;
if (!purePortal) {
return false;
}
return purePortal.contains(node);
},
purePortalRef: purePortalRef,
container: node,
}); }, [node]);
useIsomorphicLayoutEffect(function () {
className && (node.className = className);
}, [node, className]);
useIsomorphicLayoutEffect(function () {
var result = diffStyle(prevStyleRef.current || {}, style || {});
setValueForStyles(node, result);
prevStyleRef.current = style;
}, [node, style]);
useIsomorphicLayoutEffect(function () {
if (!visible || !useLayerForClickAway) {
return noop;
}
var _a = node.style, position = _a.position, top = _a.top, bottom = _a.bottom, left = _a.left, right = _a.right;
var parent = getParent(selector);
node.style.position = parent === document.body ? 'fixed' : 'absolute';
node.style.top = '0';
node.style.bottom = '0';
node.style.left = '0';
node.style.right = '0';
return function () {
node.style.position = position;
node.style.top = top;
node.style.bottom = bottom;
node.style.left = left;
node.style.right = right;
};
}, [node, useLayerForClickAway, visible, selector, getParent]);
useIsomorphicLayoutEffect(function () {
var parent = getParent(selector);
if (!visible ||
!blockPageScroll ||
!(parent instanceof HTMLElement) ||
!hasScrollbarY(parent)) {
return noop;
}
patchElement(parent);
return function () { return restoreElement(parent); };
}, [selector, visible, blockPageScroll, getParent]);
useIsomorphicLayoutEffect(function () {
function handler(event) {
var _a = propsRef.current, closeOnClickOutside = _a.closeOnClickOutside, onClose = _a.onClose, visible = _a.visible;
var purePortal = purePortalRef.current;
if (event.defaultPrevented ||
!closeOnClickOutside ||
!visible ||
!purePortal) {
return;
}
var target = event.target;
if (!(target instanceof Node) ||
target === node ||
!purePortal.contains(target)) {
onClose && onClose(event);
}
}
var dispose = noop;
if (closeOnClickOutside) {
var cancelTouchStart_1;
var cancelClick_1;
if (useLayerForClickAway) {
cancelTouchStart_1 = addEventListener(node, 'touchstart', handler);
cancelClick_1 = addEventListener(node, 'click', handler);
}
else {
cancelTouchStart_1 = addEventListener(window, 'touchstart', handler);
cancelClick_1 = addEventListener(window, 'click', handler);
}
dispose = function () {
cancelClick_1();
cancelTouchStart_1();
};
}
var onLayerReady = propsRef.current.onLayerReady;
onLayerReady && onLayerReady(node);
return dispose;
}, [useLayerForClickAway, closeOnClickOutside, node]);
useEffect(function () {
if (!visible || !closeOnESC) {
return noop;
}
function onKeyUp(e) {
var onClose = propsRef.current.onClose;
if (!onClose) {
return;
}
if (e.key === 'Escape' || e.key === 'Esc' || e.keyCode === 27) {
onClose(e);
}
}
return addEventListener(document.body, 'keyup', onKeyUp);
}, [closeOnESC, visible]);
return visible && node ? (_jsxs(PurePortal, __assign({ ref: purePortalRef, append: append, selector: node }, { children: [_jsx(MountElement, { node: node, getParent: getParent, selector: selector }, void 0), children] }), void 0)) : null;
});
Portal.displayName = 'ZentPortal';
export default Portal;