UNPKG

zent

Version:

一套前端设计语言和基于React的实现

179 lines (178 loc) 7.46 kB
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;