Compare commits

..

10 Commits

Author SHA1 Message Date
xrkffgg
a31d11d8a7 docs: add changelog 5.27.6 (#55380) 2025-10-20 16:44:32 +08:00
lijianan
2367a516c4 site: update missing isLight dependency (#55377)
* site: update missing themeData dependency

* update

---------

Co-authored-by: 遇见同学 <1875694521@qq.com>
2025-10-20 09:16:47 +08:00
lijianan
af1a78a83f refactor: avoid unnecessary re-renders by explicitly listing deps (#55376) 2025-10-20 08:37:24 +08:00
lijianan
072c9fa707 type: update any type to ConfirmDialogProps (#55371) 2025-10-19 23:21:17 +08:00
lijianan
a0a32ff450 fix: add missing getConfirmFunc dependency (#55370)
* fix: add missing getConfirmFunc dependency

* fix: add missing getConfirmFunc dependency
2025-10-19 22:53:07 +08:00
thinkasany
25001647a7 Revert "refactor(modal): use modalRef replace createRef (#55368)" (#55369)
This reverts commit 3b47ac3630.
2025-10-19 20:07:41 +08:00
thinkasany
3b47ac3630 refactor(modal): use modalRef replace createRef (#55368) 2025-10-19 16:28:58 +08:00
thinkasany
7a70bafe42 chore: rm useless eslint comments (#55367)
* chore: rm useless eslint comments

* rerun ci
2025-10-19 15:57:16 +08:00
lijianan
c0408b70aa fix: add missing checkedActiveItems dep (#55366) 2025-10-19 13:59:15 +08:00
lijianan
90696b1632 fix: add missing schema dep for useMergeSemantic (#55364)
* fix: add missing schema dep for useMergeSemantic

* update

* update
2025-10-19 13:19:08 +08:00
17 changed files with 128 additions and 152 deletions

View File

@@ -389,10 +389,9 @@ const Theme: React.FC = () => {
themeType,
...ThemesInfo[themeType],
};
setThemeData(mergedData);
form.setFieldsValue(mergedData);
}, [themeType]);
}, [form, themeType]);
const isDark = React.use(DarkContext);
@@ -433,23 +432,14 @@ const Theme: React.FC = () => {
token: { ...themeToken, colorPrimary: colorPrimaryValue },
algorithm: algorithmFn,
components: {
Layout: isLight
? {
headerBg: 'transparent',
bodyBg: 'transparent',
}
: {},
Layout: isLight ? { headerBg: 'transparent', bodyBg: 'transparent' } : {},
Menu: isLight
? {
itemBg: 'transparent',
subMenuItemBg: 'transparent',
activeBarBorderWidth: 0,
}
? { itemBg: 'transparent', subMenuItemBg: 'transparent', activeBarBorderWidth: 0 }
: {},
...(themeType === 'v4' ? defaultTheme.components : {}),
},
}),
[themeToken, colorPrimaryValue, algorithmFn, themeType],
[themeToken, colorPrimaryValue, algorithmFn, isLight, themeType],
);
// ================================ Render ================================

View File

@@ -164,7 +164,6 @@ const CodePreview: React.FC<CodePreviewProps> = ({
</div>
),
})),
// eslint-disable-next-line react-hooks/exhaustive-deps
[
entryName,
error,

View File

@@ -122,7 +122,7 @@ const GlobalLayout: React.FC = () => {
setSearchParams(nextSearchParams);
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[searchParams, setSearchParams],
);
@@ -177,7 +177,6 @@ const GlobalLayout: React.FC = () => {
return () => {
window.removeEventListener('resize', updateMobileMode);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [searchParams, updateMobileMode]);
const siteContextValue = React.useMemo<SiteContextProps>(

View File

@@ -40,12 +40,10 @@ const Content: React.FC<React.PropsWithChildren> = ({ children }) => {
useLayoutEffect(() => {
setShowDebug(process.env.NODE_ENV === 'development' || isDebugDemo);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isDebugDemo]);
const contextValue = useMemo<DemoContextProps>(
() => ({ showDebug, setShowDebug, codeType, setCodeType }),
// eslint-disable-next-line react-hooks/exhaustive-deps
[showDebug, codeType],
);

View File

@@ -15,6 +15,19 @@ tag: vVERSION
---
## 5.27.6
`2025-10-20`
- Table
- 🐞 Fix Table `pagination.align` is not working. [#55316](https://github.com/ant-design/ant-design/pull/55316)
- 🛠 Add Table missing useMemo capability to spinProps. [#55344](https://github.com/ant-design/ant-design/pull/55344)
- 🛠 Refactor Modal useMemo of ConfirmDialog to resolve useMemo invalid where Object.values generates a new array. [#55376](https://github.com/ant-design/ant-design/pull/55376)
- TypeScript
- 🤖 Add ConfigProvider the `Window` type definition in `getTargetContainer` of . [#55313](https://github.com/ant-design/ant-design/pull/55313)
- 🤖 Add ConfigProvider the `ShadowRoot` type definition in `getTargetContainer` and `getPopupContainer`. [#55278](https://github.com/ant-design/ant-design/pull/55278) [@leshalv](https://github.com/leshalv)
- 🤖 Improve Modal type definition. [#55371](https://github.com/ant-design/ant-design/pull/55371)
## 5.27.5
`2025-10-14`

View File

@@ -15,6 +15,19 @@ tag: vVERSION
---
## 5.27.6
`2025-10-20`
- Table
- 🐞 修复 Table `pagination.align` 属性失效的问题。[#55316](https://github.com/ant-design/ant-design/pull/55316)
- 🛠 补充 Table 中 spinProps 的 useMemo 缺失能力。[#55344](https://github.com/ant-design/ant-design/pull/55344)
- 🛠 重构 Modal 中 ConfirmDialog 的 useMemo以解决 Object.values 生成新数组导致 useMemo 失效的问题。[#55376](https://github.com/ant-design/ant-design/pull/55376)
- TypeScript
- 🤖 补充 ConfigProvider `getTargetContainer``Window` 类型定义。[#55313](https://github.com/ant-design/ant-design/pull/55313)
- 🤖 补充 ConfigProvider 的 `getTargetContainer``getPopupContainer``ShadowRoot` 类型定义。[#55278](https://github.com/ant-design/ant-design/pull/55278) [@leshalv](https://github.com/leshalv)
- 🤖 优化 Modal 中类型定义。[#55371](https://github.com/ant-design/ant-design/pull/55371)
## 5.27.5
`2025-10-14`

View File

@@ -1,13 +1,11 @@
import * as React from 'react';
import classnames from 'classnames';
import type { ValidChar } from './interface';
import type { ValidChar } from '../type';
type TemplateSemanticClassNames<T extends string> = Partial<Record<T, string>>;
export type SemanticSchema = {
_default?: string;
} & {
export type SemanticSchema = { _default?: string } & {
[key: `${ValidChar}${string}`]: SemanticSchema;
};
@@ -15,7 +13,7 @@ export type SemanticSchema = {
export function mergeClassNames<
T extends string,
SemanticClassNames extends Partial<Record<T, any>> = TemplateSemanticClassNames<T>,
>(schema: SemanticSchema | undefined, ...classNames: (SemanticClassNames | undefined)[]) {
>(schema?: SemanticSchema, ...classNames: (SemanticClassNames | undefined)[]) {
const mergedSchema = schema || {};
return classNames.reduce((acc: any, cur) => {
@@ -31,8 +29,10 @@ export function mergeClassNames<
} else {
// Covert string to object structure
const { _default: defaultField } = keySchema;
acc[key] = acc[key] || {};
acc[key][defaultField!] = classnames(acc[key][defaultField!], curVal);
if (defaultField) {
acc[key] = acc[key] || {};
acc[key][defaultField] = classnames(acc[key][defaultField], curVal);
}
}
} else {
// Flatten fill
@@ -40,16 +40,16 @@ export function mergeClassNames<
}
});
return acc;
}, {} as SemanticClassNames) as SemanticClassNames;
}, {} as SemanticClassNames);
}
function useSemanticClassNames<ClassNamesType extends object>(
schema: SemanticSchema | undefined,
schema?: SemanticSchema,
...classNames: (Partial<ClassNamesType> | undefined)[]
): Partial<ClassNamesType> {
return React.useMemo(
() => mergeClassNames(schema, ...classNames),
[classNames],
[classNames, schema],
) as ClassNamesType;
}
@@ -58,31 +58,25 @@ function useSemanticStyles<StylesType extends object>(
...styles: (Partial<StylesType> | undefined)[]
) {
return React.useMemo(() => {
return styles.reduce(
(acc, cur = {}) => {
Object.keys(cur).forEach((key) => {
acc[key] = { ...acc[key], ...(cur as Record<string, React.CSSProperties>)[key] };
});
return acc;
},
{} as Record<string, React.CSSProperties>,
);
return styles.reduce<Record<string, React.CSSProperties>>((acc, cur = {}) => {
Object.keys(cur).forEach((key) => {
acc[key] = { ...acc[key], ...(cur as Record<string, React.CSSProperties>)[key] };
});
return acc;
}, {});
}, [styles]) as StylesType;
}
// =========================== Export ===========================
function fillObjectBySchema<T extends object>(obj: T, schema: SemanticSchema): T {
const newObj: any = { ...obj };
Object.keys(schema).forEach((key) => {
if (key !== '_default') {
const nestSchema = (schema as any)[key] as SemanticSchema;
const nextValue = newObj[key] || {};
newObj[key] = nestSchema ? fillObjectBySchema(nextValue, nestSchema) : nextValue;
}
});
return newObj;
}
@@ -103,5 +97,5 @@ export default function useMergeSemantic<ClassNamesType extends object, StylesTy
fillObjectBySchema(mergedClassNames, schema!) as ClassNamesType,
fillObjectBySchema(mergedStyles, schema!) as StylesType,
] as const;
}, [mergedClassNames, mergedStyles]);
}, [mergedClassNames, mergedStyles, schema]);
}

View File

@@ -1,27 +0,0 @@
export type ValidChar =
| 'a'
| 'b'
| 'c'
| 'd'
| 'e'
| 'f'
| 'g'
| 'h'
| 'i'
| 'j'
| 'k'
| 'l'
| 'm'
| 'n'
| 'o'
| 'p'
| 'q'
| 'r'
| 's'
| 't'
| 'u'
| 'v'
| 'w'
| 'x'
| 'y'
| 'z';

View File

@@ -5,6 +5,8 @@ export type LiteralUnion<T extends string> = T | (string & {});
export type AnyObject = Record<PropertyKey, any>;
export type EmptyObject = Record<never, never>;
export type CustomComponent<P = AnyObject> = React.ComponentType<P> | string;
/**
@@ -78,3 +80,31 @@ export type GetContextProp<
T extends React.Context<any>,
PropName extends keyof GetContextProps<T>,
> = NonNullable<GetContextProps<T>[PropName]>;
export type ValidChar =
| 'a'
| 'b'
| 'c'
| 'd'
| 'e'
| 'f'
| 'g'
| 'h'
| 'i'
| 'j'
| 'k'
| 'l'
| 'm'
| 'n'
| 'o'
| 'p'
| 'q'
| 'r'
| 's'
| 't'
| 'u'
| 'v'
| 'w'
| 'x'
| 'y'
| 'z';

View File

@@ -48,11 +48,9 @@ export interface ConfirmDialogProps extends ModalFuncProps {
isSilent?: () => boolean;
}
export function ConfirmContent(
props: ConfirmDialogProps & {
confirmPrefixCls: string;
},
) {
export const ConfirmContent: React.FC<ConfirmDialogProps & { confirmPrefixCls: string }> = (
props,
) => {
const {
prefixCls,
icon,
@@ -113,15 +111,15 @@ export function ConfirmContent(
const okTextLocale = okText || (mergedOkCancel ? mergedLocale?.okText : mergedLocale?.justOkText);
const cancelTextLocale = cancelText || mergedLocale?.cancelText;
// ================= Context Value =================
const btnCtxValue: ModalContextProps = {
autoFocusButton,
cancelTextLocale,
okTextLocale,
mergedOkCancel,
...resetProps,
};
const btnCtxValueMemo = React.useMemo(() => btnCtxValue, [...Object.values(btnCtxValue)]);
const memoizedValue = React.useMemo<ModalContextProps>(() => {
return {
autoFocusButton,
cancelTextLocale,
okTextLocale,
mergedOkCancel,
...resetProps,
};
}, [autoFocusButton, cancelTextLocale, okTextLocale, mergedOkCancel, resetProps]);
// ====================== Footer Origin Node ======================
const footerOriginNode = (
@@ -148,26 +146,21 @@ export function ConfirmContent(
<div className={`${confirmPrefixCls}-content`}>{props.content}</div>
</div>
</div>
{footer === undefined || typeof footer === 'function' ? (
<ModalContextProvider value={btnCtxValueMemo}>
<ModalContextProvider value={memoizedValue}>
<div className={`${confirmPrefixCls}-btns`}>
{typeof footer === 'function'
? footer(footerOriginNode, {
OkBtn,
CancelBtn,
})
? footer(footerOriginNode, { OkBtn, CancelBtn })
: footerOriginNode}
</div>
</ModalContextProvider>
) : (
footer
)}
<Confirm prefixCls={prefixCls} />
</div>
);
}
};
const ConfirmDialog: React.FC<ConfirmDialogProps> = (props) => {
const {

View File

@@ -90,7 +90,7 @@ export default function confirm(config: ModalFuncProps) {
reactUnmount();
}
function render(props: any) {
const scheduleRender = (props: ConfirmDialogProps) => {
clearTimeout(timeoutId);
/**
@@ -109,12 +109,12 @@ export default function confirm(config: ModalFuncProps) {
reactUnmount = reactRender(
<ConfigProvider prefixCls={rootPrefixCls} iconPrefixCls={iconPrefixCls} theme={theme}>
{global.holderRender ? global.holderRender(dom) : dom}
{typeof global.holderRender === 'function' ? global.holderRender(dom) : dom}
</ConfigProvider>,
container,
);
});
}
};
function close(...args: any[]) {
currentConfig = {
@@ -134,22 +134,19 @@ export default function confirm(config: ModalFuncProps) {
delete currentConfig.visible;
}
render(currentConfig);
scheduleRender(currentConfig);
}
function update(configUpdate: ConfigUpdate) {
if (typeof configUpdate === 'function') {
currentConfig = configUpdate(currentConfig);
} else {
currentConfig = {
...currentConfig,
...configUpdate,
};
currentConfig = { ...currentConfig, ...configUpdate };
}
render(currentConfig);
scheduleRender(currentConfig);
}
render(currentConfig);
scheduleRender(currentConfig);
destroyFns.push(close);

View File

@@ -54,8 +54,18 @@ export const Footer: React.FC<
const okTextLocale: React.ReactNode = okText || locale?.okText;
const cancelTextLocale = cancelText || locale?.cancelText;
// ================= Context Value =================
const btnCtxValue: ModalContextProps = {
const memoizedValue = React.useMemo<ModalContextProps>(() => {
return {
confirmLoading,
okButtonProps,
cancelButtonProps,
okTextLocale,
cancelTextLocale,
okType,
onOk,
onCancel,
};
}, [
confirmLoading,
okButtonProps,
cancelButtonProps,
@@ -64,9 +74,7 @@ export const Footer: React.FC<
okType,
onOk,
onCancel,
};
const btnCtxValueMemo = React.useMemo(() => btnCtxValue, [...Object.values(btnCtxValue)]);
]);
let footerNode: React.ReactNode;
if (typeof footer === 'function' || typeof footer === 'undefined') {
@@ -78,13 +86,10 @@ export const Footer: React.FC<
);
if (typeof footer === 'function') {
footerNode = footer(footerNode, {
OkBtn: NormalOkBtn,
CancelBtn: NormalCancelBtn,
});
footerNode = footer(footerNode, { OkBtn: NormalOkBtn, CancelBtn: NormalCancelBtn });
}
footerNode = <ModalContextProvider value={btnCtxValueMemo}>{footerNode}</ModalContextProvider>;
footerNode = <ModalContextProvider value={memoizedValue}>{footerNode}</ModalContextProvider>;
} else {
footerNode = footer;
}

View File

@@ -24,13 +24,7 @@ export type HookAPI = Omit<Record<keyof ModalStaticFunctions, ModalFuncWithPromi
const ElementsHolder = React.memo(
React.forwardRef<ElementsHolderRef>((_props, ref) => {
const [elements, patchElement] = usePatchElement();
React.useImperativeHandle(
ref,
() => ({
patchElement,
}),
[],
);
React.useImperativeHandle(ref, () => ({ patchElement }), [patchElement]);
return <>{elements}</>;
}),
);
@@ -132,7 +126,7 @@ function useModal(): readonly [instance: HookAPI, contextHolder: React.ReactElem
warning: getConfirmFunc(withWarn),
confirm: getConfirmFunc(withConfirm),
}),
[],
[getConfirmFunc],
);
return [fns, <ElementsHolder key="modal-holder" ref={holderRef} />] as const;
}

View File

@@ -148,13 +148,13 @@ const TransferList = <RecordType extends KeyWiseTransferItem>(
};
const matchFilter = (text: string, item: RecordType) => {
if (filterOption) {
if (typeof filterOption === 'function') {
return filterOption(filterValue, item, direction);
}
return text.includes(filterValue);
};
const renderListBody = (listProps: TransferListBodyProps<RecordType>) => {
const customRenderListBody = (listProps: TransferListBodyProps<RecordType>) => {
let bodyContent: React.ReactNode = renderList
? renderList({
...listProps,
@@ -214,9 +214,9 @@ const TransferList = <RecordType extends KeyWiseTransferItem>(
return 'all';
}
return 'part';
}, [checkedKeys, checkedActiveItems]);
}, [checkedActiveItems.length, checkedKeys, filteredItems]);
const listBody = useMemo<React.ReactNode>(() => {
const renderListBody = () => {
const search = showSearch ? (
<div className={`${prefixCls}-body-search-wrapper`}>
<Search
@@ -230,7 +230,7 @@ const TransferList = <RecordType extends KeyWiseTransferItem>(
</div>
) : null;
const { customize, bodyContent } = renderListBody({
const { customize, bodyContent } = customRenderListBody({
...omit(props, OmitProps),
filteredItems,
filteredRenderItems,
@@ -258,17 +258,7 @@ const TransferList = <RecordType extends KeyWiseTransferItem>(
{bodyNode}
</div>
);
}, [
showSearch,
prefixCls,
searchPlaceholder,
filterValue,
disabled,
checkedKeys,
filteredItems,
filteredRenderItems,
notFoundContentEle,
]);
};
const checkBox = (
<Checkbox
@@ -402,7 +392,7 @@ const TransferList = <RecordType extends KeyWiseTransferItem>(
</span>
<span className={`${prefixCls}-header-title`}>{titleText}</span>
</div>
{listBody}
{renderListBody()}
{listFooter}
</div>
);

View File

@@ -40,7 +40,7 @@ export default antfu(
'regexp/no-misleading-capturing-group': 'off',
'regexp/no-super-linear-backtracking': 'off', // TODO: remove this
'regexp/optimal-quantifier-concatenation': 'off',
'react-hooks/exhaustive-deps': 'warn',
'react-hooks/exhaustive-deps': 'off',
'react-refresh/only-export-components': 'off', // TODO: remove this
'react/no-clone-element': 'off',
'react/no-children-for-each': 'off',

View File

@@ -1,6 +1,6 @@
{
"name": "antd",
"version": "5.27.5",
"version": "5.27.6",
"description": "An enterprise-class UI design language and React components implementation",
"license": "MIT",
"funding": {

View File

@@ -6,7 +6,6 @@ import axios from 'axios';
import chalk from 'chalk';
import dotnev from 'dotenv';
import Spinnies from 'spinnies';
import { confirm } from '@inquirer/prompts';
import checkRepo from './check-repo';
@@ -141,20 +140,9 @@ const runPrePublish = async () => {
}
if (data.state === 'pending') {
showMessage(chalk.bgYellowBright('远程分支 CI 还在执行中'), 'non-spinnable');
showMessage(chalk.bgRedBright('远程分支 CI 还在执行中,请稍候再试'), 'fail');
showMessage(` 点此查看状态https://github.com/${owner}/${repo}/commit/${sha}`);
const shouldSkip = await confirm({
message: '是否要跳过 CI 检查继续发布?',
default: false,
});
if (!shouldSkip) {
showMessage(chalk.bgRedBright('已取消发布,请等待 CI 完成后再试'), 'fail');
process.exit(1);
}
showMessage(chalk.bgYellowBright('用户选择跳过 CI 检查,继续发布流程'), 'succeed');
process.exit(1);
}
if (data.state !== 'success') {