chore: sync master into feature

This commit is contained in:
thinkasany 2025-08-01 17:43:33 +08:00
commit 416c76997f
23 changed files with 182 additions and 29 deletions

View file

@ -25,6 +25,8 @@ tag: vVERSION
- ⚡️ Optimized the calculation performance of internal methods of Dropdown, Tooltip, Tour. [#54443](https://github.com/ant-design/ant-design/pull/54443) [Meet-student](https://github.com/Meet-student)
- TypeScript
- 🤖 Fixed Tabs `TabPaneProps` type to be compatible with deprecated APIs. [#54482](https://github.com/ant-design/ant-design/pull/54482) [@leshalv](https://github.com/leshalv)
- 🌐 Localization
- 🇹🇷 Added Turkish (tr_TR) localization support for the Typography.Text Component. [#54515](https://github.com/ant-design/ant-design/pull/54515) [@deniznehlyadyuk](https://github.com/deniznehlyadyuk)
## 5.26.6

View file

@ -25,6 +25,8 @@ tag: vVERSION
- ⚡️ 优化了 Dropdown、Tooltip、Tour 等弹层类组件的内部方法的计算性能。[#54443](https://github.com/ant-design/ant-design/pull/54443) [Meet-student](https://github.com/Meet-student)
- TypeScript
- 🤖 修正了 Tabs 的 `TabPaneProps` 类型以兼容废弃 api。[#54482](https://github.com/ant-design/ant-design/pull/54482) [@leshalv](https://github.com/leshalv)
- 🌐 本地化
- 🇹🇷 新增 Typography.Text 组件的的土耳其语tr_TR本地化支持。[#54515](https://github.com/ant-design/ant-design/pull/54515) [@deniznehlyadyuk](https://github.com/deniznehlyadyuk)
## 5.26.6

View file

@ -40,6 +40,20 @@ describe('Test utils function', () => {
expect(callback).not.toHaveBeenCalled();
});
it('should work with different argument types', async () => {
const callback = jest.fn();
const throttled = throttleByAnimationFrame(callback);
const obj = { key: 'value' };
const arr = [1, 2, 3];
const fn = () => {};
throttled(obj, arr, fn, null, undefined, 0, false, '');
await waitFakeTimer();
expect(callback).toHaveBeenCalledWith(obj, arr, fn, null, undefined, 0, false, '');
});
});
describe('style', () => {

View file

@ -1,7 +1,7 @@
import raf from 'rc-util/lib/raf';
function throttleByAnimationFrame<T extends any[]>(fn: (...args: T) => void) {
let requestId: number | null;
let requestId: number | null = null;
const later = (args: T) => () => {
requestId = null;
@ -9,7 +9,7 @@ function throttleByAnimationFrame<T extends any[]>(fn: (...args: T) => void) {
};
const throttled = (...args: T) => {
if (requestId == null) {
if (requestId === null) {
requestId = raf(later(args));
}
};

View file

@ -1,15 +1,8 @@
import React, {
Children,
useContext,
useEffect,
useLayoutEffect,
useMemo,
useRef,
useState,
} from 'react';
import React, { Children, useContext, useEffect, useMemo, useRef, useState } from 'react';
import classNames from 'classnames';
import omit from 'rc-util/lib/omit';
import { useComposeRef } from 'rc-util/lib/ref';
import useLayoutEffect from 'rc-util/lib/hooks/useLayoutEffect';
import { devUseWarning } from '../_util/warning';
import Wave from '../_util/wave';

View file

@ -1,4 +1,5 @@
import React from 'react';
import { Button, Input, Space } from 'antd';
import type { SingleValueType } from 'rc-cascader/lib/Cascader';
import type { DefaultOptionType } from '..';
@ -803,4 +804,41 @@ describe('Cascader', () => {
errSpy.mockRestore();
});
it('Cascader ContextIsolator', () => {
const { container } = render(
<Space.Compact>
<Cascader
open
style={{ width: 120 }}
popupRender={(menu) => {
return (
<div>
{menu}
<Button>123</Button>
<Input style={{ width: 50 }} />
</div>
);
}}
options={[
{ value: 'jack', label: 'Jack' },
{ value: 'lucy', label: 'Lucy' },
]}
/>
<Button className="test-button">test</Button>
</Space.Compact>,
);
const compactButton = container.querySelector('.test-button');
const popupElement = document.querySelector('.ant-select-dropdown');
// selector should have compact
expect(compactButton).toBeInTheDocument();
expect(compactButton!.className.includes('compact')).toBeTruthy();
// popupRender element haven't compact
expect(popupElement).toBeInTheDocument();
const button = popupElement!.querySelector('button');
const input = popupElement!.querySelector('input');
expect(button!.className.includes('compact')).toBeFalsy();
expect(input!.className.includes('compact')).toBeFalsy();
});
});

View file

@ -31,6 +31,7 @@ import useVariant from '../form/hooks/useVariants';
import mergedBuiltinPlacements from '../select/mergedBuiltinPlacements';
import useSelectStyle from '../select/style';
import useIcons from '../select/useIcons';
import usePopupRender from '../select/usePopupRender';
import useShowArrow from '../select/useShowArrow';
import { useCompactItemContext } from '../space/Compact';
import useBase from './hooks/useBase';
@ -292,7 +293,8 @@ const Cascader = React.forwardRef<CascaderRef, CascaderProps<any>>((props, ref)
cssVarCls,
);
const mergedPopupRender = popupRender || dropdownRender;
const mergedPopupRender = usePopupRender(popupRender || dropdownRender);
const mergedPopupMenuColumnStyle = popupMenuColumnStyle || dropdownMenuColumnStyle;
const mergedOnOpenChange = onOpenChange || onDropdownVisibleChange;
const mergedPopupStyle = styles?.popup?.root || contextStyles.popup?.root || dropdownStyle;

View file

@ -6,7 +6,7 @@ import type {
} from 'rc-menu/lib/interface';
export type DataAttributes = {
[Key in `data-${string}`]: string | number;
[Key in `data-${string}`]: unknown;
};
export interface MenuItemType extends RcMenuItemType, DataAttributes {

View file

@ -1,14 +1,15 @@
import React from 'react';
import { CloseOutlined } from '@ant-design/icons';
import { Button, Input, Space } from 'antd';
import type { SelectProps } from '..';
import Select from '..';
import Form from '../../form';
import { resetWarned } from '../../_util/warning';
import focusTest from '../../../tests/shared/focusTest';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
import { act, fireEvent, render } from '../../../tests/utils';
import Form from '../../form';
const { Option } = Select;
@ -290,4 +291,42 @@ describe('Select', () => {
errSpy.mockRestore();
});
});
it('Select ContextIsolator', () => {
const { container } = render(
<Space.Compact>
<Select
open
defaultValue="lucy"
style={{ width: 120 }}
popupRender={(menu) => {
return (
<div>
{menu}
<Button>123</Button>
<Input style={{ width: 50 }} />
</div>
);
}}
options={[
{ value: 'jack', label: 'Jack' },
{ value: 'lucy', label: 'Lucy' },
]}
/>
<Button className="test-button">test</Button>
</Space.Compact>,
);
const compactButton = container.querySelector('.test-button');
const popupElement = document.querySelector('.ant-select-dropdown');
// selector should have compact
expect(compactButton).toBeInTheDocument();
expect(compactButton!.className.includes('compact')).toBeTruthy();
// popupRender element haven't compact
expect(popupElement).toBeInTheDocument();
const button = popupElement!.querySelector('button');
const input = popupElement!.querySelector('input');
expect(button!.className.includes('compact')).toBeFalsy();
expect(input!.className.includes('compact')).toBeFalsy();
});
});

View file

@ -29,6 +29,7 @@ import { useToken } from '../theme/internal';
import mergedBuiltinPlacements from './mergedBuiltinPlacements';
import useStyle from './style';
import useIcons from './useIcons';
import usePopupRender from './usePopupRender';
import useShowArrow from './useShowArrow';
type RawValue = string | number;
@ -203,7 +204,9 @@ const InternalSelect = <
popupMatchSelectWidth ?? dropdownMatchSelectWidth ?? contextPopupMatchSelectWidth;
const mergedPopupStyle = styles?.popup?.root || contextStyles.popup?.root || dropdownStyle;
const mergedPopupRender = popupRender || dropdownRender;
const mergedPopupRender = usePopupRender(popupRender || dropdownRender);
const mergedOnOpenChange = onOpenChange || onDropdownVisibleChange;
// ===================== Form Status =====================

View file

@ -0,0 +1,18 @@
import React from 'react';
import ContextIsolator from '../_util/ContextIsolator';
type RenderFunction<T extends any[]> = (...args: T) => React.ReactNode;
function usePopupRender<T extends [React.ReactElement, ...any[]]>(
renderFn?: RenderFunction<T>,
): ((...args: T) => React.ReactElement) | undefined {
return React.useMemo(() => {
if (!renderFn) {
return undefined;
}
return (...args: T) => <ContextIsolator space>{renderFn(...args)}</ContextIsolator>;
}, [renderFn]);
}
export default usePopupRender;

View file

@ -5,6 +5,7 @@ import RightOutlined from '@ant-design/icons/RightOutlined';
import UpOutlined from '@ant-design/icons/UpOutlined';
import classNames from 'classnames';
import useEvent from 'rc-util/lib/hooks/useEvent';
import useLayoutEffect from 'rc-util/lib/hooks/useLayoutEffect';
export type ShowCollapsibleIconMode = boolean | 'auto';
@ -115,7 +116,7 @@ const SplitBar: React.FC<SplitBarProps> = (props) => {
}
};
React.useLayoutEffect(() => {
useLayoutEffect(() => {
if (startPos) {
const onMouseMove = (e: MouseEvent) => {
const { pageX, pageY } = e;

View file

@ -233,7 +233,7 @@ Properties for pagination.
| Property | Description | Type | Default |
| --- | --- | --- | --- |
| position | Specify the position of `Pagination`, could be`topLeft` \| `topCenter` \| `topRight` \|`bottomLeft` \| `bottomCenter` \| `bottomRight` | Array | \[`bottomRight`] |
| position | Specify the position of `Pagination`, could be`topLeft` \| `topCenter` \| `topRight` \|`bottomLeft` \| `bottomCenter` \| `bottomRight` \| `none` | Array | \[`bottomRight`] |
More about pagination, please check [`Pagination`](/components/pagination/).

View file

@ -235,7 +235,7 @@ const columns = [
| 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- |
| position | 指定分页显示的位置, 取值为`topLeft` \| `topCenter` \| `topRight` \|`bottomLeft` \| `bottomCenter` \| `bottomRight` | Array | \[`bottomRight`] |
| position | 指定分页显示的位置, 取值为`topLeft` \| `topCenter` \| `topRight` \|`bottomLeft` \| `bottomCenter` \| `bottomRight` \| `none` | Array | \[`bottomRight`] |
更多配置项,请查看 [`Pagination`](/components/pagination-cn)。

View file

@ -1,11 +1,12 @@
import React from 'react';
import { Button, Input, Space } from 'antd';
import TreeSelect, { TreeNode } from '..';
import { resetWarned } from '../../_util/warning';
import focusTest from '../../../tests/shared/focusTest';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
import { render, fireEvent } from '../../../tests/utils';
import { fireEvent, render } from '../../../tests/utils';
describe('TreeSelect', () => {
focusTest(TreeSelect, { refFocus: true });
@ -162,4 +163,41 @@ describe('TreeSelect', () => {
errSpy.mockRestore();
});
it('TreeSelect ContextIsolator', () => {
const { container } = render(
<Space.Compact>
<TreeSelect
open
defaultValue="lucy"
style={{ width: 120 }}
popupRender={(menu) => {
return (
<div>
{menu}
<Button>123</Button>
<Input style={{ width: 50 }} />
</div>
);
}}
treeData={[
{ value: 'jack', title: 'Jack', children: [{ value: 'Emily', title: 'Emily' }] },
{ value: 'lucy', title: 'Lucy' },
]}
/>
<Button className="test-button">test</Button>
</Space.Compact>,
);
const compactButton = container.querySelector('.test-button');
const popupElement = document.querySelector('.ant-select-dropdown');
// selector should have compact
expect(compactButton).toBeInTheDocument();
expect(compactButton!.className.includes('compact')).toBeTruthy();
// popupRender element haven't compact
expect(popupElement).toBeInTheDocument();
const button = popupElement!.querySelector('button');
const input = popupElement!.querySelector('input');
expect(button!.className.includes('compact')).toBeFalsy();
expect(input!.className.includes('compact')).toBeFalsy();
});
});

View file

@ -52,7 +52,7 @@ Common props ref[Common props](/docs/react/common-props)
| fieldNames | Customize node label, value, children field name | object | { label: `label`, value: `value`, children: `children` } | 4.17.0 |
| filterTreeNode | Whether to filter treeNodes by input value. The value of `treeNodeFilterProp` is used for filtering by default | boolean \| function(inputValue: string, treeNode: TreeNode) (should return boolean) | function | |
| getPopupContainer | To set the container of the dropdown menu. The default is to create a `div` element in `body`, you can reset it to the scrolling area and make a relative reposition. [example](https://codepen.io/afc163/pen/zEjNOy?editors=0010) | function(triggerNode) | () => document.body | |
| labelInValue | Whether to embed label in value, turn the format of value from `string` to {value: string, label: ReactNode, halfChecked: string\[]} | boolean | false | |
| labelInValue | Whether to embed label in value, turn the format of value from `string` to {value: string, label: ReactNode, halfChecked: boolean (Is the option list in a semi selected state and not displayed in the values)} | boolean | false | |
| listHeight | Config popup height | number | 256 | |
| loadData | Load data asynchronously. Will not load when filtering. Check FAQ for more info | function(node) | - | |
| maxTagCount | Max tag count to show. `responsive` will cost render performance | number \| `responsive` | - | responsive: 4.10 |

View file

@ -16,6 +16,7 @@ import { getMergedStatus, getStatusClassNames } from '../_util/statusUtils';
import { devUseWarning } from '../_util/warning';
import { ConfigContext } from '../config-provider';
import type { Variant } from '../config-provider';
import { useComponentConfig } from '../config-provider/context';
import DefaultRenderEmpty from '../config-provider/defaultRenderEmpty';
import DisabledContext from '../config-provider/DisabledContext';
import useCSSVarCls from '../config-provider/hooks/useCSSVarCls';
@ -26,6 +27,7 @@ import useVariant from '../form/hooks/useVariants';
import mergedBuiltinPlacements from '../select/mergedBuiltinPlacements';
import useSelectStyle from '../select/style';
import useIcons from '../select/useIcons';
import usePopupRender from '../select/usePopupRender';
import useShowArrow from '../select/useShowArrow';
import { useCompactItemContext } from '../space/Compact';
import { useToken } from '../theme/internal';
@ -33,7 +35,6 @@ import type { AntTreeNodeProps, TreeProps } from '../tree';
import type { SwitcherIcon } from '../tree/Tree';
import SwitcherIconCom from '../tree/utils/iconUtil';
import useStyle from './style';
import { useComponentConfig } from '../config-provider/context';
type RawValue = string | number;
@ -226,7 +227,9 @@ const InternalTreeSelect = <ValueType = any, OptionType extends DataNode = DataN
);
const mergedPopupStyle = styles?.popup?.root || contextStyles?.popup?.root || dropdownStyle;
const mergedPopupRender = popupRender || dropdownRender;
const mergedPopupRender = usePopupRender(popupRender || dropdownRender);
const mergedOnOpenChange = onOpenChange || onDropdownVisibleChange;
const isMultiple = !!(treeCheckable || multiple);

View file

@ -53,7 +53,7 @@ demo:
| fieldNames | 自定义节点 label、value、children 的字段 | object | { label: `label`, value: `value`, children: `children` } | 4.17.0 |
| filterTreeNode | 是否根据输入项进行筛选,默认用 treeNodeFilterProp 的值作为要筛选的 TreeNode 的属性值 | boolean \| function(inputValue: string, treeNode: TreeNode) (函数需要返回 bool 值) | function | |
| getPopupContainer | 菜单渲染父节点。默认渲染到 body 上,如果你遇到菜单滚动定位问题,试试修改为滚动的区域,并相对其定位。[示例](https://codepen.io/afc163/pen/zEjNOy?editors=0010) | function(triggerNode) | () => document.body | |
| labelInValue | 是否把每个选项的 label 包装到 value 中,会把 value 类型从 `string` 变为 {value: string, label: ReactNode, halfChecked(treeCheckStrictly 时有效): string\[] } 的格式 | boolean | false | |
| labelInValue | 是否把每个选项的 label 包装到 value 中,会把 value 类型从 `string` 变为 {value: string, label: ReactNode, halfChecked: boolean(选项列表是否为半选状态,并且不会展示到值中) } 的格式 | boolean | false | |
| listHeight | 设置弹窗滚动高度 | number | 256 | |
| loadData | 异步加载数据。在过滤时不会调用以防止网络堵塞,可参考 FAQ 获得更多内容 | function(node) | - | |
| maxCount | 指定可选中的最多 items 数量,仅在 `multiple=true` 时生效。如果此时 (`showCheckedStrategy = 'SHOW_ALL'` 且未开启 `treeCheckStrictly`),或使用 `showCheckedStrategy = 'SHOW_PARENT'`则maxCount无效。 | number | - | 5.23.0 |

View file

@ -37,7 +37,7 @@ const CopyBtn: React.FC<CopyBtnProps> = ({
const ariaLabel = typeof copyTitle === 'string' ? copyTitle : systemStr;
return (
<Tooltip title={copyTitle}>
<Tooltip title={copyTitle} getPopupContainer={(node) => node.parentNode as HTMLElement}>
<button
type="button"
className={classNames(`${prefixCls}-copy`, {

View file

@ -1,4 +1,4 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`renders components/typography/demo/basic.tsx extend context correctly 1`] = `
<article

View file

@ -1,4 +1,4 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`renders components/typography/demo/basic.tsx correctly 1`] = `
<article

View file

@ -101,7 +101,7 @@ export interface UploadProps<T = any>
| ((file: UploadFile<T>) => Record<string, unknown> | Promise<Record<string, unknown>>);
method?: 'POST' | 'PUT' | 'PATCH' | 'post' | 'put' | 'patch';
headers?: HttpRequestHeader;
showUploadList?: boolean | ShowUploadListInterface;
showUploadList?: boolean | ShowUploadListInterface<T>;
multiple?: boolean;
accept?: string;
beforeUpload?: (

View file

@ -150,7 +150,7 @@
"rc-switch": "~4.1.0",
"rc-table": "~7.51.1",
"rc-tabs": "~15.7.0",
"rc-textarea": "~1.10.1",
"rc-textarea": "~1.10.2",
"rc-tooltip": "~6.4.0",
"rc-tree": "~5.13.1",
"rc-tree-select": "~5.27.0",
@ -328,7 +328,7 @@
"tar": "^7.4.3",
"tsx": "^4.20.3",
"typedoc": "^0.28.0",
"typescript": "~5.8.2",
"typescript": "~5.9.2",
"vanilla-jsoneditor": "^3.0.0",
"vanilla-tilt": "^1.8.1",
"webpack": "^5.100.0",