This commit is contained in:
2026-05-25 17:08:18 +08:00
parent df501f6151
commit f1259b71b5
9178 changed files with 1626125 additions and 0 deletions
@@ -0,0 +1,9 @@
import * as React from 'react';
import type { AlignType, ArrowPos, ArrowTypeOuter } from '../interface';
export interface ArrowProps {
prefixCls: string;
align: AlignType;
arrow: ArrowTypeOuter;
arrowPos: ArrowPos;
}
export default function Arrow(props: ArrowProps): React.JSX.Element;
+72
View File
@@ -0,0 +1,72 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = Arrow;
var _clsx = require("clsx");
var React = _interopRequireWildcard(require("react"));
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; }
function Arrow(props) {
const {
prefixCls,
align,
arrow,
arrowPos
} = props;
const {
className,
content,
style
} = arrow || {};
const {
x = 0,
y = 0
} = arrowPos;
const arrowRef = React.useRef(null);
// Skip if no align
if (!align || !align.points) {
return null;
}
const alignStyle = {
position: 'absolute'
};
// Skip if no need to align
if (align.autoArrow !== false) {
const popupPoints = align.points[0];
const targetPoints = align.points[1];
const popupTB = popupPoints[0];
const popupLR = popupPoints[1];
const targetTB = targetPoints[0];
const targetLR = targetPoints[1];
// Top & Bottom
if (popupTB === targetTB || !['t', 'b'].includes(popupTB)) {
alignStyle.top = y;
} else if (popupTB === 't') {
alignStyle.top = 0;
} else {
alignStyle.bottom = 0;
}
// Left & Right
if (popupLR === targetLR || !['l', 'r'].includes(popupLR)) {
alignStyle.left = x;
} else if (popupLR === 'l') {
alignStyle.left = 0;
} else {
alignStyle.right = 0;
}
}
return /*#__PURE__*/React.createElement("div", {
ref: arrowRef,
className: (0, _clsx.clsx)(`${prefixCls}-arrow`, className),
style: {
...alignStyle,
...style
}
}, content);
}
+11
View File
@@ -0,0 +1,11 @@
import type { CSSMotionProps } from '@rc-component/motion';
import * as React from 'react';
export interface MaskProps {
prefixCls: string;
open?: boolean;
zIndex?: number;
mask?: boolean;
motion?: CSSMotionProps;
mobile?: boolean;
}
export default function Mask(props: MaskProps): React.JSX.Element;
+38
View File
@@ -0,0 +1,38 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = Mask;
var _clsx = require("clsx");
var _motion = _interopRequireDefault(require("@rc-component/motion"));
var React = _interopRequireWildcard(require("react"));
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; }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
function Mask(props) {
const {
prefixCls,
open,
zIndex,
mask,
motion,
mobile
} = props;
if (!mask) {
return null;
}
return /*#__PURE__*/React.createElement(_motion.default, _extends({}, motion, {
motionAppear: true,
visible: open,
removeOnLeave: true
}), ({
className
}) => /*#__PURE__*/React.createElement("div", {
style: {
zIndex
},
className: (0, _clsx.clsx)(`${prefixCls}-mask`, mobile && `${prefixCls}-mobile-mask`, className)
}));
}
@@ -0,0 +1,7 @@
import * as React from 'react';
export interface PopupContentProps {
children?: React.ReactNode;
cache?: boolean;
}
declare const PopupContent: React.MemoExoticComponent<({ children }: PopupContentProps) => React.ReactElement<any, string | React.JSXElementConstructor<any>>>;
export default PopupContent;
@@ -0,0 +1,16 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var React = _interopRequireWildcard(require("react"));
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; }
const PopupContent = /*#__PURE__*/React.memo(({
children
}) => children, (_, next) => next.cache);
if (process.env.NODE_ENV !== 'production') {
PopupContent.displayName = 'PopupContent';
}
var _default = exports.default = PopupContent;
@@ -0,0 +1,57 @@
import type { CSSMotionProps } from '@rc-component/motion';
import { type ResizeObserverProps } from '@rc-component/resize-observer';
import * as React from 'react';
import type { TriggerProps } from '../';
import type { AlignType, ArrowPos, ArrowTypeOuter } from '../interface';
import type { PortalProps } from '@rc-component/portal';
export interface MobileConfig {
mask?: boolean;
/** Set popup motion. You can ref `rc-motion` for more info. */
motion?: CSSMotionProps;
/** Set mask motion. You can ref `rc-motion` for more info. */
maskMotion?: CSSMotionProps;
}
export interface PopupProps {
onEsc?: PortalProps['onEsc'];
prefixCls: string;
className?: string;
style?: React.CSSProperties;
popup?: TriggerProps['popup'];
target: HTMLElement;
onMouseEnter?: React.MouseEventHandler<HTMLDivElement>;
onMouseLeave?: React.MouseEventHandler<HTMLDivElement>;
onPointerEnter?: React.MouseEventHandler<HTMLDivElement>;
onPointerDownCapture?: React.MouseEventHandler<HTMLDivElement>;
zIndex?: number;
mask?: boolean;
onVisibleChanged: (visible: boolean) => void;
align?: AlignType;
arrow?: ArrowTypeOuter;
arrowPos: ArrowPos;
open: boolean;
/** Tell Portal that should keep in screen. e.g. should wait all motion end */
keepDom: boolean;
fresh?: boolean;
onClick?: React.MouseEventHandler<HTMLDivElement>;
motion?: CSSMotionProps;
maskMotion?: CSSMotionProps;
forceRender?: boolean;
getPopupContainer?: TriggerProps['getPopupContainer'];
autoDestroy?: boolean;
portal: React.ComponentType<any>;
children?: React.ReactElement;
ready: boolean;
offsetX: number;
offsetY: number;
offsetR: number;
offsetB: number;
onAlign: VoidFunction;
onPrepare: () => Promise<void>;
stretch?: string;
targetWidth?: number;
targetHeight?: number;
onResize?: ResizeObserverProps['onResize'];
mobile?: MobileConfig;
}
declare const Popup: React.ForwardRefExoticComponent<PopupProps & React.RefAttributes<HTMLDivElement>>;
export default Popup;
+200
View File
@@ -0,0 +1,200 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _clsx = require("clsx");
var _motion = _interopRequireDefault(require("@rc-component/motion"));
var _resizeObserver = _interopRequireDefault(require("@rc-component/resize-observer"));
var _useLayoutEffect = _interopRequireDefault(require("@rc-component/util/lib/hooks/useLayoutEffect"));
var _ref = require("@rc-component/util/lib/ref");
var React = _interopRequireWildcard(require("react"));
var _Arrow = _interopRequireDefault(require("./Arrow"));
var _Mask = _interopRequireDefault(require("./Mask"));
var _PopupContent = _interopRequireDefault(require("./PopupContent"));
var _useOffsetStyle = _interopRequireDefault(require("../hooks/useOffsetStyle"));
var _util = require("@rc-component/util");
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; }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
const Popup = /*#__PURE__*/React.forwardRef((props, ref) => {
const {
onEsc,
popup,
className,
prefixCls,
style,
target,
onVisibleChanged,
// Open
open,
keepDom,
fresh,
// Click
onClick,
// Mask
mask,
// Arrow
arrow,
arrowPos,
align,
// Motion
motion,
maskMotion,
// Mobile
mobile,
// Portal
forceRender,
getPopupContainer,
autoDestroy,
portal: Portal,
children,
zIndex,
onMouseEnter,
onMouseLeave,
onPointerEnter,
onPointerDownCapture,
ready,
offsetX,
offsetY,
offsetR,
offsetB,
onAlign,
onPrepare,
// Resize
onResize,
stretch,
targetWidth,
targetHeight
} = props;
const popupContent = typeof popup === 'function' ? popup() : popup;
// We can not remove holder only when motion finished.
const isNodeVisible = open || keepDom;
// ========================= Mobile =========================
const isMobile = !!mobile;
// ========================== Mask ==========================
const [mergedMask, mergedMaskMotion, mergedPopupMotion] = React.useMemo(() => {
if (mobile) {
return [mobile.mask, mobile.maskMotion, mobile.motion];
}
return [mask, maskMotion, motion];
}, [mobile, mask, maskMotion, motion]);
// ======================= Container ========================
const getPopupContainerNeedParams = getPopupContainer?.length > 0;
const [show, setShow] = React.useState(!getPopupContainer || !getPopupContainerNeedParams);
// Delay to show since `getPopupContainer` need target element
(0, _useLayoutEffect.default)(() => {
if (!show && getPopupContainerNeedParams && target) {
setShow(true);
}
}, [show, getPopupContainerNeedParams, target]);
// ========================= Resize =========================
const onInternalResize = (0, _util.useEvent)((size, ele) => {
onResize?.(size, ele);
onAlign();
});
// ========================= Styles =========================
const offsetStyle = (0, _useOffsetStyle.default)(isMobile, ready, open, align, offsetR, offsetB, offsetX, offsetY);
// ========================= Render =========================
if (!show) {
return null;
}
// >>>>> Misc
const miscStyle = {};
if (stretch) {
if (stretch.includes('height') && targetHeight) {
miscStyle.height = targetHeight;
} else if (stretch.includes('minHeight') && targetHeight) {
miscStyle.minHeight = targetHeight;
}
if (stretch.includes('width') && targetWidth) {
miscStyle.width = targetWidth;
} else if (stretch.includes('minWidth') && targetWidth) {
miscStyle.minWidth = targetWidth;
}
}
if (!open) {
miscStyle.pointerEvents = 'none';
}
return /*#__PURE__*/React.createElement(Portal, {
open: forceRender || isNodeVisible,
getContainer: getPopupContainer && (() => getPopupContainer(target)),
autoDestroy: autoDestroy,
onEsc: onEsc
}, /*#__PURE__*/React.createElement(_Mask.default, {
prefixCls: prefixCls,
open: open,
zIndex: zIndex,
mask: mergedMask,
motion: mergedMaskMotion,
mobile: isMobile
}), /*#__PURE__*/React.createElement(_resizeObserver.default, {
onResize: onInternalResize,
disabled: !open
}, resizeObserverRef => {
return /*#__PURE__*/React.createElement(_motion.default, _extends({
motionAppear: true,
motionEnter: true,
motionLeave: true,
removeOnLeave: false,
forceRender: forceRender,
leavedClassName: `${prefixCls}-hidden`
}, mergedPopupMotion, {
onAppearPrepare: onPrepare,
onEnterPrepare: onPrepare,
visible: open,
onVisibleChanged: nextVisible => {
motion?.onVisibleChanged?.(nextVisible);
onVisibleChanged(nextVisible);
}
}), ({
className: motionClassName,
style: motionStyle
}, motionRef) => {
const cls = (0, _clsx.clsx)(prefixCls, motionClassName, className, {
[`${prefixCls}-mobile`]: isMobile
});
return /*#__PURE__*/React.createElement("div", {
ref: (0, _ref.composeRef)(resizeObserverRef, ref, motionRef),
className: cls,
style: {
'--arrow-x': `${arrowPos.x || 0}px`,
'--arrow-y': `${arrowPos.y || 0}px`,
...offsetStyle,
...miscStyle,
...motionStyle,
boxSizing: 'border-box',
zIndex,
...style
},
onMouseEnter: onMouseEnter,
onMouseLeave: onMouseLeave,
onPointerEnter: onPointerEnter,
onClick: onClick,
onPointerDownCapture: onPointerDownCapture
}, arrow && /*#__PURE__*/React.createElement(_Arrow.default, {
prefixCls: prefixCls,
arrow: arrow,
arrowPos: arrowPos,
align: align
}), /*#__PURE__*/React.createElement(_PopupContent.default, {
cache: !open && !fresh
}, popupContent));
});
}), children);
});
if (process.env.NODE_ENV !== 'production') {
Popup.displayName = 'Popup';
}
var _default = exports.default = Popup;
@@ -0,0 +1,24 @@
import React from 'react';
import type { CSSMotionProps } from '@rc-component/motion';
import type { AlignType, ArrowPos } from '../interface';
export interface UniqueContainerProps {
prefixCls: string;
isMobile: boolean;
ready: boolean;
open: boolean;
align: AlignType;
offsetR: number;
offsetB: number;
offsetX: number;
offsetY: number;
arrowPos?: ArrowPos;
popupSize?: {
width: number;
height: number;
};
motion?: CSSMotionProps;
uniqueContainerClassName?: string;
uniqueContainerStyle?: React.CSSProperties;
}
declare const UniqueContainer: (props: UniqueContainerProps) => React.JSX.Element;
export default UniqueContainer;
@@ -0,0 +1,83 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _react = _interopRequireDefault(require("react"));
var _useOffsetStyle = _interopRequireDefault(require("../hooks/useOffsetStyle"));
var _clsx = require("clsx");
var _motion = _interopRequireDefault(require("@rc-component/motion"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
const UniqueContainer = props => {
const {
prefixCls,
isMobile,
ready,
open,
align,
offsetR,
offsetB,
offsetX,
offsetY,
arrowPos,
popupSize,
motion,
uniqueContainerClassName,
uniqueContainerStyle
} = props;
const containerCls = `${prefixCls}-unique-container`;
const [motionVisible, setMotionVisible] = _react.default.useState(false);
// ========================= Styles =========================
const offsetStyle = (0, _useOffsetStyle.default)(isMobile, ready, open, align, offsetR, offsetB, offsetX, offsetY);
// Cache for offsetStyle when ready is true
const cachedOffsetStyleRef = _react.default.useRef(offsetStyle);
// Update cached offset style when ready is true
if (ready) {
cachedOffsetStyleRef.current = offsetStyle;
}
// Apply popup size if available
const sizeStyle = {};
if (popupSize) {
sizeStyle.width = popupSize.width;
sizeStyle.height = popupSize.height;
}
// ========================= Render =========================
return /*#__PURE__*/_react.default.createElement(_motion.default, _extends({
motionAppear: true,
motionEnter: true,
motionLeave: true,
removeOnLeave: false,
leavedClassName: `${containerCls}-hidden`
}, motion, {
visible: open,
onVisibleChanged: nextVisible => {
setMotionVisible(nextVisible);
}
}), ({
className: motionClassName,
style: motionStyle
}) => {
const cls = (0, _clsx.clsx)(containerCls, motionClassName, uniqueContainerClassName, {
[`${containerCls}-visible`]: motionVisible
});
return /*#__PURE__*/_react.default.createElement("div", {
className: cls,
style: {
'--arrow-x': `${arrowPos?.x || 0}px`,
'--arrow-y': `${arrowPos?.y || 0}px`,
...cachedOffsetStyleRef.current,
...sizeStyle,
...motionStyle,
...uniqueContainerStyle
}
});
});
};
var _default = exports.default = UniqueContainer;
@@ -0,0 +1,9 @@
import * as React from 'react';
import { type UniqueShowOptions } from '../context';
export interface UniqueProviderProps {
children: React.ReactNode;
/** Additional handle options data to do the customize info */
postTriggerProps?: (options: UniqueShowOptions) => UniqueShowOptions;
}
declare const UniqueProvider: ({ children, postTriggerProps, }: UniqueProviderProps) => React.JSX.Element;
export default UniqueProvider;
@@ -0,0 +1,184 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var React = _interopRequireWildcard(require("react"));
var _portal = _interopRequireDefault(require("@rc-component/portal"));
var _context = _interopRequireWildcard(require("../context"));
var _useDelay = _interopRequireDefault(require("../hooks/useDelay"));
var _useAlign = _interopRequireDefault(require("../hooks/useAlign"));
var _Popup = _interopRequireDefault(require("../Popup"));
var _util = require("@rc-component/util");
var _useTargetState = _interopRequireDefault(require("./useTargetState"));
var _findDOMNode = require("@rc-component/util/lib/Dom/findDOMNode");
var _UniqueContainer = _interopRequireDefault(require("./UniqueContainer"));
var _clsx = require("clsx");
var _util2 = require("../util");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
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; }
const UniqueProvider = ({
children,
postTriggerProps
}) => {
const [trigger, open, options, onTargetVisibleChanged] = (0, _useTargetState.default)();
// ========================== Options ===========================
const mergedOptions = React.useMemo(() => {
if (!options || !postTriggerProps) {
return options;
}
return postTriggerProps(options);
}, [options, postTriggerProps]);
// =========================== Popup ============================
const [popupEle, setPopupEle] = React.useState(null);
const [popupSize, setPopupSize] = React.useState(null);
// Used for forwardRef popup. Not use internal
const externalPopupRef = React.useRef(null);
const setPopupRef = (0, _util.useEvent)(node => {
externalPopupRef.current = node;
if ((0, _findDOMNode.isDOM)(node) && popupEle !== node) {
setPopupEle(node);
}
});
// ========================== Register ==========================
// Store the isOpen function from the latest show call
const isOpenRef = React.useRef(null);
const delayInvoke = (0, _useDelay.default)();
const show = (0, _util.useEvent)((showOptions, isOpen) => {
// Store the isOpen function for later use in hide
isOpenRef.current = isOpen;
delayInvoke(() => {
trigger(showOptions);
}, showOptions.delay);
});
const hide = delay => {
delayInvoke(() => {
// Check if we should still hide by calling the isOpen function
// If isOpen returns true, it means another trigger wants to keep it open
if (isOpenRef.current?.()) {
return; // Don't hide if something else wants it open
}
trigger(false);
// Don't clear target, currentNode, options immediately, wait until animation completes
}, delay);
};
// Callback after animation completes
const onVisibleChanged = (0, _util.useEvent)(visible => {
// Call useTargetState callback to handle animation state
onTargetVisibleChanged(visible);
});
// =========================== Align ============================
const [ready, offsetX, offsetY, offsetR, offsetB, arrowX, arrowY,
// scaleX - not used in UniqueProvider
,,
// scaleY - not used in UniqueProvider
alignInfo, onAlign] = (0, _useAlign.default)(open, popupEle, mergedOptions?.target, mergedOptions?.popupPlacement, mergedOptions?.builtinPlacements || {}, mergedOptions?.popupAlign, undefined,
// onPopupAlign
false // isMobile
);
const alignedClassName = React.useMemo(() => {
if (!mergedOptions) {
return '';
}
const baseClassName = (0, _util2.getAlignPopupClassName)(mergedOptions.builtinPlacements || {}, mergedOptions.prefixCls || '', alignInfo, false // alignPoint is false for UniqueProvider
);
return (0, _clsx.clsx)(baseClassName, mergedOptions.getPopupClassNameFromAlign?.(alignInfo));
}, [alignInfo, mergedOptions?.getPopupClassNameFromAlign, mergedOptions?.builtinPlacements, mergedOptions?.prefixCls]);
const contextValue = React.useMemo(() => ({
show,
hide
}), []);
// =========================== Align ============================
React.useEffect(() => {
onAlign();
}, [mergedOptions?.target]);
// =========================== Motion ===========================
const onPrepare = (0, _util.useEvent)(() => {
onAlign();
return Promise.resolve();
});
// ======================== Trigger Context =====================
const subPopupElements = React.useRef({});
const parentContext = React.useContext(_context.default);
const triggerContextValue = React.useMemo(() => ({
registerSubPopup: (id, subPopupEle) => {
subPopupElements.current[id] = subPopupEle;
parentContext?.registerSubPopup(id, subPopupEle);
}
}), [parentContext]);
// =========================== Render ===========================
const prefixCls = mergedOptions?.prefixCls;
return /*#__PURE__*/React.createElement(_context.UniqueContext.Provider, {
value: contextValue
}, children, mergedOptions && /*#__PURE__*/React.createElement(_context.default.Provider, {
value: triggerContextValue
}, /*#__PURE__*/React.createElement(_Popup.default, {
ref: setPopupRef,
portal: _portal.default,
onEsc: mergedOptions.onEsc,
prefixCls: prefixCls,
popup: mergedOptions.popup,
className: (0, _clsx.clsx)(mergedOptions.popupClassName, alignedClassName, `${prefixCls}-unique-controlled`),
style: mergedOptions.popupStyle,
target: mergedOptions.target,
open: open,
keepDom: true,
fresh: true,
autoDestroy: false,
onVisibleChanged: onVisibleChanged,
ready: ready,
offsetX: offsetX,
offsetY: offsetY,
offsetR: offsetR,
offsetB: offsetB,
onAlign: onAlign,
onPrepare: onPrepare,
onResize: size => setPopupSize({
width: size.offsetWidth,
height: size.offsetHeight
}),
arrowPos: {
x: arrowX,
y: arrowY
},
align: alignInfo,
zIndex: mergedOptions.zIndex,
mask: mergedOptions.mask,
arrow: mergedOptions.arrow,
motion: mergedOptions.popupMotion,
maskMotion: mergedOptions.maskMotion,
getPopupContainer: mergedOptions.getPopupContainer
}, /*#__PURE__*/React.createElement(_UniqueContainer.default, {
prefixCls: prefixCls,
isMobile: false,
ready: ready,
open: open,
align: alignInfo,
offsetR: offsetR,
offsetB: offsetB,
offsetX: offsetX,
offsetY: offsetY,
arrowPos: {
x: arrowX,
y: arrowY
},
popupSize: popupSize,
motion: mergedOptions.popupMotion,
uniqueContainerClassName: (0, _clsx.clsx)(mergedOptions.uniqueContainerClassName, alignedClassName),
uniqueContainerStyle: mergedOptions.uniqueContainerStyle
}))));
};
var _default = exports.default = UniqueProvider;
@@ -0,0 +1,16 @@
import type { UniqueShowOptions } from '../context';
/**
* Control the state of popup bind target:
* 1. When set `target`. Do show the popup.
* 2. When `target` is removed. Do hide the popup.
* 3. When `target` change to another one:
* a. We wait motion finish of previous popup.
* b. Then we set new target and show the popup.
* 4. During appear/enter animation, cache new options and apply after animation completes.
*/
export default function useTargetState(): [
trigger: (options: UniqueShowOptions | false) => void,
open: boolean,
cacheOptions: UniqueShowOptions | null,
onVisibleChanged: (visible: boolean) => void
];
@@ -0,0 +1,62 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = useTargetState;
var _react = _interopRequireDefault(require("react"));
var _util = require("@rc-component/util");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* Control the state of popup bind target:
* 1. When set `target`. Do show the popup.
* 2. When `target` is removed. Do hide the popup.
* 3. When `target` change to another one:
* a. We wait motion finish of previous popup.
* b. Then we set new target and show the popup.
* 4. During appear/enter animation, cache new options and apply after animation completes.
*/
function useTargetState() {
const [options, setOptions] = _react.default.useState(null);
const [open, setOpen] = _react.default.useState(false);
const [isAnimating, setIsAnimating] = _react.default.useState(false);
const pendingOptionsRef = _react.default.useRef(null);
const trigger = (0, _util.useEvent)(nextOptions => {
if (nextOptions === false) {
// Clear pending options when hiding
pendingOptionsRef.current = null;
setOpen(false);
} else {
if (isAnimating && open) {
// If animating (appear or enter), cache new options
pendingOptionsRef.current = nextOptions;
} else {
setOpen(true);
// Set new options
setOptions(nextOptions);
pendingOptionsRef.current = null;
// Only mark as animating when transitioning from closed to open
if (!open) {
setIsAnimating(true);
}
}
}
});
const onVisibleChanged = (0, _util.useEvent)(visible => {
if (visible) {
// Animation enter completed, check if there are pending options
setIsAnimating(false);
if (pendingOptionsRef.current) {
// Apply pending options
setOptions(pendingOptionsRef.current);
pendingOptionsRef.current = null;
}
} else {
// Animation leave completed
setIsAnimating(false);
pendingOptionsRef.current = null;
}
});
return [trigger, open, options, onVisibleChanged];
}
+38
View File
@@ -0,0 +1,38 @@
import * as React from 'react';
import type { CSSMotionProps } from '@rc-component/motion';
import type { PortalProps } from '@rc-component/portal';
import type { TriggerProps } from './index';
import type { AlignType, ArrowTypeOuter, BuildInPlacements } from './interface';
export interface TriggerContextProps {
registerSubPopup: (id: string, node: HTMLElement) => void;
}
declare const TriggerContext: React.Context<TriggerContextProps>;
export default TriggerContext;
export interface UniqueShowOptions {
id: string;
popup: TriggerProps['popup'];
target: HTMLElement;
delay: number;
prefixCls?: string;
popupClassName?: string;
uniqueContainerClassName?: string;
uniqueContainerStyle?: React.CSSProperties;
popupStyle?: React.CSSProperties;
popupPlacement?: string;
builtinPlacements?: BuildInPlacements;
popupAlign?: AlignType;
zIndex?: number;
mask?: boolean;
maskClosable?: boolean;
popupMotion?: CSSMotionProps;
maskMotion?: CSSMotionProps;
arrow?: ArrowTypeOuter;
getPopupContainer?: TriggerProps['getPopupContainer'];
getPopupClassNameFromAlign?: (align: AlignType) => string;
onEsc?: PortalProps['onEsc'];
}
export interface UniqueContextProps {
show: (options: UniqueShowOptions, isOpen: () => boolean) => void;
hide: (delay: number) => void;
}
export declare const UniqueContext: React.Context<UniqueContextProps>;
+14
View File
@@ -0,0 +1,14 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = exports.UniqueContext = void 0;
var React = _interopRequireWildcard(require("react"));
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; }
// ===================== Nest =====================
const TriggerContext = /*#__PURE__*/React.createContext(null);
var _default = exports.default = TriggerContext; // ==================== Unique ====================
const UniqueContext = exports.UniqueContext = /*#__PURE__*/React.createContext(null);
@@ -0,0 +1,5 @@
import type { ActionType } from '../interface';
type InternalActionType = ActionType | 'touch';
type ActionTypes = InternalActionType | InternalActionType[];
export default function useAction(action: ActionTypes, showAction?: ActionTypes, hideAction?: ActionTypes): [showAction: Set<InternalActionType>, hideAction: Set<InternalActionType>];
export {};
@@ -0,0 +1,27 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = useAction;
var React = _interopRequireWildcard(require("react"));
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; }
function toArray(val) {
return val ? Array.isArray(val) ? val : [val] : [];
}
function useAction(action, showAction, hideAction) {
return React.useMemo(() => {
const mergedShowAction = toArray(showAction ?? action);
const mergedHideAction = toArray(hideAction ?? action);
const showActionSet = new Set(mergedShowAction);
const hideActionSet = new Set(mergedHideAction);
if (showActionSet.has('hover') && !showActionSet.has('click')) {
showActionSet.add('touch');
}
if (hideActionSet.has('hover') && !hideActionSet.has('click')) {
hideActionSet.add('touch');
}
return [showActionSet, hideActionSet];
}, [action, showAction, hideAction]);
}
@@ -0,0 +1,15 @@
import type { TriggerProps } from '..';
import type { AlignType } from '../interface';
export default function useAlign(open: boolean, popupEle: HTMLElement, target: HTMLElement | [x: number, y: number], placement: string, builtinPlacements: any, popupAlign?: AlignType, onPopupAlign?: TriggerProps['onPopupAlign'], mobile?: boolean): [
ready: boolean,
offsetX: number,
offsetY: number,
offsetR: number,
offsetB: number,
arrowX: number,
arrowY: number,
scaleX: number,
scaleY: number,
align: AlignType,
onAlign: VoidFunction
];
@@ -0,0 +1,540 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = useAlign;
var _findDOMNode = require("@rc-component/util/lib/Dom/findDOMNode");
var _isVisible = _interopRequireDefault(require("@rc-component/util/lib/Dom/isVisible"));
var _useEvent = _interopRequireDefault(require("@rc-component/util/lib/hooks/useEvent"));
var _useLayoutEffect = _interopRequireDefault(require("@rc-component/util/lib/hooks/useLayoutEffect"));
var React = _interopRequireWildcard(require("react"));
var _util = require("../util");
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; }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function getUnitOffset(size, offset = 0) {
const offsetStr = `${offset}`;
const cells = offsetStr.match(/^(.*)\%$/);
if (cells) {
return size * (parseFloat(cells[1]) / 100);
}
return parseFloat(offsetStr);
}
function getNumberOffset(rect, offset) {
const [offsetX, offsetY] = offset || [];
return [getUnitOffset(rect.width, offsetX), getUnitOffset(rect.height, offsetY)];
}
function splitPoints(points = '') {
return [points[0], points[1]];
}
function getAlignPoint(rect, points) {
const topBottom = points[0];
const leftRight = points[1];
let x;
let y;
// Top & Bottom
if (topBottom === 't') {
y = rect.y;
} else if (topBottom === 'b') {
y = rect.y + rect.height;
} else {
y = rect.y + rect.height / 2;
}
// Left & Right
if (leftRight === 'l') {
x = rect.x;
} else if (leftRight === 'r') {
x = rect.x + rect.width;
} else {
x = rect.x + rect.width / 2;
}
return {
x,
y
};
}
function reversePoints(points, index) {
const reverseMap = {
t: 'b',
b: 't',
l: 'r',
r: 'l'
};
const clone = [...points];
clone[index] = reverseMap[points[index]] || 'c';
return clone;
}
function flatPoints(points) {
return points.join('');
}
function useAlign(open, popupEle, target, placement, builtinPlacements, popupAlign, onPopupAlign, mobile) {
const [offsetInfo, setOffsetInfo] = React.useState({
ready: false,
offsetX: 0,
offsetY: 0,
offsetR: 0,
offsetB: 0,
arrowX: 0,
arrowY: 0,
scaleX: 1,
scaleY: 1,
align: builtinPlacements[placement] || {}
});
const alignCountRef = React.useRef(0);
const scrollerList = React.useMemo(() => {
if (!popupEle || mobile) {
return [];
}
return (0, _util.collectScroller)(popupEle);
}, [popupEle]);
// ========================= Flip ==========================
// We will memo flip info.
// If size change to make flip, it will memo the flip info and use it in next align.
const prevFlipRef = React.useRef({});
const resetFlipCache = () => {
prevFlipRef.current = {};
};
if (!open) {
resetFlipCache();
}
// ========================= Align =========================
const onAlign = (0, _useEvent.default)(() => {
if (popupEle && target && open && !mobile) {
const popupElement = popupEle;
const doc = popupElement.ownerDocument;
const win = (0, _util.getWin)(popupElement);
const {
position: popupPosition
} = win.getComputedStyle(popupElement);
const originLeft = popupElement.style.left;
const originTop = popupElement.style.top;
const originRight = popupElement.style.right;
const originBottom = popupElement.style.bottom;
const originOverflow = popupElement.style.overflow;
// Placement
const placementInfo = {
...builtinPlacements[placement],
...popupAlign
};
// placeholder element
const placeholderElement = doc.createElement('div');
popupElement.parentElement?.appendChild(placeholderElement);
placeholderElement.style.left = `${popupElement.offsetLeft}px`;
placeholderElement.style.top = `${popupElement.offsetTop}px`;
placeholderElement.style.position = popupPosition;
placeholderElement.style.height = `${popupElement.offsetHeight}px`;
placeholderElement.style.width = `${popupElement.offsetWidth}px`;
// Reset first
popupElement.style.left = '0';
popupElement.style.top = '0';
popupElement.style.right = 'auto';
popupElement.style.bottom = 'auto';
popupElement.style.overflow = 'hidden';
// Calculate align style, we should consider `transform` case
let targetRect;
if (Array.isArray(target)) {
targetRect = {
x: target[0],
y: target[1],
width: 0,
height: 0
};
} else {
const rect = target.getBoundingClientRect();
rect.x = rect.x ?? rect.left;
rect.y = rect.y ?? rect.top;
targetRect = {
x: rect.x,
y: rect.y,
width: rect.width,
height: rect.height
};
}
const popupRect = popupElement.getBoundingClientRect();
const {
height,
width
} = win.getComputedStyle(popupElement);
popupRect.x = popupRect.x ?? popupRect.left;
popupRect.y = popupRect.y ?? popupRect.top;
const {
clientWidth,
clientHeight,
scrollWidth,
scrollHeight,
scrollTop,
scrollLeft
} = doc.documentElement;
const popupHeight = popupRect.height;
const popupWidth = popupRect.width;
const targetHeight = targetRect.height;
const targetWidth = targetRect.width;
// Get bounding of visible area
const visibleRegion = {
left: 0,
top: 0,
right: clientWidth,
bottom: clientHeight
};
const scrollRegion = {
left: -scrollLeft,
top: -scrollTop,
right: scrollWidth - scrollLeft,
bottom: scrollHeight - scrollTop
};
let {
htmlRegion
} = placementInfo;
const VISIBLE = 'visible';
const VISIBLE_FIRST = 'visibleFirst';
if (htmlRegion !== 'scroll' && htmlRegion !== VISIBLE_FIRST) {
htmlRegion = VISIBLE;
}
const isVisibleFirst = htmlRegion === VISIBLE_FIRST;
const scrollRegionArea = (0, _util.getVisibleArea)(scrollRegion, scrollerList);
const visibleRegionArea = (0, _util.getVisibleArea)(visibleRegion, scrollerList);
const visibleArea = htmlRegion === VISIBLE ? visibleRegionArea : scrollRegionArea;
// When set to `visibleFirst`,
// the check `adjust` logic will use `visibleRegion` for check first.
const adjustCheckVisibleArea = isVisibleFirst ? visibleRegionArea : visibleArea;
// Record right & bottom align data
popupElement.style.left = 'auto';
popupElement.style.top = 'auto';
popupElement.style.right = '0';
popupElement.style.bottom = '0';
const popupMirrorRect = popupElement.getBoundingClientRect();
// Reset back
popupElement.style.left = originLeft;
popupElement.style.top = originTop;
popupElement.style.right = originRight;
popupElement.style.bottom = originBottom;
popupElement.style.overflow = originOverflow;
popupElement.parentElement?.removeChild(placeholderElement);
// Calculate scale
const scaleX = (0, _util.toNum)(Math.round(popupWidth / parseFloat(width) * 1000) / 1000);
const scaleY = (0, _util.toNum)(Math.round(popupHeight / parseFloat(height) * 1000) / 1000);
// No need to align since it's not visible in view
if (scaleX === 0 || scaleY === 0 || (0, _findDOMNode.isDOM)(target) && !(0, _isVisible.default)(target)) {
return;
}
// Offset
const {
offset,
targetOffset
} = placementInfo;
let [popupOffsetX, popupOffsetY] = getNumberOffset(popupRect, offset);
const [targetOffsetX, targetOffsetY] = getNumberOffset(targetRect, targetOffset);
targetRect.x -= targetOffsetX;
targetRect.y -= targetOffsetY;
// Points
const [popupPoint, targetPoint] = placementInfo.points || [];
const targetPoints = splitPoints(targetPoint);
const popupPoints = splitPoints(popupPoint);
const targetAlignPoint = getAlignPoint(targetRect, targetPoints);
const popupAlignPoint = getAlignPoint(popupRect, popupPoints);
// Real align info may not same as origin one
const nextAlignInfo = {
...placementInfo
};
let nextPoints = [popupPoints, targetPoints];
// Next Offset
let nextOffsetX = targetAlignPoint.x - popupAlignPoint.x + popupOffsetX;
let nextOffsetY = targetAlignPoint.y - popupAlignPoint.y + popupOffsetY;
// ============== Intersection ===============
// Get area by position. Used for check if flip area is better
function getIntersectionVisibleArea(offsetX, offsetY, area = visibleArea) {
const l = popupRect.x + offsetX;
const t = popupRect.y + offsetY;
const r = l + popupWidth;
const b = t + popupHeight;
const visibleL = Math.max(l, area.left);
const visibleT = Math.max(t, area.top);
const visibleR = Math.min(r, area.right);
const visibleB = Math.min(b, area.bottom);
return Math.max(0, (visibleR - visibleL) * (visibleB - visibleT));
}
const originIntersectionVisibleArea = getIntersectionVisibleArea(nextOffsetX, nextOffsetY);
// As `visibleFirst`, we prepare this for check
const originIntersectionRecommendArea = getIntersectionVisibleArea(nextOffsetX, nextOffsetY, visibleRegionArea);
// ========================== Overflow ===========================
const targetAlignPointTL = getAlignPoint(targetRect, ['t', 'l']);
const popupAlignPointTL = getAlignPoint(popupRect, ['t', 'l']);
const targetAlignPointBR = getAlignPoint(targetRect, ['b', 'r']);
const popupAlignPointBR = getAlignPoint(popupRect, ['b', 'r']);
const overflow = placementInfo.overflow || {};
const {
adjustX,
adjustY,
shiftX,
shiftY
} = overflow;
const supportAdjust = val => {
if (typeof val === 'boolean') {
return val;
}
return val >= 0;
};
// Prepare position
let nextPopupY;
let nextPopupBottom;
let nextPopupX;
let nextPopupRight;
function syncNextPopupPosition() {
nextPopupY = popupRect.y + nextOffsetY;
nextPopupBottom = nextPopupY + popupHeight;
nextPopupX = popupRect.x + nextOffsetX;
nextPopupRight = nextPopupX + popupWidth;
}
syncNextPopupPosition();
// >>>>>>>>>> Top & Bottom
const needAdjustY = supportAdjust(adjustY);
const sameTB = popupPoints[0] === targetPoints[0];
// Bottom to Top
if (needAdjustY && popupPoints[0] === 't' && (nextPopupBottom > adjustCheckVisibleArea.bottom || prevFlipRef.current.bt)) {
let tmpNextOffsetY = nextOffsetY;
if (sameTB) {
tmpNextOffsetY -= popupHeight - targetHeight;
} else {
tmpNextOffsetY = targetAlignPointTL.y - popupAlignPointBR.y - popupOffsetY;
}
const newVisibleArea = getIntersectionVisibleArea(nextOffsetX, tmpNextOffsetY);
const newVisibleRecommendArea = getIntersectionVisibleArea(nextOffsetX, tmpNextOffsetY, visibleRegionArea);
if (
// Of course use larger one
newVisibleArea > originIntersectionVisibleArea || newVisibleArea === originIntersectionVisibleArea && (!isVisibleFirst ||
// Choose recommend one
newVisibleRecommendArea >= originIntersectionRecommendArea)) {
prevFlipRef.current.bt = true;
nextOffsetY = tmpNextOffsetY;
popupOffsetY = -popupOffsetY;
nextPoints = [reversePoints(nextPoints[0], 0), reversePoints(nextPoints[1], 0)];
} else {
prevFlipRef.current.bt = false;
}
}
// Top to Bottom
if (needAdjustY && popupPoints[0] === 'b' && (nextPopupY < adjustCheckVisibleArea.top || prevFlipRef.current.tb)) {
let tmpNextOffsetY = nextOffsetY;
if (sameTB) {
tmpNextOffsetY += popupHeight - targetHeight;
} else {
tmpNextOffsetY = targetAlignPointBR.y - popupAlignPointTL.y - popupOffsetY;
}
const newVisibleArea = getIntersectionVisibleArea(nextOffsetX, tmpNextOffsetY);
const newVisibleRecommendArea = getIntersectionVisibleArea(nextOffsetX, tmpNextOffsetY, visibleRegionArea);
if (
// Of course use larger one
newVisibleArea > originIntersectionVisibleArea || newVisibleArea === originIntersectionVisibleArea && (!isVisibleFirst ||
// Choose recommend one
newVisibleRecommendArea >= originIntersectionRecommendArea)) {
prevFlipRef.current.tb = true;
nextOffsetY = tmpNextOffsetY;
popupOffsetY = -popupOffsetY;
nextPoints = [reversePoints(nextPoints[0], 0), reversePoints(nextPoints[1], 0)];
} else {
prevFlipRef.current.tb = false;
}
}
// >>>>>>>>>> Left & Right
const needAdjustX = supportAdjust(adjustX);
// >>>>> Flip
const sameLR = popupPoints[1] === targetPoints[1];
// Right to Left
if (needAdjustX && popupPoints[1] === 'l' && (nextPopupRight > adjustCheckVisibleArea.right || prevFlipRef.current.rl)) {
let tmpNextOffsetX = nextOffsetX;
if (sameLR) {
tmpNextOffsetX -= popupWidth - targetWidth;
} else {
tmpNextOffsetX = targetAlignPointTL.x - popupAlignPointBR.x - popupOffsetX;
}
const newVisibleArea = getIntersectionVisibleArea(tmpNextOffsetX, nextOffsetY);
const newVisibleRecommendArea = getIntersectionVisibleArea(tmpNextOffsetX, nextOffsetY, visibleRegionArea);
if (
// Of course use larger one
newVisibleArea > originIntersectionVisibleArea || newVisibleArea === originIntersectionVisibleArea && (!isVisibleFirst ||
// Choose recommend one
newVisibleRecommendArea >= originIntersectionRecommendArea)) {
prevFlipRef.current.rl = true;
nextOffsetX = tmpNextOffsetX;
popupOffsetX = -popupOffsetX;
nextPoints = [reversePoints(nextPoints[0], 1), reversePoints(nextPoints[1], 1)];
} else {
prevFlipRef.current.rl = false;
}
}
// Left to Right
if (needAdjustX && popupPoints[1] === 'r' && (nextPopupX < adjustCheckVisibleArea.left || prevFlipRef.current.lr)) {
let tmpNextOffsetX = nextOffsetX;
if (sameLR) {
tmpNextOffsetX += popupWidth - targetWidth;
} else {
tmpNextOffsetX = targetAlignPointBR.x - popupAlignPointTL.x - popupOffsetX;
}
const newVisibleArea = getIntersectionVisibleArea(tmpNextOffsetX, nextOffsetY);
const newVisibleRecommendArea = getIntersectionVisibleArea(tmpNextOffsetX, nextOffsetY, visibleRegionArea);
if (
// Of course use larger one
newVisibleArea > originIntersectionVisibleArea || newVisibleArea === originIntersectionVisibleArea && (!isVisibleFirst ||
// Choose recommend one
newVisibleRecommendArea >= originIntersectionRecommendArea)) {
prevFlipRef.current.lr = true;
nextOffsetX = tmpNextOffsetX;
popupOffsetX = -popupOffsetX;
nextPoints = [reversePoints(nextPoints[0], 1), reversePoints(nextPoints[1], 1)];
} else {
prevFlipRef.current.lr = false;
}
}
nextAlignInfo.points = [flatPoints(nextPoints[0]), flatPoints(nextPoints[1])];
// ============================ Shift ============================
syncNextPopupPosition();
const numShiftX = shiftX === true ? 0 : shiftX;
if (typeof numShiftX === 'number') {
// Left
if (nextPopupX < visibleRegionArea.left) {
nextOffsetX -= nextPopupX - visibleRegionArea.left - popupOffsetX;
if (targetRect.x + targetWidth < visibleRegionArea.left + numShiftX) {
nextOffsetX += targetRect.x - visibleRegionArea.left + targetWidth - numShiftX;
}
}
// Right
if (nextPopupRight > visibleRegionArea.right) {
nextOffsetX -= nextPopupRight - visibleRegionArea.right - popupOffsetX;
if (targetRect.x > visibleRegionArea.right - numShiftX) {
nextOffsetX += targetRect.x - visibleRegionArea.right + numShiftX;
}
}
}
const numShiftY = shiftY === true ? 0 : shiftY;
if (typeof numShiftY === 'number') {
// Top
if (nextPopupY < visibleRegionArea.top) {
nextOffsetY -= nextPopupY - visibleRegionArea.top - popupOffsetY;
// When target if far away from visible area
// Stop shift
if (targetRect.y + targetHeight < visibleRegionArea.top + numShiftY) {
nextOffsetY += targetRect.y - visibleRegionArea.top + targetHeight - numShiftY;
}
}
// Bottom
if (nextPopupBottom > visibleRegionArea.bottom) {
nextOffsetY -= nextPopupBottom - visibleRegionArea.bottom - popupOffsetY;
if (targetRect.y > visibleRegionArea.bottom - numShiftY) {
nextOffsetY += targetRect.y - visibleRegionArea.bottom + numShiftY;
}
}
}
// ============================ Arrow ============================
// Arrow center align
const popupLeft = popupRect.x + nextOffsetX;
const popupRight = popupLeft + popupWidth;
const popupTop = popupRect.y + nextOffsetY;
const popupBottom = popupTop + popupHeight;
const targetLeft = targetRect.x;
const targetRight = targetLeft + targetWidth;
const targetTop = targetRect.y;
const targetBottom = targetTop + targetHeight;
/** Max left of the popup and target element */
const maxLeft = Math.max(popupLeft, targetLeft);
/** Min right of the popup and target element */
const minRight = Math.min(popupRight, targetRight);
/** The center X of popup & target cross area */
const xCenter = (maxLeft + minRight) / 2;
/** Arrow X of popup offset */
const nextArrowX = xCenter - popupLeft;
const maxTop = Math.max(popupTop, targetTop);
const minBottom = Math.min(popupBottom, targetBottom);
const yCenter = (maxTop + minBottom) / 2;
const nextArrowY = yCenter - popupTop;
onPopupAlign?.(popupEle, nextAlignInfo);
// Additional calculate right & bottom position
let offsetX4Right = popupMirrorRect.right - popupRect.x - (nextOffsetX + popupRect.width);
let offsetY4Bottom = popupMirrorRect.bottom - popupRect.y - (nextOffsetY + popupRect.height);
if (scaleX === 1) {
nextOffsetX = Math.floor(nextOffsetX);
offsetX4Right = Math.floor(offsetX4Right);
}
if (scaleY === 1) {
nextOffsetY = Math.floor(nextOffsetY);
offsetY4Bottom = Math.floor(offsetY4Bottom);
}
const nextOffsetInfo = {
ready: true,
offsetX: nextOffsetX / scaleX,
offsetY: nextOffsetY / scaleY,
offsetR: offsetX4Right / scaleX,
offsetB: offsetY4Bottom / scaleY,
arrowX: nextArrowX / scaleX,
arrowY: nextArrowY / scaleY,
scaleX,
scaleY,
align: nextAlignInfo
};
setOffsetInfo(nextOffsetInfo);
}
});
const triggerAlign = () => {
alignCountRef.current += 1;
const id = alignCountRef.current;
// Merge all align requirement into one frame
Promise.resolve().then(() => {
if (alignCountRef.current === id) {
onAlign();
}
});
};
// Reset ready status when placement & open changed
const resetReady = () => {
setOffsetInfo(ori => ({
...ori,
ready: false
}));
};
(0, _useLayoutEffect.default)(resetReady, [placement]);
(0, _useLayoutEffect.default)(() => {
if (!open) {
resetReady();
}
}, [open]);
return [offsetInfo.ready, offsetInfo.offsetX, offsetInfo.offsetY, offsetInfo.offsetR, offsetInfo.offsetB, offsetInfo.arrowX, offsetInfo.arrowY, offsetInfo.scaleX, offsetInfo.scaleY, offsetInfo.align, triggerAlign];
}
@@ -0,0 +1 @@
export default function useDelay(): (callback: VoidFunction, delay: number) => void;
@@ -0,0 +1,36 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = useDelay;
var React = _interopRequireWildcard(require("react"));
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; }
function useDelay() {
const delayRef = React.useRef(null);
const clearDelay = () => {
if (delayRef.current) {
clearTimeout(delayRef.current);
delayRef.current = null;
}
};
const delayInvoke = (callback, delay) => {
clearDelay();
if (delay === 0) {
callback();
} else {
delayRef.current = setTimeout(() => {
callback();
}, delay * 1000);
}
};
// Clean up on unmount
React.useEffect(() => {
return () => {
clearDelay();
};
}, []);
return delayInvoke;
}
@@ -0,0 +1,3 @@
import type * as React from 'react';
import type { AlignType } from '../interface';
export default function useOffsetStyle(isMobile: boolean, ready: boolean, open: boolean, align: AlignType, offsetR: number, offsetB: number, offsetX: number, offsetY: number): React.CSSProperties;
@@ -0,0 +1,41 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = useOffsetStyle;
function useOffsetStyle(isMobile, ready, open, align, offsetR, offsetB, offsetX, offsetY) {
// >>>>> Offset
const AUTO = 'auto';
const offsetStyle = isMobile ? {} : {
left: '-1000vw',
top: '-1000vh',
right: AUTO,
bottom: AUTO
};
// Set align style
if (!isMobile && (ready || !open)) {
const {
points
} = align;
const dynamicInset = align.dynamicInset || align._experimental?.dynamicInset;
const alignRight = dynamicInset && points[0][1] === 'r';
const alignBottom = dynamicInset && points[0][0] === 'b';
if (alignRight) {
offsetStyle.right = offsetR;
offsetStyle.left = AUTO;
} else {
offsetStyle.left = offsetX;
offsetStyle.right = AUTO;
}
if (alignBottom) {
offsetStyle.bottom = offsetB;
offsetStyle.top = AUTO;
} else {
offsetStyle.top = offsetY;
offsetStyle.bottom = AUTO;
}
}
return offsetStyle;
}
@@ -0,0 +1 @@
export default function useWatch(open: boolean, target: HTMLElement, popup: HTMLElement, onAlign: VoidFunction, onScroll: VoidFunction): void;
@@ -0,0 +1,42 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = useWatch;
var _useLayoutEffect = _interopRequireDefault(require("@rc-component/util/lib/hooks/useLayoutEffect"));
var _util = require("../util");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function useWatch(open, target, popup, onAlign, onScroll) {
(0, _useLayoutEffect.default)(() => {
if (open && target && popup) {
const targetElement = target;
const popupElement = popup;
const targetScrollList = (0, _util.collectScroller)(targetElement);
const popupScrollList = (0, _util.collectScroller)(popupElement);
const win = (0, _util.getWin)(popupElement);
const mergedList = new Set([win, ...targetScrollList, ...popupScrollList]);
function notifyScroll() {
onAlign();
onScroll();
}
mergedList.forEach(scroller => {
scroller.addEventListener('scroll', notifyScroll, {
passive: true
});
});
win.addEventListener('resize', notifyScroll, {
passive: true
});
// First time always do align
onAlign();
return () => {
mergedList.forEach(scroller => {
scroller.removeEventListener('scroll', notifyScroll);
win.removeEventListener('resize', notifyScroll);
});
};
}
}, [open, target, popup]);
}
@@ -0,0 +1,5 @@
/**
* Close if click on the window.
* Return the function that click on the Popup element.
*/
export default function useWinClick(open: boolean, clickToHide: boolean, targetEle: HTMLElement, popupEle: HTMLElement, mask: boolean, maskClosable: boolean, inPopupOrChild: (target: EventTarget) => boolean, triggerOpen: (open: boolean) => void): () => void;
@@ -0,0 +1,66 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = useWinClick;
var _shadow = require("@rc-component/util/lib/Dom/shadow");
var _warning = require("@rc-component/util/lib/warning");
var React = _interopRequireWildcard(require("react"));
var _util = require("../util");
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; }
/**
* Close if click on the window.
* Return the function that click on the Popup element.
*/
function useWinClick(open, clickToHide, targetEle, popupEle, mask, maskClosable, inPopupOrChild, triggerOpen) {
const openRef = React.useRef(open);
openRef.current = open;
const popupPointerDownRef = React.useRef(false);
// Click to hide is special action since click popup element should not hide
React.useEffect(() => {
if (clickToHide && popupEle && (!mask || maskClosable)) {
const onPointerDown = () => {
popupPointerDownRef.current = false;
};
const onTriggerClose = e => {
if (openRef.current && !inPopupOrChild(e.composedPath?.()?.[0] || e.target) && !popupPointerDownRef.current) {
triggerOpen(false);
}
};
const win = (0, _util.getWin)(popupEle);
win.addEventListener('pointerdown', onPointerDown, true);
win.addEventListener('mousedown', onTriggerClose, true);
win.addEventListener('contextmenu', onTriggerClose, true);
// shadow root
const targetShadowRoot = (0, _shadow.getShadowRoot)(targetEle);
if (targetShadowRoot) {
targetShadowRoot.addEventListener('mousedown', onTriggerClose, true);
targetShadowRoot.addEventListener('contextmenu', onTriggerClose, true);
}
// Warning if target and popup not in same root
if (process.env.NODE_ENV !== 'production' && targetEle) {
const targetRoot = targetEle.getRootNode?.();
const popupRoot = popupEle.getRootNode?.();
(0, _warning.warning)(targetRoot === popupRoot, `trigger element and popup element should in same shadow root.`);
}
return () => {
win.removeEventListener('pointerdown', onPointerDown, true);
win.removeEventListener('mousedown', onTriggerClose, true);
win.removeEventListener('contextmenu', onTriggerClose, true);
if (targetShadowRoot) {
targetShadowRoot.removeEventListener('mousedown', onTriggerClose, true);
targetShadowRoot.removeEventListener('contextmenu', onTriggerClose, true);
}
};
}
}, [clickToHide, targetEle, popupEle, mask, maskClosable]);
function onPopupPointerDown() {
popupPointerDownRef.current = true;
}
return onPopupPointerDown;
}
+80
View File
@@ -0,0 +1,80 @@
import type { CSSMotionProps } from '@rc-component/motion';
import * as React from 'react';
import { type MobileConfig } from './Popup';
import type { ActionType, AlignType, ArrowTypeOuter, BuildInPlacements } from './interface';
export type { ActionType, AlignType, ArrowTypeOuter as ArrowType, BuildInPlacements, };
import UniqueProvider, { type UniqueProviderProps } from './UniqueProvider';
export { UniqueProvider };
export type { UniqueProviderProps };
export interface TriggerRef {
nativeElement: HTMLElement;
popupElement: HTMLDivElement;
forceAlign: VoidFunction;
}
export interface TriggerProps {
children: React.ReactElement<any> | ((info: {
open: boolean;
}) => React.ReactElement<any>);
action?: ActionType | ActionType[];
showAction?: ActionType[];
hideAction?: ActionType[];
prefixCls?: string;
zIndex?: number;
onPopupAlign?: (element: HTMLElement, align: AlignType) => void;
stretch?: string;
popupVisible?: boolean;
defaultPopupVisible?: boolean;
onOpenChange?: (visible: boolean) => void;
afterOpenChange?: (visible: boolean) => void;
/** @deprecated Use `onOpenChange` instead */
onPopupVisibleChange?: (visible: boolean) => void;
/** @deprecated Use `afterOpenChange` instead */
afterPopupVisibleChange?: (visible: boolean) => void;
getPopupContainer?: (node: HTMLElement) => HTMLElement;
forceRender?: boolean;
autoDestroy?: boolean;
mask?: boolean;
maskClosable?: boolean;
/** Set popup motion. You can ref `rc-motion` for more info. */
popupMotion?: CSSMotionProps;
/** Set mask motion. You can ref `rc-motion` for more info. */
maskMotion?: CSSMotionProps;
mouseEnterDelay?: number;
mouseLeaveDelay?: number;
focusDelay?: number;
blurDelay?: number;
popup: React.ReactNode | (() => React.ReactNode);
popupPlacement?: string;
builtinPlacements?: BuildInPlacements;
popupAlign?: AlignType;
popupClassName?: string;
/** Pass to `UniqueProvider` UniqueContainer */
uniqueContainerClassName?: string;
/** Pass to `UniqueProvider` UniqueContainer */
uniqueContainerStyle?: React.CSSProperties;
popupStyle?: React.CSSProperties;
getPopupClassNameFromAlign?: (align: AlignType) => string;
onPopupClick?: React.MouseEventHandler<HTMLDivElement>;
alignPoint?: boolean;
/**
* Trigger will memo content when close.
* This may affect the case if want to keep content update.
* Set `fresh` to `false` will always keep update.
*/
fresh?: boolean;
/**
* Config with UniqueProvider to shared the floating popup.
*/
unique?: boolean;
arrow?: boolean | ArrowTypeOuter;
/**
* @private Bump fixed position at bottom in mobile.
* Will replace the config of root props.
* This will directly trade as mobile view which will not check what real is.
* This is internal usage currently, do not use in your prod.
*/
mobile?: MobileConfig;
}
export declare function generateTrigger(PortalComponent?: React.ComponentType<any>): React.ForwardRefExoticComponent<TriggerProps & React.RefAttributes<TriggerRef>>;
declare const _default: React.ForwardRefExoticComponent<TriggerProps & React.RefAttributes<TriggerRef>>;
export default _default;
+576
View File
@@ -0,0 +1,576 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "UniqueProvider", {
enumerable: true,
get: function () {
return _UniqueProvider.default;
}
});
exports.default = void 0;
exports.generateTrigger = generateTrigger;
var _portal = _interopRequireDefault(require("@rc-component/portal"));
var _clsx = require("clsx");
var _resizeObserver = require("@rc-component/resize-observer");
var _findDOMNode = require("@rc-component/util/lib/Dom/findDOMNode");
var _shadow = require("@rc-component/util/lib/Dom/shadow");
var _ref = require("@rc-component/util/lib/ref");
var _useEvent = _interopRequireDefault(require("@rc-component/util/lib/hooks/useEvent"));
var _useId = _interopRequireDefault(require("@rc-component/util/lib/hooks/useId"));
var _useLayoutEffect = _interopRequireDefault(require("@rc-component/util/lib/hooks/useLayoutEffect"));
var React = _interopRequireWildcard(require("react"));
var _Popup = _interopRequireDefault(require("./Popup"));
var _context = _interopRequireWildcard(require("./context"));
var _useAction = _interopRequireDefault(require("./hooks/useAction"));
var _useAlign = _interopRequireDefault(require("./hooks/useAlign"));
var _useDelay = _interopRequireDefault(require("./hooks/useDelay"));
var _useWatch = _interopRequireDefault(require("./hooks/useWatch"));
var _useWinClick = _interopRequireDefault(require("./hooks/useWinClick"));
var _util = require("./util");
var _UniqueProvider = _interopRequireDefault(require("./UniqueProvider"));
var _util2 = require("@rc-component/util");
var _reactDom = require("react-dom");
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; }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
// Removed Props List
// Seems this can be auto
// getDocument?: (element?: HTMLElement) => Document;
// New version will not wrap popup with `rc-trigger-popup-content` when multiple children
function generateTrigger(PortalComponent = _portal.default) {
const Trigger = /*#__PURE__*/React.forwardRef((props, ref) => {
const {
prefixCls = 'rc-trigger-popup',
children,
// Action
action = 'hover',
showAction,
hideAction,
// Open
popupVisible,
defaultPopupVisible,
onOpenChange,
afterOpenChange,
onPopupVisibleChange,
afterPopupVisibleChange,
// Delay
mouseEnterDelay,
mouseLeaveDelay = 0.1,
focusDelay,
blurDelay,
// Mask
mask,
maskClosable = true,
// Portal
getPopupContainer,
forceRender,
autoDestroy,
// Popup
popup,
popupClassName,
uniqueContainerClassName,
uniqueContainerStyle,
popupStyle,
popupPlacement,
builtinPlacements = {},
popupAlign,
zIndex,
stretch,
getPopupClassNameFromAlign,
fresh,
unique,
alignPoint,
onPopupClick,
onPopupAlign,
// Arrow
arrow,
// Motion
popupMotion,
maskMotion,
// Private
mobile,
...restProps
} = props;
const mergedAutoDestroy = autoDestroy || false;
const openUncontrolled = popupVisible === undefined;
// =========================== Mobile ===========================
const isMobile = !!mobile;
// ========================== Context ===========================
const subPopupElements = React.useRef({});
const parentContext = React.useContext(_context.default);
const context = React.useMemo(() => {
return {
registerSubPopup: (id, subPopupEle) => {
subPopupElements.current[id] = subPopupEle;
parentContext?.registerSubPopup(id, subPopupEle);
}
};
}, [parentContext]);
// ======================== UniqueContext =========================
const uniqueContext = React.useContext(_context.UniqueContext);
// =========================== Popup ============================
const id = (0, _useId.default)();
const [popupEle, setPopupEle] = React.useState(null);
// Used for forwardRef popup. Not use internal
const externalPopupRef = React.useRef(null);
const setPopupRef = (0, _useEvent.default)(node => {
externalPopupRef.current = node;
if ((0, _findDOMNode.isDOM)(node) && popupEle !== node) {
setPopupEle(node);
}
parentContext?.registerSubPopup(id, node);
});
// =========================== Target ===========================
// Use state to control here since `useRef` update not trigger render
const [targetEle, setTargetEle] = React.useState(null);
// Used for forwardRef target. Not use internal
const externalForwardRef = React.useRef(null);
const setTargetRef = (0, _useEvent.default)(node => {
const domNode = (0, _findDOMNode.getDOM)(node);
if ((0, _findDOMNode.isDOM)(domNode) && targetEle !== domNode) {
setTargetEle(domNode);
externalForwardRef.current = domNode;
}
});
const cloneProps = {};
const inPopupOrChild = (0, _useEvent.default)(ele => {
const childDOM = targetEle;
return childDOM?.contains(ele) || (0, _shadow.getShadowRoot)(childDOM)?.host === ele || ele === childDOM || popupEle?.contains(ele) || (0, _shadow.getShadowRoot)(popupEle)?.host === ele || ele === popupEle || Object.values(subPopupElements.current).some(subPopupEle => subPopupEle?.contains(ele) || ele === subPopupEle);
});
// =========================== Arrow ============================
const innerArrow = arrow ? {
// true and Object likely
...(arrow !== true ? arrow : {})
} : null;
// ============================ Open ============================
const [internalOpen, setInternalOpen] = (0, _util2.useControlledState)(defaultPopupVisible || false, popupVisible);
const mergedOpen = internalOpen || false;
// ========================== Children ==========================
const child = React.useMemo(() => {
const nextChild = typeof children === 'function' ? children({
open: mergedOpen
}) : children;
return React.Children.only(nextChild);
}, [children, mergedOpen]);
const originChildProps = child?.props || {};
// Support ref
const isOpen = (0, _useEvent.default)(() => mergedOpen);
// Extract common options for UniqueProvider
const getUniqueOptions = (0, _useEvent.default)((delay = 0) => ({
popup,
target: targetEle,
delay,
prefixCls,
popupClassName,
uniqueContainerClassName,
uniqueContainerStyle,
popupStyle,
popupPlacement,
builtinPlacements,
popupAlign,
zIndex,
mask,
maskClosable,
popupMotion,
maskMotion,
arrow: innerArrow,
getPopupContainer,
getPopupClassNameFromAlign,
id,
onEsc
}));
// Handle controlled state changes for UniqueProvider
// Only sync to UniqueProvider when it's controlled mode
// If there is a parentContext, don't call uniqueContext methods
(0, _useLayoutEffect.default)(() => {
if (uniqueContext && unique && targetEle && !openUncontrolled && !parentContext) {
if (mergedOpen) {
uniqueContext.show(getUniqueOptions(mouseEnterDelay), isOpen);
} else {
uniqueContext.hide(mouseLeaveDelay);
}
}
}, [mergedOpen, targetEle]);
const openRef = React.useRef(mergedOpen);
openRef.current = mergedOpen;
const internalTriggerOpen = (0, _useEvent.default)(nextOpen => {
(0, _reactDom.flushSync)(() => {
if (mergedOpen !== nextOpen) {
setInternalOpen(nextOpen);
onOpenChange?.(nextOpen);
onPopupVisibleChange?.(nextOpen);
}
});
});
// Trigger for delay
const delayInvoke = (0, _useDelay.default)();
const triggerOpen = (nextOpen, delay = 0) => {
// If it's controlled mode, always use internal trigger logic
// UniqueProvider will be synced through useLayoutEffect
if (popupVisible !== undefined) {
delayInvoke(() => {
internalTriggerOpen(nextOpen);
}, delay);
return;
}
// If UniqueContext exists and not controlled, pass delay to Provider instead of handling it internally
// If there is a parentContext, don't call uniqueContext methods
if (uniqueContext && unique && openUncontrolled && !parentContext) {
if (nextOpen) {
uniqueContext.show(getUniqueOptions(delay), isOpen);
} else {
uniqueContext.hide(delay);
}
return;
}
delayInvoke(() => {
internalTriggerOpen(nextOpen);
}, delay);
};
function onEsc({
top
}) {
if (top) {
triggerOpen(false);
}
}
// ========================== Motion ============================
const [inMotion, setInMotion] = React.useState(false);
(0, _useLayoutEffect.default)(firstMount => {
if (!firstMount || mergedOpen) {
setInMotion(true);
}
}, [mergedOpen]);
const [motionPrepareResolve, setMotionPrepareResolve] = React.useState(null);
// =========================== Align ============================
const [mousePos, setMousePos] = React.useState(null);
const setMousePosByEvent = event => {
setMousePos([event.clientX, event.clientY]);
};
const [ready, offsetX, offsetY, offsetR, offsetB, arrowX, arrowY, scaleX, scaleY, alignInfo, onAlign] = (0, _useAlign.default)(mergedOpen, popupEle, alignPoint && mousePos !== null ? mousePos : targetEle, popupPlacement, builtinPlacements, popupAlign, onPopupAlign, isMobile);
const [showActions, hideActions] = (0, _useAction.default)(action, showAction, hideAction);
const clickToShow = showActions.has('click');
const clickToHide = hideActions.has('click') || hideActions.has('contextMenu');
const triggerAlign = (0, _useEvent.default)(() => {
if (!inMotion) {
onAlign();
}
});
const onScroll = () => {
if (openRef.current && alignPoint && clickToHide) {
triggerOpen(false);
}
};
(0, _useWatch.default)(mergedOpen, targetEle, popupEle, triggerAlign, onScroll);
(0, _useLayoutEffect.default)(() => {
triggerAlign();
}, [mousePos, popupPlacement]);
// When no builtinPlacements and popupAlign changed
(0, _useLayoutEffect.default)(() => {
if (mergedOpen && !builtinPlacements?.[popupPlacement]) {
triggerAlign();
}
}, [JSON.stringify(popupAlign)]);
const alignedClassName = React.useMemo(() => {
const baseClassName = (0, _util.getAlignPopupClassName)(builtinPlacements, prefixCls, alignInfo, alignPoint);
return (0, _clsx.clsx)(baseClassName, getPopupClassNameFromAlign?.(alignInfo));
}, [alignInfo, getPopupClassNameFromAlign, builtinPlacements, prefixCls, alignPoint]);
// ============================ Refs ============================
React.useImperativeHandle(ref, () => ({
nativeElement: externalForwardRef.current,
popupElement: externalPopupRef.current,
forceAlign: triggerAlign
}));
// ========================== Stretch ===========================
const [targetWidth, setTargetWidth] = React.useState(0);
const [targetHeight, setTargetHeight] = React.useState(0);
const syncTargetSize = () => {
if (stretch && targetEle) {
const rect = targetEle.getBoundingClientRect();
setTargetWidth(rect.width);
setTargetHeight(rect.height);
}
};
const onTargetResize = () => {
syncTargetSize();
triggerAlign();
};
// ========================== Motion ============================
const onVisibleChanged = visible => {
setInMotion(false);
onAlign();
afterOpenChange?.(visible);
afterPopupVisibleChange?.(visible);
};
// We will trigger align when motion is in prepare
const onPrepare = () => new Promise(resolve => {
syncTargetSize();
setMotionPrepareResolve(() => resolve);
});
(0, _useLayoutEffect.default)(() => {
if (motionPrepareResolve) {
onAlign();
motionPrepareResolve();
setMotionPrepareResolve(null);
}
}, [motionPrepareResolve]);
// =========================== Action ===========================
/**
* Util wrapper for trigger action
* @param eventName Listen event name
* @param nextOpen Next open state after trigger
* @param delay Delay to trigger open change
* @param callback Callback if current event need additional action
* @param ignoreCheck Ignore current event if check return true
*/
function wrapperAction(eventName, nextOpen, delay, callback, ignoreCheck) {
cloneProps[eventName] = (event, ...args) => {
if (!ignoreCheck || !ignoreCheck()) {
callback?.(event);
triggerOpen(nextOpen, delay);
}
// Pass to origin
originChildProps[eventName]?.(event, ...args);
};
}
// ======================= Action: Touch ========================
const touchToShow = showActions.has('touch');
const touchToHide = hideActions.has('touch');
/** Used for prevent `hover` event conflict with mobile env */
const touchedRef = React.useRef(false);
if (touchToShow || touchToHide) {
cloneProps.onTouchStart = (...args) => {
touchedRef.current = true;
if (openRef.current && touchToHide) {
triggerOpen(false);
} else if (!openRef.current && touchToShow) {
triggerOpen(true);
}
// Pass to origin
originChildProps.onTouchStart?.(...args);
};
}
// ======================= Action: Click ========================
if (clickToShow || clickToHide) {
cloneProps.onClick = (event, ...args) => {
if (openRef.current && clickToHide) {
triggerOpen(false);
} else if (!openRef.current && clickToShow) {
setMousePosByEvent(event);
triggerOpen(true);
}
// Pass to origin
originChildProps.onClick?.(event, ...args);
touchedRef.current = false;
};
}
// Click to hide is special action since click popup element should not hide
const onPopupPointerDown = (0, _useWinClick.default)(mergedOpen, clickToHide || touchToHide, targetEle, popupEle, mask, maskClosable, inPopupOrChild, triggerOpen);
// ======================= Action: Hover ========================
const hoverToShow = showActions.has('hover');
const hoverToHide = hideActions.has('hover');
let onPopupMouseEnter;
let onPopupMouseLeave;
const ignoreMouseTrigger = () => {
return touchedRef.current;
};
if (hoverToShow) {
const onMouseEnterCallback = event => {
setMousePosByEvent(event);
};
// Compatible with old browser which not support pointer event
wrapperAction('onMouseEnter', true, mouseEnterDelay, onMouseEnterCallback, ignoreMouseTrigger);
wrapperAction('onPointerEnter', true, mouseEnterDelay, onMouseEnterCallback, ignoreMouseTrigger);
onPopupMouseEnter = event => {
// Only trigger re-open when popup is visible
if ((mergedOpen || inMotion) && popupEle?.contains(event.target)) {
triggerOpen(true, mouseEnterDelay);
}
};
// Align Point
if (alignPoint) {
cloneProps.onMouseMove = event => {
originChildProps.onMouseMove?.(event);
};
}
}
if (hoverToHide) {
wrapperAction('onMouseLeave', false, mouseLeaveDelay, undefined, ignoreMouseTrigger);
wrapperAction('onPointerLeave', false, mouseLeaveDelay, undefined, ignoreMouseTrigger);
onPopupMouseLeave = () => {
triggerOpen(false, mouseLeaveDelay);
};
}
// ======================= Action: Focus ========================
if (showActions.has('focus')) {
wrapperAction('onFocus', true, focusDelay);
}
if (hideActions.has('focus')) {
wrapperAction('onBlur', false, blurDelay);
}
// ==================== Action: ContextMenu =====================
if (showActions.has('contextMenu')) {
cloneProps.onContextMenu = (event, ...args) => {
if (openRef.current && hideActions.has('contextMenu')) {
triggerOpen(false);
} else {
setMousePosByEvent(event);
triggerOpen(true);
}
event.preventDefault();
// Pass to origin
originChildProps.onContextMenu?.(event, ...args);
};
}
// ============================ Perf ============================
const rendedRef = React.useRef(false);
rendedRef.current ||= forceRender || mergedOpen || inMotion;
// =========================== Render ===========================
const mergedChildrenProps = {
...originChildProps,
...cloneProps
};
// Pass props into cloneProps for nest usage
const passedProps = {};
const passedEventList = ['onContextMenu', 'onClick', 'onMouseDown', 'onTouchStart', 'onMouseEnter', 'onMouseLeave', 'onFocus', 'onBlur'];
passedEventList.forEach(eventName => {
if (restProps[eventName]) {
passedProps[eventName] = (...args) => {
mergedChildrenProps[eventName]?.(...args);
restProps[eventName](...args);
};
}
});
const arrowPos = {
x: arrowX,
y: arrowY
};
// =================== Resize Observer ===================
// Use hook to observe target element resize
// Pass targetEle directly instead of a function so the hook will re-observe when target changes
(0, _resizeObserver.useResizeObserver)(mergedOpen, targetEle, onTargetResize);
// Compose refs
const mergedRef = (0, _ref.useComposeRef)(setTargetRef, (0, _ref.getNodeRef)(child));
// Child Node
const triggerNode = /*#__PURE__*/React.cloneElement(child, {
...mergedChildrenProps,
...passedProps,
ref: mergedRef
});
// Render
return /*#__PURE__*/React.createElement(React.Fragment, null, triggerNode, rendedRef.current && (!uniqueContext || !unique) && /*#__PURE__*/React.createElement(_context.default.Provider, {
value: context
}, /*#__PURE__*/React.createElement(_Popup.default, {
portal: PortalComponent,
ref: setPopupRef,
prefixCls: prefixCls,
popup: popup,
className: (0, _clsx.clsx)(popupClassName, !isMobile && alignedClassName),
style: popupStyle,
target: targetEle,
onMouseEnter: onPopupMouseEnter,
onMouseLeave: onPopupMouseLeave
// https://github.com/ant-design/ant-design/issues/43924
,
onPointerEnter: onPopupMouseEnter,
zIndex: zIndex
// Open
,
open: mergedOpen,
keepDom: inMotion,
fresh: fresh
// Click
,
onClick: onPopupClick,
onPointerDownCapture: onPopupPointerDown
// Mask
,
mask: mask
// Motion
,
motion: popupMotion,
maskMotion: maskMotion,
onVisibleChanged: onVisibleChanged,
onPrepare: onPrepare
// Portal
,
forceRender: forceRender,
autoDestroy: mergedAutoDestroy,
getPopupContainer: getPopupContainer,
onEsc: onEsc
// Arrow
,
align: alignInfo,
arrow: innerArrow,
arrowPos: arrowPos
// Align
,
ready: ready,
offsetX: offsetX,
offsetY: offsetY,
offsetR: offsetR,
offsetB: offsetB,
onAlign: triggerAlign
// Stretch
,
stretch: stretch,
targetWidth: targetWidth / scaleX,
targetHeight: targetHeight / scaleY
// Mobile
,
mobile: mobile
})));
});
if (process.env.NODE_ENV !== 'production') {
Trigger.displayName = 'Trigger';
}
return Trigger;
}
var _default = exports.default = generateTrigger(_portal.default);
+92
View File
@@ -0,0 +1,92 @@
/// <reference types="react" />
export type Placement = 'top' | 'left' | 'right' | 'bottom' | 'topLeft' | 'topRight' | 'bottomLeft' | 'bottomRight' | 'leftTop' | 'leftBottom' | 'rightTop' | 'rightBottom';
export type AlignPointTopBottom = 't' | 'b' | 'c';
export type AlignPointLeftRight = 'l' | 'r' | 'c';
/** Two char of 't' 'b' 'c' 'l' 'r'. Example: 'lt' */
export type AlignPoint = `${AlignPointTopBottom}${AlignPointLeftRight}`;
export type OffsetType = number | `${number}%`;
export interface AlignType {
/**
* move point of source node to align with point of target node.
* Such as ['tr','cc'], align top right point of source node with center point of target node.
* Point can be 't'(top), 'b'(bottom), 'c'(center), 'l'(left), 'r'(right) */
points?: (string | AlignPoint)[];
/**
* @private Do not use in your production code
*/
_experimental?: Record<string, any>;
/**
* offset source node by offset[0] in x and offset[1] in y.
* If offset contains percentage string value, it is relative to sourceNode region.
*/
offset?: OffsetType[];
/**
* offset target node by offset[0] in x and offset[1] in y.
* If targetOffset contains percentage string value, it is relative to targetNode region.
*/
targetOffset?: OffsetType[];
/**
* If adjustX field is true, will adjust source node in x direction if source node is invisible.
* If adjustY field is true, will adjust source node in y direction if source node is invisible.
*/
overflow?: {
adjustX?: boolean | number;
adjustY?: boolean | number;
shiftX?: boolean | number;
shiftY?: boolean | number;
};
/** Auto adjust arrow position */
autoArrow?: boolean;
/**
* Config visible region check of html node. Default `visible`:
* - `visible`:
* The visible region of user browser window.
* Use `clientHeight` for check.
* If `visible` region not satisfy, fallback to `scroll`.
* - `scroll`:
* The whole region of the html scroll area.
* Use `scrollHeight` for check.
* - `visibleFirst`:
* Similar to `visible`, but if `visible` region not satisfy, fallback to `scroll`.
*/
htmlRegion?: 'visible' | 'scroll' | 'visibleFirst';
/**
* Auto chose position with `top` or `bottom` by the align result
*/
dynamicInset?: boolean;
/**
* Whether use css right instead of left to position
*/
useCssRight?: boolean;
/**
* Whether use css bottom instead of top to position
*/
useCssBottom?: boolean;
/**
* Whether use css transform instead of left/top/right/bottom to position if browser supports.
* Defaults to false.
*/
useCssTransform?: boolean;
ignoreShake?: boolean;
}
export interface ArrowTypeOuter {
style?: React.CSSProperties;
className?: string;
content?: React.ReactNode;
}
export type ArrowPos = {
x?: number;
y?: number;
};
export type BuildInPlacements = Record<string, AlignType>;
export type StretchType = string;
export type ActionType = 'hover' | 'focus' | 'click' | 'contextMenu';
export type AnimationType = string;
export type TransitionNameType = string;
export interface Point {
pageX: number;
pageY: number;
}
export interface CommonEventHandler {
remove: () => void;
}
+5
View File
@@ -0,0 +1,5 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
+5
View File
@@ -0,0 +1,5 @@
import * as React from 'react';
import { UniqueProvider } from './index';
declare const _default: React.ForwardRefExoticComponent<import("./index").TriggerProps & React.RefAttributes<import("./index").TriggerRef>>;
export default _default;
export { UniqueProvider };
+36
View File
@@ -0,0 +1,36 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "UniqueProvider", {
enumerable: true,
get: function () {
return _index.UniqueProvider;
}
});
exports.default = void 0;
var React = _interopRequireWildcard(require("react"));
var _index = require("./index");
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; }
const MockPortal = ({
open,
autoDestroy,
children,
getContainer
}) => {
const [visible, setVisible] = React.useState(open);
React.useEffect(() => {
getContainer?.();
});
React.useEffect(() => {
if (open) {
setVisible(true);
} else if (autoDestroy) {
setVisible(false);
}
}, [open, autoDestroy]);
return visible ? children : null;
};
var _default = exports.default = (0, _index.generateTrigger)(MockPortal);
+44
View File
@@ -0,0 +1,44 @@
import type { AlignType, BuildInPlacements } from './interface';
export declare function getAlignPopupClassName(builtinPlacements: BuildInPlacements, prefixCls: string, align: AlignType, isAlignPoint: boolean): string;
export declare function getWin(ele: HTMLElement): Window & typeof globalThis;
/**
* Get all the scrollable parent elements of the element
* @param ele The element to be detected
* @param areaOnly Only return the parent which will cut visible area
*/
export declare function collectScroller(ele: HTMLElement): HTMLElement[];
export declare function toNum(num: number, defaultValue?: number): number;
export interface VisibleArea {
left: number;
top: number;
right: number;
bottom: number;
}
/**
*
*
* **************************************
* * Border *
* * ************************** *
* * * * * *
* * B * * S * B *
* * o * * c * o *
* * r * Content * r * r *
* * d * * o * d *
* * e * * l * e *
* * r ******************** l * r *
* * * Scroll * *
* * ************************** *
* * Border *
* **************************************
*
*/
/**
* Get visible area of element
*/
export declare function getVisibleArea(initArea: VisibleArea, scrollerList?: HTMLElement[]): {
left: number;
top: number;
right: number;
bottom: number;
};
+147
View File
@@ -0,0 +1,147 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.collectScroller = collectScroller;
exports.getAlignPopupClassName = getAlignPopupClassName;
exports.getVisibleArea = getVisibleArea;
exports.getWin = getWin;
exports.toNum = toNum;
function isPointsEq(a1 = [], a2 = [], isAlignPoint) {
const getVal = (a, index) => a[index] || '';
if (isAlignPoint) {
return getVal(a1, 0) === getVal(a2, 0);
}
return getVal(a1, 0) === getVal(a2, 0) && getVal(a1, 1) === getVal(a2, 1);
}
function getAlignPopupClassName(builtinPlacements, prefixCls, align, isAlignPoint) {
const {
points
} = align;
const placements = Object.keys(builtinPlacements);
for (let i = 0; i < placements.length; i += 1) {
const placement = placements[i];
if (isPointsEq(builtinPlacements[placement]?.points, points, isAlignPoint)) {
return `${prefixCls}-placement-${placement}`;
}
}
return '';
}
function getWin(ele) {
return ele.ownerDocument.defaultView;
}
/**
* Get all the scrollable parent elements of the element
* @param ele The element to be detected
* @param areaOnly Only return the parent which will cut visible area
*/
function collectScroller(ele) {
const scrollerList = [];
let current = ele?.parentElement;
const scrollStyle = ['hidden', 'scroll', 'clip', 'auto'];
while (current) {
const {
overflowX,
overflowY,
overflow
} = getWin(current).getComputedStyle(current);
if ([overflowX, overflowY, overflow].some(o => scrollStyle.includes(o))) {
scrollerList.push(current);
}
current = current.parentElement;
}
return scrollerList;
}
function toNum(num, defaultValue = 1) {
return Number.isNaN(num) ? defaultValue : num;
}
function getPxValue(val) {
return toNum(parseFloat(val), 0);
}
/**
*
*
* **************************************
* * Border *
* * ************************** *
* * * * * *
* * B * * S * B *
* * o * * c * o *
* * r * Content * r * r *
* * d * * o * d *
* * e * * l * e *
* * r ******************** l * r *
* * * Scroll * *
* * ************************** *
* * Border *
* **************************************
*
*/
/**
* Get visible area of element
*/
function getVisibleArea(initArea, scrollerList) {
const visibleArea = {
...initArea
};
(scrollerList || []).forEach(ele => {
if (ele instanceof HTMLBodyElement || ele instanceof HTMLHtmlElement) {
return;
}
// Skip if static position which will not affect visible area
const {
overflow,
overflowClipMargin,
borderTopWidth,
borderBottomWidth,
borderLeftWidth,
borderRightWidth
} = getWin(ele).getComputedStyle(ele);
const eleRect = ele.getBoundingClientRect();
const {
offsetHeight: eleOutHeight,
clientHeight: eleInnerHeight,
offsetWidth: eleOutWidth,
clientWidth: eleInnerWidth
} = ele;
const borderTopNum = getPxValue(borderTopWidth);
const borderBottomNum = getPxValue(borderBottomWidth);
const borderLeftNum = getPxValue(borderLeftWidth);
const borderRightNum = getPxValue(borderRightWidth);
const scaleX = toNum(Math.round(eleRect.width / eleOutWidth * 1000) / 1000);
const scaleY = toNum(Math.round(eleRect.height / eleOutHeight * 1000) / 1000);
// Original visible area
const eleScrollWidth = (eleOutWidth - eleInnerWidth - borderLeftNum - borderRightNum) * scaleX;
const eleScrollHeight = (eleOutHeight - eleInnerHeight - borderTopNum - borderBottomNum) * scaleY;
// Cut border size
const scaledBorderTopWidth = borderTopNum * scaleY;
const scaledBorderBottomWidth = borderBottomNum * scaleY;
const scaledBorderLeftWidth = borderLeftNum * scaleX;
const scaledBorderRightWidth = borderRightNum * scaleX;
// Clip margin
let clipMarginWidth = 0;
let clipMarginHeight = 0;
if (overflow === 'clip') {
const clipNum = getPxValue(overflowClipMargin);
clipMarginWidth = clipNum * scaleX;
clipMarginHeight = clipNum * scaleY;
}
// Region
const eleLeft = eleRect.x + scaledBorderLeftWidth - clipMarginWidth;
const eleTop = eleRect.y + scaledBorderTopWidth - clipMarginHeight;
const eleRight = eleLeft + eleRect.width + 2 * clipMarginWidth - scaledBorderLeftWidth - scaledBorderRightWidth - eleScrollWidth;
const eleBottom = eleTop + eleRect.height + 2 * clipMarginHeight - scaledBorderTopWidth - scaledBorderBottomWidth - eleScrollHeight;
visibleArea.left = Math.max(visibleArea.left, eleLeft);
visibleArea.top = Math.max(visibleArea.top, eleTop);
visibleArea.right = Math.min(visibleArea.right, eleRight);
visibleArea.bottom = Math.min(visibleArea.bottom, eleBottom);
});
return visibleArea;
}