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
+10
View File
@@ -0,0 +1,10 @@
export * from './useClosable';
export * from './useForceUpdate';
export * from './useMergedMask';
export * from './useMergeSemantic';
export * from './useMultipleSelect';
export * from './useOrientation';
export * from './usePatchElement';
export * from './useProxyImperativeHandle';
export * from './useSyncState';
export * from './useZIndex';
+10
View File
@@ -0,0 +1,10 @@
export * from './useClosable';
export * from './useForceUpdate';
export * from './useMergedMask';
export * from './useMergeSemantic';
export * from './useMultipleSelect';
export * from './useOrientation';
export * from './usePatchElement';
export * from './useProxyImperativeHandle';
export * from './useSyncState';
export * from './useZIndex';
+29
View File
@@ -0,0 +1,29 @@
import type { ReactNode } from 'react';
import React from 'react';
import type { DialogProps } from '@rc-component/dialog';
export type ClosableType = DialogProps['closable'];
export type BaseContextClosable = {
closable?: ClosableType;
closeIcon?: ReactNode;
};
export type ContextClosable<T extends BaseContextClosable = any> = Partial<Pick<T, 'closable' | 'closeIcon'>>;
export declare const pickClosable: <T extends BaseContextClosable>(context?: ContextClosable<T>) => ContextClosable<T> | undefined;
/** Collection contains the all the props related with closable. e.g. `closable`, `closeIcon` */
interface ClosableCollection {
closable?: ClosableType;
closeIcon?: ReactNode;
disabled?: boolean;
}
interface FallbackCloseCollection extends ClosableCollection {
/**
* Some components need to wrap CloseIcon twice,
* this method will be executed once after the final CloseIcon is calculated
*/
closeIconRender?: (closeIcon: ReactNode) => ReactNode;
}
type DataAttributes = {
[key: `data-${string}`]: string;
};
export declare const computeClosable: (propCloseCollection?: ClosableCollection, contextCloseCollection?: ClosableCollection | null, fallbackCloseCollection?: FallbackCloseCollection, closeLabel?: string) => [closable: boolean, closeIcon: React.ReactNode, closeBtnIsDisabled: boolean, ariaOrDataProps: React.AriaAttributes & DataAttributes];
export declare const useClosable: (propCloseCollection?: ClosableCollection, contextCloseCollection?: ClosableCollection | null, fallbackCloseCollection?: FallbackCloseCollection) => [closable: boolean, closeIcon: ReactNode, closeBtnIsDisabled: boolean, ariaOrDataProps: React.AriaAttributes & DataAttributes];
export {};
+105
View File
@@ -0,0 +1,105 @@
"use client";
import React from 'react';
import CloseOutlined from "@ant-design/icons/es/icons/CloseOutlined";
import pickAttrs from "@rc-component/util/es/pickAttrs";
import { useLocale } from '../../locale';
import defaultLocale from '../../locale/en_US';
import extendsObject from '../extendsObject';
import { isNonNullable, isPlainObject } from '../is';
export const pickClosable = context => {
if (!context) {
return undefined;
}
const {
closable,
closeIcon
} = context;
return {
closable,
closeIcon
};
};
const EmptyFallbackCloseCollection = {};
const computeClosableConfig = (closable, closeIcon) => {
if (!closable && (closable === false || closeIcon === false || closeIcon === null)) {
return false;
}
if (closable === undefined && closeIcon === undefined) {
return null;
}
let closableConfig = {
closeIcon: typeof closeIcon !== 'boolean' && closeIcon !== null ? closeIcon : undefined
};
if (isPlainObject(closable)) {
closableConfig = {
...closableConfig,
...closable
};
}
return closableConfig;
};
const mergeClosableConfigs = (propConfig, contextConfig, fallbackConfig) => {
if (propConfig === false) {
return false;
}
if (propConfig) {
return extendsObject(fallbackConfig, contextConfig, propConfig);
}
if (contextConfig === false) {
return false;
}
if (contextConfig) {
return extendsObject(fallbackConfig, contextConfig);
}
return fallbackConfig.closable ? fallbackConfig : false;
};
const computeCloseIcon = (mergedConfig, fallbackCloseCollection, closeLabel) => {
const {
closeIconRender
} = fallbackCloseCollection;
const {
closeIcon,
...restConfig
} = mergedConfig;
let finalCloseIcon = closeIcon;
const ariaOrDataProps = pickAttrs(restConfig, true);
if (isNonNullable(finalCloseIcon)) {
if (closeIconRender) {
finalCloseIcon = closeIconRender(finalCloseIcon);
}
finalCloseIcon = /*#__PURE__*/React.isValidElement(finalCloseIcon) ? (/*#__PURE__*/React.cloneElement(finalCloseIcon, {
'aria-label': closeLabel,
...finalCloseIcon.props,
...ariaOrDataProps
})) : (/*#__PURE__*/React.createElement("span", {
"aria-label": closeLabel,
...ariaOrDataProps
}, finalCloseIcon));
}
return [finalCloseIcon, ariaOrDataProps];
};
export const computeClosable = (propCloseCollection, contextCloseCollection, fallbackCloseCollection = EmptyFallbackCloseCollection, closeLabel = 'Close') => {
const propConfig = computeClosableConfig(propCloseCollection?.closable, propCloseCollection?.closeIcon);
const contextConfig = computeClosableConfig(contextCloseCollection?.closable, contextCloseCollection?.closeIcon);
const mergedFallback = {
closeIcon: /*#__PURE__*/React.createElement(CloseOutlined, null),
...fallbackCloseCollection
};
const mergedConfig = mergeClosableConfigs(propConfig, contextConfig, mergedFallback);
const closeBtnIsDisabled = typeof mergedConfig !== 'boolean' ? !!mergedConfig?.disabled : false;
if (mergedConfig === false) {
return [false, null, closeBtnIsDisabled, {}];
}
const [closeIcon, ariaProps] = computeCloseIcon(mergedConfig, mergedFallback, closeLabel);
return [true, closeIcon, closeBtnIsDisabled, ariaProps];
};
export const useClosable = (propCloseCollection, contextCloseCollection, fallbackCloseCollection = EmptyFallbackCloseCollection) => {
const [contextLocale] = useLocale('global', defaultLocale.global);
return React.useMemo(() => {
return computeClosable(propCloseCollection, contextCloseCollection, {
closeIcon: /*#__PURE__*/React.createElement(CloseOutlined, null),
...fallbackCloseCollection
}, contextLocale.close);
}, [propCloseCollection, contextCloseCollection, fallbackCloseCollection, contextLocale.close]);
};
+2
View File
@@ -0,0 +1,2 @@
import React from 'react';
export declare const useForceUpdate: () => [number, React.ActionDispatch<[]>];
+4
View File
@@ -0,0 +1,4 @@
import React from 'react';
export const useForceUpdate = () => {
return React.useReducer(ori => ori + 1, 0);
};
+32
View File
@@ -0,0 +1,32 @@
import * as React from 'react';
import type { AnyObject, EmptyObject, ValidChar } from '../type';
export type SemanticSchema = {
_default?: string;
} & {
[key: `${ValidChar}${string}`]: SemanticSchema;
};
export type Resolvable<T, P extends AnyObject> = T | ((info: {
props: P;
}) => T);
export type SemanticClassNamesType<Props extends AnyObject, SemanticClassNames extends Record<PropertyKey, string>, NestedStructure extends EmptyObject = EmptyObject> = Resolvable<Readonly<SemanticClassNames>, Props> & NestedStructure;
export type SemanticStylesType<Props extends AnyObject, SemanticStyles extends Record<PropertyKey, React.CSSProperties>, NestedStructure extends EmptyObject = EmptyObject> = Resolvable<Readonly<SemanticStyles>, Props> & NestedStructure;
export type SemanticType<P = any, T = any> = T | ((info: {
props: P;
}) => T);
export declare const mergeClassNames: <Name extends string, SemanticClassNames extends Partial<Record<Name, any>>>(schema?: SemanticSchema, ...classNames: (SemanticClassNames | undefined)[]) => SemanticClassNames;
export declare const mergeStyles: <StylesType extends AnyObject>(...styles: (Partial<StylesType> | undefined)[]) => Record<PropertyKey, React.CSSProperties>;
export declare const resolveStyleOrClass: <T extends AnyObject>(value: T | ((config: any) => T), info: {
props: AnyObject;
}) => T;
type MaybeFn<T, P> = T | ((info: {
props: P;
}) => T) | undefined;
type ObjectOnly<T> = T extends (...args: any) => any ? never : T;
/**
* @desc Merge classNames and styles from multiple sources. When `schema` is provided, it **must** provide the nest object structure.
* @descZH 合并来自多个来源的 classNames 和 styles,当提供了 `schema` 时,必须提供嵌套的对象结构。
*/
export declare const useMergeSemantic: <ClassNamesType extends AnyObject, StylesType extends AnyObject, Props extends AnyObject>(classNamesList: MaybeFn<ClassNamesType, Props>[], stylesList: MaybeFn<StylesType, Props>[], info: {
props: Props;
}, schema?: SemanticSchema) => readonly [ObjectOnly<ClassNamesType>, ObjectOnly<StylesType>];
export {};
+85
View File
@@ -0,0 +1,85 @@
import _toConsumableArray from "@babel/runtime/helpers/esm/toConsumableArray";
import * as React from 'react';
import { clsx } from 'clsx';
import { isPlainObject } from '../is';
// ========================= ClassNames =========================
export const mergeClassNames = (schema, ...classNames) => {
const mergedSchema = schema || {};
return classNames.filter(Boolean).reduce((acc, cur) => {
// Loop keys of the current classNames
Object.keys(cur || {}).forEach(key => {
const keySchema = mergedSchema[key];
const curVal = cur[key];
if (isPlainObject(keySchema)) {
if (isPlainObject(curVal)) {
// Loop fill
acc[key] = mergeClassNames(keySchema, acc[key], curVal);
} else {
// Covert string to object structure
const {
_default: defaultField
} = keySchema;
if (defaultField) {
acc[key] = acc[key] || {};
acc[key][defaultField] = clsx(acc[key][defaultField], curVal);
}
}
} else {
// Flatten fill
acc[key] = clsx(acc[key], curVal);
}
});
return acc;
}, {});
};
const useSemanticClassNames = (schema, ...classNames) => {
return React.useMemo(() => mergeClassNames.apply(void 0, [schema].concat(classNames)), [schema].concat(classNames));
};
// =========================== Styles ===========================
export const mergeStyles = (...styles) => {
return styles.filter(Boolean).reduce((acc, cur = {}) => {
Object.keys(cur).forEach(key => {
acc[key] = {
...acc[key],
...cur[key]
};
});
return acc;
}, {});
};
const useSemanticStyles = (...styles) => {
return React.useMemo(() => mergeStyles.apply(void 0, styles), [].concat(styles));
};
// =========================== Export ===========================
const fillObjectBySchema = (obj, schema) => {
const newObj = {
...obj
};
Object.keys(schema).forEach(key => {
if (key !== '_default') {
const nestSchema = schema[key];
const nextValue = newObj[key] || {};
newObj[key] = nestSchema ? fillObjectBySchema(nextValue, nestSchema) : nextValue;
}
});
return newObj;
};
export const resolveStyleOrClass = (value, info) => {
return typeof value === 'function' ? value(info) : value;
};
/**
* @desc Merge classNames and styles from multiple sources. When `schema` is provided, it **must** provide the nest object structure.
* @descZH 合并来自多个来源的 classNames 和 styles,当提供了 `schema` 时,必须提供嵌套的对象结构。
*/
export const useMergeSemantic = (classNamesList, stylesList, info, schema) => {
const resolvedClassNamesList = classNamesList.map(classNames => classNames ? resolveStyleOrClass(classNames, info) : undefined);
const resolvedStylesList = stylesList.map(styles => styles ? resolveStyleOrClass(styles, info) : undefined);
const mergedClassNames = useSemanticClassNames.apply(void 0, [schema].concat(_toConsumableArray(resolvedClassNamesList)));
const mergedStyles = useSemanticStyles.apply(void 0, _toConsumableArray(resolvedStylesList));
return React.useMemo(() => {
if (!schema) {
return [mergedClassNames, mergedStyles];
}
return [fillObjectBySchema(mergedClassNames, schema), fillObjectBySchema(mergedStyles, schema)];
}, [mergedClassNames, mergedStyles, schema]);
};
+10
View File
@@ -0,0 +1,10 @@
export interface MaskConfig {
enabled?: boolean;
blur?: boolean;
closable?: boolean;
}
export type MaskType = MaskConfig | boolean;
export declare const normalizeMaskConfig: (mask?: MaskType, maskClosable?: boolean) => MaskConfig;
export declare const useMergedMask: (mask?: MaskType, contextMask?: MaskType, prefixCls?: string, maskClosable?: boolean) => [config: boolean, maskBlurClassName: {
[key: string]: string | undefined;
}, maskClosable: boolean];
+33
View File
@@ -0,0 +1,33 @@
import { useMemo } from 'react';
import { isPlainObject } from '../is';
export const normalizeMaskConfig = (mask, maskClosable) => {
let maskConfig = {};
if (isPlainObject(mask)) {
maskConfig = mask;
}
if (typeof mask === 'boolean') {
maskConfig = {
enabled: mask
};
}
if (maskConfig.closable === undefined && maskClosable !== undefined) {
maskConfig.closable = maskClosable;
}
return maskConfig;
};
export const useMergedMask = (mask, contextMask, prefixCls, maskClosable) => {
return useMemo(() => {
const maskConfig = normalizeMaskConfig(mask, maskClosable);
const contextMaskConfig = normalizeMaskConfig(contextMask);
const mergedConfig = {
blur: false,
...contextMaskConfig,
...maskConfig,
closable: maskConfig.closable ?? maskClosable ?? contextMaskConfig.closable ?? true
};
const className = mergedConfig.blur ? `${prefixCls}-mask-blur` : undefined;
return [mergedConfig.enabled !== false, {
mask: className
}, !!mergedConfig.closable];
}, [mask, contextMask, prefixCls, maskClosable]);
};
@@ -0,0 +1,6 @@
export type PrevSelectedIndex = null | number;
/**
* @title multipleSelect hooks
* @description multipleSelect by hold down shift key
*/
export declare const useMultipleSelect: <T, K>(getKey: (item: T, index: number, array: T[]) => K) => readonly [(currentSelectedIndex: number, data: T[], selectedKeys: Set<K>) => K[], import("react").Dispatch<import("react").SetStateAction<PrevSelectedIndex>>];
+31
View File
@@ -0,0 +1,31 @@
import { useCallback, useState } from 'react';
/**
* @title multipleSelect hooks
* @description multipleSelect by hold down shift key
*/
export const useMultipleSelect = getKey => {
const [prevSelectedIndex, setPrevSelectedIndex] = useState(null);
const multipleSelect = useCallback((currentSelectedIndex, data, selectedKeys) => {
const configPrevSelectedIndex = prevSelectedIndex ?? currentSelectedIndex;
// add/delete the selected range
const startIndex = Math.min(configPrevSelectedIndex || 0, currentSelectedIndex);
const endIndex = Math.max(configPrevSelectedIndex || 0, currentSelectedIndex);
const rangeKeys = data.slice(startIndex, endIndex + 1).map(getKey);
const shouldSelected = rangeKeys.some(rangeKey => !selectedKeys.has(rangeKey));
const changedKeys = [];
rangeKeys.forEach(item => {
if (shouldSelected) {
if (!selectedKeys.has(item)) {
changedKeys.push(item);
}
selectedKeys.add(item);
} else {
selectedKeys.delete(item);
changedKeys.push(item);
}
});
setPrevSelectedIndex(shouldSelected ? endIndex : null);
return changedKeys;
}, [prevSelectedIndex]);
return [multipleSelect, setPrevSelectedIndex];
};
+2
View File
@@ -0,0 +1,2 @@
export type Orientation = 'horizontal' | 'vertical';
export declare const useOrientation: (orientation?: Orientation, vertical?: boolean, legacyDirection?: Orientation) => [Orientation, boolean];
+19
View File
@@ -0,0 +1,19 @@
import { useMemo } from 'react';
const isValidOrientation = orientation => {
return orientation === 'horizontal' || orientation === 'vertical';
};
export const useOrientation = (orientation, vertical, legacyDirection) => {
return useMemo(() => {
const validOrientation = isValidOrientation(orientation);
let mergedOrientation;
if (validOrientation) {
mergedOrientation = orientation;
} else if (typeof vertical === 'boolean') {
mergedOrientation = vertical ? 'vertical' : 'horizontal';
} else {
const validLegacyDirection = isValidOrientation(legacyDirection);
mergedOrientation = validLegacyDirection ? legacyDirection : 'horizontal';
}
return [mergedOrientation, mergedOrientation === 'vertical'];
}, [legacyDirection, orientation, vertical]);
};
@@ -0,0 +1,2 @@
import * as React from 'react';
export declare const usePatchElement: () => [React.ReactElement[], (element: React.ReactElement) => () => void];
+15
View File
@@ -0,0 +1,15 @@
import _toConsumableArray from "@babel/runtime/helpers/esm/toConsumableArray";
import * as React from 'react';
export const usePatchElement = () => {
const [elements, setElements] = React.useState([]);
const patchElement = React.useCallback(element => {
// append a new element to elements (and create a new ref)
setElements(originElements => [].concat(_toConsumableArray(originElements), [element]));
// return a function that removes the new element out of elements (and create a new ref)
// it works a little like useEffect
return () => {
setElements(originElements => originElements.filter(ele => ele !== element));
};
}, []);
return [elements, patchElement];
};
@@ -0,0 +1,4 @@
import type { Ref } from 'react';
export declare const useProxyImperativeHandle: <NativeELementType extends HTMLElement, ReturnRefType extends {
nativeElement: NativeELementType;
}>(ref: Ref<any> | undefined, init: () => ReturnRefType) => void;
@@ -0,0 +1,34 @@
// Proxy the dom ref with `{ nativeElement, otherFn }` type
// ref: https://github.com/ant-design/ant-design/discussions/45242
import { useImperativeHandle } from 'react';
const fillProxy = (element, handler) => {
element._antProxy = element._antProxy || {};
Object.keys(handler).forEach(key => {
if (!(key in element._antProxy)) {
const ori = element[key];
element._antProxy[key] = ori;
element[key] = handler[key];
}
});
return element;
};
export const useProxyImperativeHandle = (ref, init) => {
return useImperativeHandle(ref, () => {
const refObj = init();
const {
nativeElement
} = refObj;
if (typeof Proxy !== 'undefined') {
return new Proxy(nativeElement, {
get(obj, prop) {
if (refObj[prop]) {
return refObj[prop];
}
return Reflect.get(obj, prop);
}
});
}
// Fallback of IE
return fillProxy(nativeElement, refObj);
});
};
+3
View File
@@ -0,0 +1,3 @@
type UseSyncStateProps<T> = readonly [() => T, (newValue: T) => void];
export declare const useSyncState: <T>(initialValue: T) => UseSyncStateProps<T>;
export {};
+10
View File
@@ -0,0 +1,10 @@
import * as React from 'react';
import { useForceUpdate } from './useForceUpdate';
export const useSyncState = initialValue => {
const ref = React.useRef(initialValue);
const [, forceUpdate] = useForceUpdate();
return [() => ref.current, newValue => {
ref.current = newValue;
forceUpdate();
}];
};
+8
View File
@@ -0,0 +1,8 @@
export type ZIndexContainer = 'Modal' | 'Drawer' | 'Popover' | 'Popconfirm' | 'Tooltip' | 'Tour' | 'FloatButton';
export type ZIndexConsumer = 'SelectLike' | 'Dropdown' | 'DatePicker' | 'Menu' | 'ImagePreview';
export declare const CONTAINER_MAX_OFFSET: number;
export declare const containerBaseZIndexOffset: Record<ZIndexContainer, number>;
export declare const consumerBaseZIndexOffset: Record<ZIndexConsumer, number>;
type ReturnResult = [zIndex: number | undefined, contextZIndex: number];
export declare const useZIndex: (componentType: ZIndexContainer | ZIndexConsumer, customZIndex?: number) => ReturnResult;
export {};
+64
View File
@@ -0,0 +1,64 @@
import React from 'react';
import useToken from '../../theme/useToken';
import { devUseWarning } from '../warning';
import ZIndexContext from '../zindexContext';
// Z-Index control range
// Container: 1000 + offset 100 (max base + 10 * offset = 2000)
// Popover: offset 50
// Notification: Container Max zIndex + componentOffset
const CONTAINER_OFFSET = 100;
const CONTAINER_OFFSET_MAX_COUNT = 10;
export const CONTAINER_MAX_OFFSET = CONTAINER_OFFSET * CONTAINER_OFFSET_MAX_COUNT;
/**
* Static function will default be the `CONTAINER_MAX_OFFSET`.
* But it still may have children component like Select, Dropdown.
* So the warning zIndex should exceed the `CONTAINER_MAX_OFFSET`.
*/
const CONTAINER_MAX_OFFSET_WITH_CHILDREN = CONTAINER_MAX_OFFSET + CONTAINER_OFFSET;
export const containerBaseZIndexOffset = {
Modal: CONTAINER_OFFSET,
Drawer: CONTAINER_OFFSET,
Popover: CONTAINER_OFFSET,
Popconfirm: CONTAINER_OFFSET,
Tooltip: CONTAINER_OFFSET,
Tour: CONTAINER_OFFSET,
FloatButton: CONTAINER_OFFSET
};
export const consumerBaseZIndexOffset = {
SelectLike: 50,
Dropdown: 50,
DatePicker: 50,
Menu: 50,
ImagePreview: 1
};
const isContainerType = type => {
return type in containerBaseZIndexOffset;
};
export const useZIndex = (componentType, customZIndex) => {
const [, token] = useToken();
const parentZIndex = React.useContext(ZIndexContext);
const isContainer = isContainerType(componentType);
let result;
if (customZIndex !== undefined) {
result = [customZIndex, customZIndex];
} else {
let zIndex = parentZIndex ?? 0;
if (isContainer) {
zIndex +=
// Use preset token zIndex by default but not stack when has parent container
(parentZIndex ? 0 : token.zIndexPopupBase) +
// Container offset
containerBaseZIndexOffset[componentType];
} else {
zIndex += consumerBaseZIndexOffset[componentType];
}
result = [parentZIndex === undefined ? customZIndex : zIndex, zIndex];
}
if (process.env.NODE_ENV !== 'production') {
const warning = devUseWarning(componentType);
const maxZIndex = token.zIndexPopupBase + CONTAINER_MAX_OFFSET_WITH_CHILDREN;
const currentZIndex = result[0] || 0;
process.env.NODE_ENV !== "production" ? warning(customZIndex !== undefined || currentZIndex <= maxZIndex, 'usage', '`zIndex` is over design token `zIndexPopupBase` too much. It may cause unexpected override.') : void 0;
}
return result;
};