mirror of
https://gitee.com/ant-design/ant-design.git
synced 2024-11-30 02:59:04 +08:00
Merge branch 'master' into next-merge-master
This commit is contained in:
commit
7a38804f95
38
.circleci/config.yml
Normal file
38
.circleci/config.yml
Normal file
@ -0,0 +1,38 @@
|
||||
# Use the latest 2.1 version of CircleCI pipeline process engine.
|
||||
# See: https://circleci.com/docs/2.0/configuration-reference
|
||||
version: 2.1
|
||||
|
||||
# Define a job to be invoked later in a workflow.
|
||||
# See: https://circleci.com/docs/2.0/configuration-reference/#jobs
|
||||
jobs:
|
||||
test-argos-ci:
|
||||
docker:
|
||||
- image: circleci/node:16-browsers
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: Install node_modules
|
||||
command: npm i
|
||||
- run:
|
||||
name: Install argos cli
|
||||
command: npm i fast-glob lodash @argos-ci/core
|
||||
- run:
|
||||
name: Install puppeteer
|
||||
command: node node_modules/puppeteer/install.js
|
||||
- run:
|
||||
name: Build dist file
|
||||
command: npm run dist
|
||||
- run:
|
||||
name: Run image screenshot tests
|
||||
command: npm run test-image
|
||||
- run:
|
||||
name: Upload screenshots to Argos CI
|
||||
command: npm run argos
|
||||
|
||||
|
||||
# Invoke jobs via workflows
|
||||
# See: https://circleci.com/docs/2.0/configuration-reference/#workflows
|
||||
workflows:
|
||||
test-argos-ci-workflow:
|
||||
jobs:
|
||||
- test-argos-ci
|
63
.github/workflows/ui-upload.yml
vendored
63
.github/workflows/ui-upload.yml
vendored
@ -1,63 +0,0 @@
|
||||
# Upload 📷 UI snapshots to argos server, help visual regression testing.
|
||||
name: 📷 UI Upload
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["📷 UI"]
|
||||
types:
|
||||
- completed
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
upload-ui:
|
||||
permissions:
|
||||
actions: read # for dawidd6/action-download-artifact to query and download artifacts
|
||||
pull-requests: read # for dawidd6/action-download-artifact to query commit hash
|
||||
name: deploy preview
|
||||
runs-on: ubuntu-latest
|
||||
if: >
|
||||
github.repository == 'ant-design/ant-design' &&
|
||||
github.event.workflow_run.conclusion == 'success'
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Download commit artifact
|
||||
uses: dawidd6/action-download-artifact@v2.23.0
|
||||
with:
|
||||
workflow: ${{ github.event.workflow_run.workflow_id }}
|
||||
name: commit
|
||||
|
||||
- name: Save commit id
|
||||
id: commit
|
||||
run: echo "::set-output name=id::$(<commit.txt)"
|
||||
|
||||
- name: Download branch artifact
|
||||
uses: dawidd6/action-download-artifact@v2.23.0
|
||||
with:
|
||||
workflow: ${{ github.event.workflow_run.workflow_id }}
|
||||
name: branch
|
||||
|
||||
- name: Save branch id
|
||||
id: branch
|
||||
run: echo "::set-output name=id::$(<branch.txt)"
|
||||
|
||||
- name: Download snapshots artifact
|
||||
uses: dawidd6/action-download-artifact@v2.23.0
|
||||
with:
|
||||
workflow: ${{ github.event.workflow_run.workflow_id }}
|
||||
workflow_conclusion: success
|
||||
name: snapshots
|
||||
path: imageSnapshots
|
||||
|
||||
- name: Install
|
||||
run: npm i fast-glob lodash @argos-ci/core
|
||||
|
||||
- name: Upload on Argos
|
||||
id: deploy
|
||||
run: npm run argos
|
||||
env:
|
||||
ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }}
|
||||
ARGOS_PARALLEL_NONCE: ${{ github.run_id }}
|
93
.github/workflows/ui.yml
vendored
93
.github/workflows/ui.yml
vendored
@ -1,93 +0,0 @@
|
||||
name: 📷 UI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
push:
|
||||
|
||||
# Cancel prev CI if new commit come
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
imagesnapshot:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: cache package-lock.json
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: package-temp-dir
|
||||
key: lock-${{ github.sha }}
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '16'
|
||||
|
||||
- name: create package-lock.json
|
||||
run: npm i --package-lock-only --ignore-scripts
|
||||
|
||||
- name: hack for single file
|
||||
run: |
|
||||
if [ ! -d "package-temp-dir" ]; then
|
||||
mkdir package-temp-dir
|
||||
fi
|
||||
cp package-lock.json package-temp-dir
|
||||
- name: cache node_modules
|
||||
id: node_modules_cache_id
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: node_modules
|
||||
key: node_modules-${{ hashFiles('**/package-temp-dir/package-lock.json') }}
|
||||
|
||||
- name: install
|
||||
if: steps.node_modules_cache_id.outputs.cache-hit != 'true'
|
||||
run: npm ci
|
||||
|
||||
- name: test
|
||||
run: npm run test-image
|
||||
|
||||
- name: upload snapshots artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: snapshots
|
||||
path: imageSnapshots/
|
||||
retention-days: 3
|
||||
|
||||
- name: Save commit
|
||||
if: github.event_name == 'pull_request' && github.base_ref == 'master'
|
||||
run: echo ${{ github.event.pull_request.head.sha }} > ./commit.txt
|
||||
|
||||
- name: Save commit
|
||||
if: github.event_name == 'push'
|
||||
run: echo ${{ github.sha }} > ./commit.txt
|
||||
|
||||
- name: Upload commit
|
||||
if: ${{ always() }}
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: commit
|
||||
path: ./commit.txt
|
||||
|
||||
- name: Save branch
|
||||
if: github.event_name == 'pull_request' && github.base_ref == 'master'
|
||||
run: echo pull/${{ github.event.pull_request.number }}/merge > ./branch.txt
|
||||
|
||||
- name: Save branch
|
||||
if: github.event_name == 'push'
|
||||
run: echo ${GITHUB_REF##*/} > ./branch.txt
|
||||
|
||||
- name: Upload branch
|
||||
if: ${{ always() }}
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: branch
|
||||
path: ./branch.txt
|
@ -15,6 +15,25 @@ timeline: true
|
||||
|
||||
---
|
||||
|
||||
## 4.23.6
|
||||
|
||||
`2022-10-17`
|
||||
|
||||
- Table
|
||||
- 🐞 Fix Table with sticky header shadow style issue. [#38023](https://github.com/ant-design/ant-design/pull/38023) [@liuycy](https://github.com/liuycy)
|
||||
- 🐞 Fix Table with `ellipsis` missing `title` attribute. [416c61f](https://github.com/ant-design/ant-design/commit/416c61f)
|
||||
- 🐞 Fix Breadcrumb not support number `0`. [#38006](https://github.com/ant-design/ant-design/pull/38006) [@li-jia-nan](https://github.com/li-jia-nan)
|
||||
- Input
|
||||
- 🐞 Fix Input.TextArea render extra input node when enable `autoSize`. [#38050](https://github.com/ant-design/ant-design/pull/38050)
|
||||
- 🐞 Fix Input.Password that should not have value prop on input after click toggle icon. [#37900](https://github.com/ant-design/ant-design/pull/37900) [@linxianxi](https://github.com/linxianxi)
|
||||
- 💄 Fix border style issues for Input.Search in RTL. [#37980](https://github.com/ant-design/ant-design/pull/37980) [@foryuki](https://github.com/foryuki)
|
||||
- 🐞 Fix AutoComplete warning for unused `dropdownClassName`. [#37974](https://github.com/ant-design/ant-design/pull/37974) [@heiyu4585](https://github.com/heiyu4585)
|
||||
- 🐞 Fix Typography with ellipsis that the computed `fontSize` style be calculated as empty string in some case. [#37928](https://github.com/ant-design/ant-design/pull/37928) [@zheeeng](https://github.com/zheeeng)
|
||||
- 🐞 Fix editable Tabs add button missing in edge case. [#37937](https://github.com/ant-design/ant-design/pull/37937)
|
||||
- 🐞 Fix RangePicker panel blink in some case. [#439](https://github.com/react-component/picker/pull/439)
|
||||
- 🛠 Refactor Spin with Function Component. [#37969](https://github.com/ant-design/ant-design/pull/37969) [@li-jia-nan](https://github.com/li-jia-nan)
|
||||
- 🛠 Refactor Statistic.Countdown with Function Component. [#37938](https://github.com/ant-design/ant-design/pull/37938) [@li-jia-nan](https://github.com/li-jia-nan)
|
||||
|
||||
## 4.23.5
|
||||
|
||||
`2022-10-10`
|
||||
|
@ -15,6 +15,25 @@ timeline: true
|
||||
|
||||
---
|
||||
|
||||
## 4.23.6
|
||||
|
||||
`2022-10-17`
|
||||
|
||||
- Table
|
||||
- 🐞 修复 Table 配置固定表头时的阴影样式问题。[#38023](https://github.com/ant-design/ant-design/pull/38023) [@liuycy](https://github.com/liuycy)
|
||||
- 🐞 修复 Table 配置省略时丢失 `title` 属性问题。[416c61f](https://github.com/ant-design/ant-design/commit/416c61f)
|
||||
- 🐞 修复 Breadcrumb 不支持数字 `0` 的问题。[#38006](https://github.com/ant-design/ant-design/pull/38006) [@li-jia-nan](https://github.com/li-jia-nan)
|
||||
- Input
|
||||
- 🐞 修复 Input.TextArea 配置 `autoSize` 时会额外渲染 input 节点的问题。[#38050](https://github.com/ant-design/ant-design/pull/38050)
|
||||
- 🐞 修复 Input.Password 在点击隐藏按钮后 input 上会有 value 属性的问题。[#37900](https://github.com/ant-design/ant-design/pull/37900) [@linxianxi](https://github.com/linxianxi)
|
||||
- 💄 修复 RTL 下 Input.Search 边框样式问题。[#37980](https://github.com/ant-design/ant-design/pull/37980) [@foryuki](https://github.com/foryuki)
|
||||
- 🐞 修复 AutoComplete 会报未使用的废弃属性 `dropdownClassName` 的问题。[#37974](https://github.com/ant-design/ant-design/pull/37974) [@heiyu4585](https://github.com/heiyu4585)
|
||||
- 🐞 修复 Typography 省略算法在计算一些元素 fontSize 时为空字符串的情况[#37928](https://github.com/ant-design/ant-design/pull/37928) [@zheeeng](https://github.com/zheeeng)
|
||||
- 🐞 Fix Tabs 添加按钮在某些边界情况下无法展示的问题。[#37937](https://github.com/ant-design/ant-design/pull/37937)
|
||||
- 🐞 修复 RangePicker 在某些情况下面板会闪烁的问题。[#439](https://github.com/react-component/picker/pull/439)
|
||||
- 🛠 重构 Spin 为 Function Component。[#37969](https://github.com/ant-design/ant-design/pull/37969) [@li-jia-nan](https://github.com/li-jia-nan)
|
||||
- 🛠 重构 Statistic.Countdown 为 Function Component.[#37938](https://github.com/ant-design/ant-design/pull/37938) [@li-jia-nan](https://github.com/li-jia-nan)
|
||||
|
||||
## 4.23.5
|
||||
|
||||
`2022-10-10`
|
||||
|
@ -1,6 +0,0 @@
|
||||
FROM buildkite/puppeteer:10.0.0
|
||||
RUN mkdir /app
|
||||
WORKDIR /app
|
||||
COPY package.json ./
|
||||
ENV PATH="${PATH}:/app/node_modules/.bin"
|
||||
COPY . .
|
@ -3,7 +3,7 @@ import * as React from 'react';
|
||||
export const { isValidElement } = React;
|
||||
|
||||
export function isFragment(child: React.ReactElement): boolean {
|
||||
return child && child.type === React.Fragment;
|
||||
return child && isValidElement(child) && child.type === React.Fragment;
|
||||
}
|
||||
|
||||
type AnyObject = Record<PropertyKey, any>;
|
||||
|
@ -37,10 +37,10 @@ function getOffsetTop(element: HTMLElement, container: AnchorContainer): number
|
||||
|
||||
const sharpMatcherRegx = /#([\S ]+)$/;
|
||||
|
||||
type Section = {
|
||||
interface Section {
|
||||
link: string;
|
||||
top: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface AnchorProps {
|
||||
prefixCls?: string;
|
||||
@ -92,11 +92,6 @@ export interface AntAnchor {
|
||||
}
|
||||
|
||||
class Anchor extends React.Component<InternalAnchorProps, AnchorState, ConfigConsumerProps> {
|
||||
static defaultProps = {
|
||||
affix: true,
|
||||
showInkInFixed: false,
|
||||
};
|
||||
|
||||
static contextType = ConfigContext;
|
||||
|
||||
state = {
|
||||
@ -114,20 +109,20 @@ class Anchor extends React.Component<InternalAnchorProps, AnchorState, ConfigCon
|
||||
|
||||
private links: string[] = [];
|
||||
|
||||
private scrollEvent: any;
|
||||
private scrollEvent: ReturnType<typeof addEventListener>;
|
||||
|
||||
private animating: boolean;
|
||||
|
||||
private prefixCls?: string;
|
||||
|
||||
// Context
|
||||
registerLink = (link: string) => {
|
||||
registerLink: AntAnchor['registerLink'] = link => {
|
||||
if (!this.links.includes(link)) {
|
||||
this.links.push(link);
|
||||
}
|
||||
};
|
||||
|
||||
unregisterLink = (link: string) => {
|
||||
unregisterLink: AntAnchor['unregisterLink'] = link => {
|
||||
const index = this.links.indexOf(link);
|
||||
if (index !== -1) {
|
||||
this.links.splice(index, 1);
|
||||
@ -177,7 +172,7 @@ class Anchor extends React.Component<InternalAnchorProps, AnchorState, ConfigCon
|
||||
const linkSections: Array<Section> = [];
|
||||
const container = this.getContainer();
|
||||
this.links.forEach(link => {
|
||||
const sharpLinkMatch = sharpMatcherRegx.exec(link.toString());
|
||||
const sharpLinkMatch = sharpMatcherRegx.exec(link?.toString());
|
||||
if (!sharpLinkMatch) {
|
||||
return;
|
||||
}
|
||||
@ -185,10 +180,7 @@ class Anchor extends React.Component<InternalAnchorProps, AnchorState, ConfigCon
|
||||
if (target) {
|
||||
const top = getOffsetTop(target, container);
|
||||
if (top < offsetTop + bounds) {
|
||||
linkSections.push({
|
||||
link,
|
||||
top,
|
||||
});
|
||||
linkSections.push({ link, top });
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -262,10 +254,9 @@ class Anchor extends React.Component<InternalAnchorProps, AnchorState, ConfigCon
|
||||
updateInk = () => {
|
||||
const { prefixCls, wrapperRef } = this;
|
||||
const anchorNode = wrapperRef.current;
|
||||
const linkNode = anchorNode?.getElementsByClassName(`${prefixCls}-link-title-active`)[0];
|
||||
|
||||
const linkNode = anchorNode?.querySelector<HTMLElement>(`.${prefixCls}-link-title-active`);
|
||||
if (linkNode) {
|
||||
this.inkNode.style.top = `${(linkNode as any).offsetTop + linkNode.clientHeight / 2 - 4.5}px`;
|
||||
this.inkNode.style.top = `${linkNode.offsetTop + linkNode.clientHeight / 2 - 4.5}px`;
|
||||
}
|
||||
};
|
||||
|
||||
@ -286,8 +277,8 @@ class Anchor extends React.Component<InternalAnchorProps, AnchorState, ConfigCon
|
||||
className = '',
|
||||
style,
|
||||
offsetTop,
|
||||
affix,
|
||||
showInkInFixed,
|
||||
affix = true,
|
||||
showInkInFixed = false,
|
||||
children,
|
||||
onClick,
|
||||
rootClassName,
|
||||
@ -316,7 +307,7 @@ class Anchor extends React.Component<InternalAnchorProps, AnchorState, ConfigCon
|
||||
[`${prefixCls}-fixed`]: !affix && !showInkInFixed,
|
||||
});
|
||||
|
||||
const wrapperStyle = {
|
||||
const wrapperStyle: React.CSSProperties = {
|
||||
maxHeight: offsetTop ? `calc(100vh - ${offsetTop}px)` : '100vh',
|
||||
...style,
|
||||
};
|
||||
@ -336,24 +327,24 @@ class Anchor extends React.Component<InternalAnchorProps, AnchorState, ConfigCon
|
||||
|
||||
return (
|
||||
<AnchorContext.Provider value={contextValue}>
|
||||
{!affix ? (
|
||||
anchorContent
|
||||
) : (
|
||||
{affix ? (
|
||||
<Affix offsetTop={offsetTop} target={this.getContainer}>
|
||||
{anchorContent}
|
||||
</Affix>
|
||||
) : (
|
||||
anchorContent
|
||||
)}
|
||||
</AnchorContext.Provider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// just use in test
|
||||
export type InternalAnchorClass = Anchor;
|
||||
|
||||
const AnchorFC = React.forwardRef<Anchor, AnchorProps>((props, ref) => {
|
||||
const { prefixCls: customizePrefixCls } = props;
|
||||
const { getPrefixCls } = React.useContext(ConfigContext);
|
||||
|
||||
const anchorPrefixCls = getPrefixCls('anchor', customizePrefixCls);
|
||||
|
||||
const [wrapSSR, hashId] = useStyle(anchorPrefixCls);
|
||||
|
@ -14,71 +14,53 @@ export interface AnchorLinkProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
class AnchorLink extends React.Component<AnchorLinkProps, any, AntAnchor> {
|
||||
static defaultProps = {
|
||||
href: '#',
|
||||
};
|
||||
const AnchorLink: React.FC<AnchorLinkProps> = props => {
|
||||
const { href = '#', title, prefixCls: customizePrefixCls, children, className, target } = props;
|
||||
|
||||
static contextType = AnchorContext;
|
||||
const context = React.useContext<AntAnchor | undefined>(AnchorContext);
|
||||
|
||||
context: AntAnchor;
|
||||
const { registerLink, unregisterLink, scrollTo, onClick, activeLink } = context || {};
|
||||
|
||||
componentDidMount() {
|
||||
this.context.registerLink(this.props.href);
|
||||
}
|
||||
React.useEffect(() => {
|
||||
registerLink?.(href);
|
||||
return () => {
|
||||
unregisterLink?.(href);
|
||||
};
|
||||
}, [href]);
|
||||
|
||||
componentDidUpdate({ href: prevHref }: AnchorLinkProps) {
|
||||
const { href } = this.props;
|
||||
if (prevHref !== href) {
|
||||
this.context.unregisterLink(prevHref);
|
||||
this.context.registerLink(href);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.context.unregisterLink(this.props.href);
|
||||
}
|
||||
|
||||
handleClick = (e: React.MouseEvent<HTMLElement>) => {
|
||||
const { scrollTo, onClick } = this.context;
|
||||
const { href, title } = this.props;
|
||||
const handleClick = (e: React.MouseEvent<HTMLElement>) => {
|
||||
onClick?.(e, { title, href });
|
||||
scrollTo(href);
|
||||
scrollTo?.(href);
|
||||
};
|
||||
|
||||
renderAnchorLink = ({ getPrefixCls }: ConfigConsumerProps) => {
|
||||
const { prefixCls: customizePrefixCls, href, title, children, className, target } = this.props;
|
||||
const prefixCls = getPrefixCls('anchor', customizePrefixCls);
|
||||
const active = this.context.activeLink === href;
|
||||
const wrapperClassName = classNames(
|
||||
`${prefixCls}-link`,
|
||||
{
|
||||
[`${prefixCls}-link-active`]: active,
|
||||
},
|
||||
className,
|
||||
);
|
||||
const titleClassName = classNames(`${prefixCls}-link-title`, {
|
||||
[`${prefixCls}-link-title-active`]: active,
|
||||
});
|
||||
return (
|
||||
<div className={wrapperClassName}>
|
||||
<a
|
||||
className={titleClassName}
|
||||
href={href}
|
||||
title={typeof title === 'string' ? title : ''}
|
||||
target={target}
|
||||
onClick={this.handleClick}
|
||||
>
|
||||
{title}
|
||||
</a>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
return <ConfigConsumer>{this.renderAnchorLink}</ConfigConsumer>;
|
||||
}
|
||||
}
|
||||
return (
|
||||
<ConfigConsumer>
|
||||
{({ getPrefixCls }: ConfigConsumerProps) => {
|
||||
const prefixCls = getPrefixCls('anchor', customizePrefixCls);
|
||||
const active = activeLink === href;
|
||||
const wrapperClassName = classNames(`${prefixCls}-link`, className, {
|
||||
[`${prefixCls}-link-active`]: active,
|
||||
});
|
||||
const titleClassName = classNames(`${prefixCls}-link-title`, {
|
||||
[`${prefixCls}-link-title-active`]: active,
|
||||
});
|
||||
return (
|
||||
<div className={wrapperClassName}>
|
||||
<a
|
||||
className={titleClassName}
|
||||
href={href}
|
||||
title={typeof title === 'string' ? title : ''}
|
||||
target={target}
|
||||
onClick={handleClick}
|
||||
>
|
||||
{title}
|
||||
</a>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</ConfigConsumer>
|
||||
);
|
||||
};
|
||||
|
||||
export default AnchorLink;
|
||||
|
@ -41,11 +41,17 @@ describe('Anchor Render', () => {
|
||||
getClientRectsMock.mockReturnValue({ length: 1 } as DOMRectList);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllTimers();
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.clearAllTimers();
|
||||
jest.useRealTimers();
|
||||
getBoundingClientRectMock.mockRestore();
|
||||
getClientRectsMock.mockRestore();
|
||||
@ -69,7 +75,7 @@ describe('Anchor Render', () => {
|
||||
expect(anchorInstance!.state).not.toBe(null);
|
||||
});
|
||||
|
||||
it('Anchor render perfectly for complete href - click', () => {
|
||||
it('Anchor render perfectly for complete href - click', async () => {
|
||||
const hash = getHashUrl();
|
||||
let anchorInstance: InternalAnchorClass;
|
||||
const { container } = render(
|
||||
@ -82,6 +88,7 @@ describe('Anchor Render', () => {
|
||||
</Anchor>,
|
||||
);
|
||||
fireEvent.click(container.querySelector(`a[href="http://www.example.com/#${hash}"]`)!);
|
||||
await waitFakeTimer();
|
||||
expect(anchorInstance!.state!.activeLink).toBe(`http://www.example.com/#${hash}`);
|
||||
});
|
||||
|
||||
@ -100,13 +107,12 @@ describe('Anchor Render', () => {
|
||||
</Anchor>,
|
||||
);
|
||||
anchorInstance!.handleScrollTo('/#/faq?locale=en#Q1');
|
||||
expect(anchorInstance!.state.activeLink).toBe('/#/faq?locale=en#Q1');
|
||||
expect(scrollToSpy).not.toHaveBeenCalled();
|
||||
await waitFakeTimer();
|
||||
expect(anchorInstance!.state.activeLink).toBe('/#/faq?locale=en#Q1');
|
||||
expect(scrollToSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('Anchor render perfectly for complete href - scroll', () => {
|
||||
it('Anchor render perfectly for complete href - scroll', async () => {
|
||||
const hash = getHashUrl();
|
||||
const root = createDiv();
|
||||
render(<div id={hash}>Hello</div>, { container: root });
|
||||
@ -121,6 +127,7 @@ describe('Anchor Render', () => {
|
||||
</Anchor>,
|
||||
);
|
||||
anchorInstance!.handleScroll();
|
||||
await waitFakeTimer();
|
||||
expect(anchorInstance!.state!.activeLink).toBe(`http://www.example.com/#${hash}`);
|
||||
});
|
||||
|
||||
@ -141,10 +148,10 @@ describe('Anchor Render', () => {
|
||||
);
|
||||
|
||||
anchorInstance!.handleScrollTo(`##${hash}`);
|
||||
await waitFakeTimer();
|
||||
expect(anchorInstance!.state.activeLink).toBe(`##${hash}`);
|
||||
const calls = scrollToSpy.mock.calls.length;
|
||||
await waitFakeTimer();
|
||||
expect(scrollToSpy.mock.calls.length).toBeGreaterThan(calls);
|
||||
expect(scrollToSpy.mock.calls.length).toBe(calls);
|
||||
});
|
||||
|
||||
it('should remove listener when unmount', async () => {
|
||||
@ -373,7 +380,7 @@ describe('Anchor Render', () => {
|
||||
|
||||
it('Anchor targetOffset prop', async () => {
|
||||
const hash = getHashUrl();
|
||||
let dateNowMock;
|
||||
let dateNowMock: jest.SpyInstance;
|
||||
|
||||
function dataNowMockFn() {
|
||||
let start = 0;
|
||||
@ -438,7 +445,7 @@ describe('Anchor Render', () => {
|
||||
// https://github.com/ant-design/ant-design/issues/31941
|
||||
it('Anchor targetOffset prop when contain spaces', async () => {
|
||||
const hash = `${getHashUrl()} s p a c e s`;
|
||||
let dateNowMock;
|
||||
let dateNowMock: jest.SpyInstance;
|
||||
|
||||
function dataNowMockFn() {
|
||||
let start = 0;
|
||||
@ -543,13 +550,9 @@ describe('Anchor Render', () => {
|
||||
});
|
||||
|
||||
it('test edge case when getBoundingClientRect return zero size', async () => {
|
||||
getBoundingClientRectMock.mockReturnValue({
|
||||
width: 0,
|
||||
height: 0,
|
||||
top: 1000,
|
||||
} as DOMRect);
|
||||
getBoundingClientRectMock.mockReturnValue({ width: 0, height: 0, top: 1000 } as DOMRect);
|
||||
const hash = getHashUrl();
|
||||
let dateNowMock;
|
||||
let dateNowMock: jest.SpyInstance;
|
||||
|
||||
function dataNowMockFn() {
|
||||
let start = 0;
|
||||
@ -615,7 +618,7 @@ describe('Anchor Render', () => {
|
||||
|
||||
it('test edge case when container is not windows', async () => {
|
||||
const hash = getHashUrl();
|
||||
let dateNowMock;
|
||||
let dateNowMock: jest.SpyInstance;
|
||||
|
||||
function dataNowMockFn() {
|
||||
let start = 0;
|
||||
@ -758,5 +761,15 @@ describe('Anchor Render', () => {
|
||||
rerender(<Demo current={hash2} />);
|
||||
expect(container.querySelector(`.ant-anchor-link-title-active`)?.textContent).toBe(hash2);
|
||||
});
|
||||
it('should correct render when href is null', () => {
|
||||
expect(() => {
|
||||
render(
|
||||
<Anchor>
|
||||
<Link href={null as unknown as string} title="test" />
|
||||
</Anchor>,
|
||||
);
|
||||
fireEvent.scroll(window || document);
|
||||
}).not.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,6 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import type { AntAnchor } from './Anchor';
|
||||
|
||||
const AnchorContext = React.createContext<AntAnchor>(null as any);
|
||||
const AnchorContext = React.createContext<AntAnchor | undefined>(undefined);
|
||||
|
||||
export default AnchorContext;
|
||||
|
@ -619,37 +619,48 @@ describe('Form', () => {
|
||||
jest.useFakeTimers();
|
||||
|
||||
const shouldNotRender = jest.fn();
|
||||
const StaticInput: React.FC = () => {
|
||||
const StaticInput: React.FC<React.InputHTMLAttributes<HTMLInputElement>> = ({
|
||||
id,
|
||||
value = '',
|
||||
}) => {
|
||||
shouldNotRender();
|
||||
return <Input />;
|
||||
return <input id={id} value={value} />;
|
||||
};
|
||||
|
||||
const shouldRender = jest.fn();
|
||||
const DynamicInput: React.FC = () => {
|
||||
const DynamicInput: React.FC<React.InputHTMLAttributes<HTMLInputElement>> = ({
|
||||
value = '',
|
||||
id,
|
||||
}) => {
|
||||
shouldRender();
|
||||
return <Input />;
|
||||
return <input id={id} value={value} />;
|
||||
};
|
||||
|
||||
const formRef = React.createRef<FormInstance>();
|
||||
|
||||
pureRender(
|
||||
const { container } = pureRender(
|
||||
<Form ref={formRef}>
|
||||
<Form.Item>
|
||||
<StaticInput />
|
||||
</Form.Item>
|
||||
<Form.Item name="light">
|
||||
<DynamicInput />
|
||||
<DynamicInput id="changed" />
|
||||
</Form.Item>
|
||||
</Form>,
|
||||
);
|
||||
|
||||
await waitFakeTimer();
|
||||
|
||||
expect(container.querySelector<HTMLInputElement>('#changed')!.value).toEqual('');
|
||||
expect(shouldNotRender).toHaveBeenCalledTimes(1);
|
||||
expect(shouldRender).toHaveBeenCalledTimes(1);
|
||||
|
||||
formRef.current?.setFieldsValue({ light: 'bamboo' });
|
||||
formRef.current!.setFieldsValue({ light: 'bamboo' });
|
||||
await waitFakeTimer(100, 100);
|
||||
|
||||
await waitFakeTimer();
|
||||
expect(formRef.current!.getFieldsValue()).toEqual({ light: 'bamboo' });
|
||||
|
||||
expect(container.querySelector<HTMLInputElement>('#changed')!.value).toEqual('bamboo');
|
||||
expect(shouldNotRender).toHaveBeenCalledTimes(1);
|
||||
expect(shouldRender).toHaveBeenCalledTimes(2);
|
||||
|
||||
|
@ -18,7 +18,8 @@ const localeValues: Locale = {
|
||||
filterConfirm: 'OK',
|
||||
filterReset: 'Réinitialiser',
|
||||
filterEmptyText: 'Aucun filtre',
|
||||
filterCheckall: 'Sélectionner la page actuelle',
|
||||
filterCheckall: 'Tout sélectionner',
|
||||
filterSearchPlaceholder: 'Chercher dans les filtres',
|
||||
emptyText: 'Aucune donnée',
|
||||
selectAll: 'Sélectionner la page actuelle',
|
||||
selectInvert: 'Inverser la sélection de la page actuelle',
|
||||
|
@ -146,7 +146,7 @@ One of the Table `columns` prop for describing the table's columns, Column has t
|
||||
| showSorterTooltip | If header show next sorter direction tooltip, override `showSorterTooltip` in table | boolean \| [Tooltip props](/components/tooltip/) | true | |
|
||||
| sortDirections | Supported sort way, override `sortDirections` in `Table`, could be `ascend`, `descend` | Array | \[`ascend`, `descend`] | |
|
||||
| sorter | Sort function for local sort, see [Array.sort](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)'s compareFunction. If you need sort buttons only, set to `true` | function \| boolean | - | |
|
||||
| sortOrder | Order of sorted values: `'ascend'` `'descend'` `false` | boolean \| string | - | |
|
||||
| sortOrder | Order of sorted values: `ascend` `descend` `null` | `ascend` \| `descend` \| null | - | |
|
||||
| title | Title of this column | ReactNode \| ({ sortOrder, sortColumn, filters }) => ReactNode | - | |
|
||||
| width | Width of this column ([width not working?](https://github.com/ant-design/ant-design/issues/13825#issuecomment-449889241)) | string \| number | - | |
|
||||
| onCell | Set props on per cell | function(record, rowIndex) | - | |
|
||||
|
@ -147,7 +147,7 @@ const columns = [
|
||||
| showSorterTooltip | 表头显示下一次排序的 tooltip 提示, 覆盖 table 中 `showSorterTooltip` | boolean \| [Tooltip props](/components/tooltip/#API) | true | |
|
||||
| sortDirections | 支持的排序方式,覆盖 `Table` 中 `sortDirections`, 取值为 `ascend` `descend` | Array | \[`ascend`, `descend`] | |
|
||||
| sorter | 排序函数,本地排序使用一个函数(参考 [Array.sort](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) 的 compareFunction),需要服务端排序可设为 true | function \| boolean | - | |
|
||||
| sortOrder | 排序的受控属性,外界可用此控制列的排序,可设置为 `ascend` `descend` false | boolean \| string | - | |
|
||||
| sortOrder | 排序的受控属性,外界可用此控制列的排序,可设置为 `ascend` `descend` `null` | `ascend` \| `descend` \| null | - | |
|
||||
| title | 列头显示文字(函数用法 `3.10.0` 后支持) | ReactNode \| ({ sortOrder, sortColumn, filters }) => ReactNode | - | |
|
||||
| width | 列宽度([指定了也不生效?](https://github.com/ant-design/ant-design/issues/13825#issuecomment-449889241)) | string \| number | - | |
|
||||
| onCell | 设置单元格属性 | function(record, rowIndex) | - | |
|
||||
|
@ -111,7 +111,7 @@ File icon realize by using switcherIcon. You can overwrite the style to hide it:
|
||||
|
||||
### Why defaultExpandAll not working on ajax data?
|
||||
|
||||
`default` prefix prop only works when inited. So `defaultExpandAll` has already executed when ajax load data. You can control `expandedKeys` or render Tree when data loaded to realize expanded all.
|
||||
`default` prefix prop only works when initializing. So `defaultExpandAll` has already executed when ajax load data. You can control `expandedKeys` or render Tree when data loaded to realize expanded all.
|
||||
|
||||
### Virtual scroll limitation
|
||||
|
||||
|
@ -67,6 +67,13 @@ const WALKING = 2;
|
||||
const DONE_WITH_ELLIPSIS = 3;
|
||||
const DONE_WITHOUT_ELLIPSIS = 4;
|
||||
|
||||
type WalkingState =
|
||||
| typeof NONE
|
||||
| typeof PREPARE
|
||||
| typeof WALKING
|
||||
| typeof DONE_WITH_ELLIPSIS
|
||||
| typeof DONE_WITHOUT_ELLIPSIS;
|
||||
|
||||
const Ellipsis = ({
|
||||
enabledMeasure,
|
||||
children,
|
||||
@ -76,20 +83,15 @@ const Ellipsis = ({
|
||||
rows,
|
||||
onEllipsis,
|
||||
}: EllipsisProps) => {
|
||||
const [cutLength, setCutLength] = React.useState<[number, number, number]>([0, 0, 0]);
|
||||
const [walkingState, setWalkingState] = React.useState<
|
||||
| typeof NONE
|
||||
| typeof PREPARE
|
||||
| typeof WALKING
|
||||
| typeof DONE_WITH_ELLIPSIS
|
||||
| typeof DONE_WITHOUT_ELLIPSIS
|
||||
>(NONE);
|
||||
const [startLen, midLen, endLen] = cutLength;
|
||||
const [[startLen, midLen, endLen], setCutLength] = React.useState<
|
||||
[startLen: number, midLen: number, endLen: number]
|
||||
>([0, 0, 0]);
|
||||
const [walkingState, setWalkingState] = React.useState<WalkingState>(NONE);
|
||||
|
||||
const [singleRowHeight, setSingleRowHeight] = React.useState(0);
|
||||
|
||||
const singleRowRef = React.useRef<HTMLSpanElement>(null);
|
||||
const midRowRef = React.useRef<HTMLSpanElement>(null);
|
||||
const singleRowRef = React.useRef<HTMLElement>(null);
|
||||
const midRowRef = React.useRef<HTMLElement>(null);
|
||||
|
||||
const nodeList = React.useMemo(() => toArray(text), [text]);
|
||||
const totalLen = React.useMemo(() => getNodesLen(nodeList), [nodeList]);
|
||||
@ -167,7 +169,7 @@ const Ellipsis = ({
|
||||
|
||||
const renderMeasure = (
|
||||
content: React.ReactNode,
|
||||
ref: React.Ref<HTMLSpanElement>,
|
||||
ref: React.Ref<HTMLElement>,
|
||||
style: React.CSSProperties,
|
||||
) => (
|
||||
<span
|
||||
@ -189,7 +191,7 @@ const Ellipsis = ({
|
||||
</span>
|
||||
);
|
||||
|
||||
const renderMeasureSlice = (len: number, ref: React.Ref<HTMLSpanElement>) => {
|
||||
const renderMeasureSlice = (len: number, ref: React.Ref<HTMLElement>) => {
|
||||
const sliceNodeList = sliceNodes(nodeList, len);
|
||||
|
||||
return renderMeasure(children(sliceNodeList, true), ref, measureStyle);
|
||||
|
@ -59,7 +59,8 @@ export interface EllipsisConfig {
|
||||
tooltip?: React.ReactNode | TooltipProps;
|
||||
}
|
||||
|
||||
export interface BlockProps extends TypographyProps {
|
||||
export interface BlockProps<C extends keyof JSX.IntrinsicElements = keyof JSX.IntrinsicElements>
|
||||
extends TypographyProps<C> {
|
||||
title?: string;
|
||||
editable?: boolean | EditConfig;
|
||||
copyable?: boolean | CopyConfig;
|
||||
@ -113,13 +114,9 @@ function toList<T extends any>(val: T | T[]): T[] {
|
||||
return Array.isArray(val) ? val : [val];
|
||||
}
|
||||
|
||||
interface InternalBlockProps extends BlockProps {
|
||||
component: string;
|
||||
}
|
||||
|
||||
const ELLIPSIS_STR = '...';
|
||||
|
||||
const Base = React.forwardRef((props: InternalBlockProps, ref: any) => {
|
||||
const Base = React.forwardRef<HTMLElement, BlockProps>((props, ref) => {
|
||||
const {
|
||||
prefixCls: customizePrefixCls,
|
||||
className,
|
||||
@ -151,7 +148,7 @@ const Base = React.forwardRef((props: InternalBlockProps, ref: any) => {
|
||||
'strong',
|
||||
'keyboard',
|
||||
'italic',
|
||||
]) as any;
|
||||
]);
|
||||
|
||||
// ========================== Editable ==========================
|
||||
const [enableEdit, editConfig] = useMergedConfig<EditConfig>(editable);
|
||||
@ -175,7 +172,7 @@ const Base = React.forwardRef((props: InternalBlockProps, ref: any) => {
|
||||
}
|
||||
}, [editing]);
|
||||
|
||||
const onEditClick = (e?: React.MouseEvent<HTMLDivElement>) => {
|
||||
const onEditClick = (e?: React.MouseEvent<HTMLElement>) => {
|
||||
e?.preventDefault();
|
||||
triggerEdit(true);
|
||||
};
|
||||
@ -193,7 +190,7 @@ const Base = React.forwardRef((props: InternalBlockProps, ref: any) => {
|
||||
// ========================== Copyable ==========================
|
||||
const [enableCopy, copyConfig] = useMergedConfig<CopyConfig>(copyable);
|
||||
const [copied, setCopied] = React.useState(false);
|
||||
const copyIdRef = React.useRef<NodeJS.Timeout>();
|
||||
const copyIdRef = React.useRef<number>();
|
||||
|
||||
const copyOptions: Pick<CopyConfig, 'format'> = {};
|
||||
if (copyConfig.format) {
|
||||
@ -201,7 +198,7 @@ const Base = React.forwardRef((props: InternalBlockProps, ref: any) => {
|
||||
}
|
||||
|
||||
const cleanCopyId = () => {
|
||||
clearTimeout(copyIdRef.current!);
|
||||
window.clearTimeout(copyIdRef.current!);
|
||||
};
|
||||
|
||||
const onCopyClick = (e?: React.MouseEvent<HTMLDivElement>) => {
|
||||
@ -214,7 +211,7 @@ const Base = React.forwardRef((props: InternalBlockProps, ref: any) => {
|
||||
|
||||
// Trigger tips update
|
||||
cleanCopyId();
|
||||
copyIdRef.current = setTimeout(() => {
|
||||
copyIdRef.current = window.setTimeout(() => {
|
||||
setCopied(false);
|
||||
}, 3000);
|
||||
|
||||
@ -351,7 +348,7 @@ const Base = React.forwardRef((props: InternalBlockProps, ref: any) => {
|
||||
tooltipProps = { title: ellipsisConfig.tooltip };
|
||||
}
|
||||
const topAriaLabel = React.useMemo(() => {
|
||||
const isValid = (val: any) => ['string', 'number'].includes(typeof val);
|
||||
const isValid = (val: any): val is string | number => ['string', 'number'].includes(typeof val);
|
||||
|
||||
if (!enableEllipsis || cssEllipsis) {
|
||||
return undefined;
|
||||
@ -490,7 +487,7 @@ const Base = React.forwardRef((props: InternalBlockProps, ref: any) => {
|
||||
|
||||
return (
|
||||
<ResizeObserver onResize={onResize} disabled={!mergedEnableEllipsis || cssEllipsis}>
|
||||
{resizeRef => (
|
||||
{(resizeRef: React.RefObject<HTMLElement>) => (
|
||||
<EllipsisTooltip
|
||||
tooltipProps={tooltipProps}
|
||||
enabledEllipsis={mergedEnableEllipsis}
|
||||
@ -515,8 +512,8 @@ const Base = React.forwardRef((props: InternalBlockProps, ref: any) => {
|
||||
component={component}
|
||||
ref={composeRef(resizeRef, typographyRef, ref)}
|
||||
direction={direction}
|
||||
onClick={triggerType.includes('text') ? onEditClick : null}
|
||||
aria-label={topAriaLabel}
|
||||
onClick={triggerType.includes('text') ? onEditClick : undefined}
|
||||
aria-label={topAriaLabel?.toString()}
|
||||
title={title}
|
||||
{...textProps}
|
||||
>
|
||||
|
@ -5,6 +5,7 @@ import KeyCode from 'rc-util/lib/KeyCode';
|
||||
import * as React from 'react';
|
||||
import type { DirectionType } from '../config-provider';
|
||||
import TextArea from '../input/TextArea';
|
||||
import type { TextAreaRef } from '../input/TextArea';
|
||||
import { cloneElement } from '../_util/reactNode';
|
||||
import useStyle from './style';
|
||||
|
||||
@ -39,7 +40,7 @@ const Editable: React.FC<EditableProps> = ({
|
||||
component,
|
||||
enterIcon = <EnterOutlined />,
|
||||
}) => {
|
||||
const ref = React.useRef<any>();
|
||||
const ref = React.useRef<TextAreaRef>(null);
|
||||
|
||||
const inComposition = React.useRef(false);
|
||||
const lastKeyCode = React.useRef<number>();
|
||||
@ -129,7 +130,7 @@ const Editable: React.FC<EditableProps> = ({
|
||||
return wrapSSR(
|
||||
<div className={textAreaClassName} style={style}>
|
||||
<TextArea
|
||||
ref={ref as any}
|
||||
ref={ref}
|
||||
maxLength={maxLength}
|
||||
value={current}
|
||||
onChange={onChange}
|
||||
|
@ -4,15 +4,12 @@ import type { BlockProps } from './Base';
|
||||
import Base from './Base';
|
||||
|
||||
export interface LinkProps
|
||||
extends BlockProps,
|
||||
Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'type'> {
|
||||
extends BlockProps<'a'>,
|
||||
Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'type' | keyof BlockProps<'a'>> {
|
||||
ellipsis?: boolean;
|
||||
}
|
||||
|
||||
const Link: React.ForwardRefRenderFunction<HTMLElement, LinkProps> = (
|
||||
{ ellipsis, rel, ...restProps },
|
||||
ref,
|
||||
) => {
|
||||
const Link = React.forwardRef<HTMLElement, LinkProps>(({ ellipsis, rel, ...restProps }, ref) => {
|
||||
warning(
|
||||
typeof ellipsis !== 'object',
|
||||
'Typography.Link',
|
||||
@ -28,11 +25,10 @@ const Link: React.ForwardRefRenderFunction<HTMLElement, LinkProps> = (
|
||||
rel: rel === undefined && restProps.target === '_blank' ? 'noopener noreferrer' : rel,
|
||||
};
|
||||
|
||||
// https://github.com/ant-design/ant-design/issues/26622
|
||||
// @ts-ignore
|
||||
// @ts-expect-error: https://github.com/ant-design/ant-design/issues/26622
|
||||
delete mergedProps.navigate;
|
||||
|
||||
return <Base {...mergedProps} ref={baseRef} ellipsis={!!ellipsis} component="a" />;
|
||||
};
|
||||
});
|
||||
|
||||
export default React.forwardRef(Link);
|
||||
export default Link;
|
||||
|
@ -2,12 +2,12 @@ import * as React from 'react';
|
||||
import type { BlockProps } from './Base';
|
||||
import Base from './Base';
|
||||
|
||||
export interface ParagraphProps extends BlockProps {
|
||||
onClick?: (e?: React.MouseEvent<HTMLDivElement>) => void;
|
||||
}
|
||||
export interface ParagraphProps
|
||||
extends BlockProps<'div'>,
|
||||
Omit<React.HTMLAttributes<HTMLDivElement>, 'type' | keyof BlockProps<'div'>> {}
|
||||
|
||||
const Paragraph: React.ForwardRefRenderFunction<HTMLDivElement, ParagraphProps> = (props, ref) => (
|
||||
const Paragraph = React.forwardRef<HTMLElement, ParagraphProps>((props, ref) => (
|
||||
<Base ref={ref} {...props} component="div" />
|
||||
);
|
||||
));
|
||||
|
||||
export default React.forwardRef(Paragraph);
|
||||
export default Paragraph;
|
||||
|
@ -4,9 +4,10 @@ import warning from '../_util/warning';
|
||||
import type { BlockProps, EllipsisConfig } from './Base';
|
||||
import Base from './Base';
|
||||
|
||||
export interface TextProps extends BlockProps {
|
||||
export interface TextProps
|
||||
extends BlockProps<'span'>,
|
||||
Omit<React.HTMLAttributes<HTMLSpanElement>, 'type' | keyof BlockProps<'span'>> {
|
||||
ellipsis?: boolean | Omit<EllipsisConfig, 'expandable' | 'rows' | 'onExpand'>;
|
||||
onClick?: (e?: React.MouseEvent<HTMLDivElement>) => void;
|
||||
}
|
||||
|
||||
const Text: React.ForwardRefRenderFunction<HTMLSpanElement, TextProps> = (
|
||||
|
@ -6,17 +6,18 @@ import Base from './Base';
|
||||
|
||||
const TITLE_ELE_LIST = tupleNum(1, 2, 3, 4, 5);
|
||||
|
||||
export type TitleProps = Omit<
|
||||
BlockProps & {
|
||||
level?: typeof TITLE_ELE_LIST[number];
|
||||
onClick?: (e?: React.MouseEvent<HTMLDivElement>) => void;
|
||||
},
|
||||
'strong'
|
||||
>;
|
||||
export interface TitleProps
|
||||
extends Omit<BlockProps<'h1' | 'h2' | 'h3' | 'h4' | 'h5'>, 'strong'>,
|
||||
Omit<
|
||||
React.HTMLAttributes<HTMLHeadElement>,
|
||||
'type' | keyof BlockProps<'h1' | 'h2' | 'h3' | 'h4' | 'h5'>
|
||||
> {
|
||||
level?: typeof TITLE_ELE_LIST[number];
|
||||
}
|
||||
|
||||
const Title: React.ForwardRefRenderFunction<HTMLHeadingElement, TitleProps> = (props, ref) => {
|
||||
const Title = React.forwardRef<HTMLElement, TitleProps>((props, ref) => {
|
||||
const { level = 1, ...restProps } = props;
|
||||
let component: string;
|
||||
let component: keyof JSX.IntrinsicElements;
|
||||
|
||||
if (TITLE_ELE_LIST.indexOf(level) !== -1) {
|
||||
component = `h${level}`;
|
||||
@ -30,6 +31,6 @@ const Title: React.ForwardRefRenderFunction<HTMLHeadingElement, TitleProps> = (p
|
||||
}
|
||||
|
||||
return <Base ref={ref} {...restProps} component={component} />;
|
||||
};
|
||||
});
|
||||
|
||||
export default React.forwardRef(Title);
|
||||
export default Title;
|
||||
|
@ -1,72 +1,81 @@
|
||||
import classNames from 'classnames';
|
||||
import { composeRef } from 'rc-util/lib/ref';
|
||||
import * as React from 'react';
|
||||
import type { DirectionType } from '../config-provider';
|
||||
import { ConfigContext } from '../config-provider';
|
||||
import warning from '../_util/warning';
|
||||
import useStyle from './style';
|
||||
|
||||
export interface TypographyProps {
|
||||
export interface TypographyProps<C extends keyof JSX.IntrinsicElements>
|
||||
extends React.HTMLAttributes<HTMLElement> {
|
||||
id?: string;
|
||||
prefixCls?: string;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
children?: React.ReactNode;
|
||||
/** @internal */
|
||||
component?: C;
|
||||
['aria-label']?: string;
|
||||
direction?: DirectionType;
|
||||
}
|
||||
|
||||
interface InternalTypographyProps extends TypographyProps {
|
||||
component?: string;
|
||||
interface InternalTypographyProps<C extends keyof JSX.IntrinsicElements>
|
||||
extends TypographyProps<C> {
|
||||
/** @deprecated Use `ref` directly if using React 16 */
|
||||
setContentRef?: (node: HTMLElement) => void;
|
||||
}
|
||||
|
||||
const Typography: React.ForwardRefRenderFunction<{}, InternalTypographyProps> = (
|
||||
{
|
||||
prefixCls: customizePrefixCls,
|
||||
component = 'article',
|
||||
className,
|
||||
'aria-label': ariaLabel,
|
||||
setContentRef,
|
||||
children,
|
||||
...restProps
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const { getPrefixCls, direction } = React.useContext(ConfigContext);
|
||||
|
||||
let mergedRef = ref;
|
||||
if (setContentRef) {
|
||||
warning(false, 'Typography', '`setContentRef` is deprecated. Please use `ref` instead.');
|
||||
mergedRef = composeRef(ref, setContentRef);
|
||||
}
|
||||
|
||||
const Component = component as any;
|
||||
const prefixCls = getPrefixCls('typography', customizePrefixCls);
|
||||
|
||||
// Style
|
||||
const [wrapSSR, hashId] = useStyle(prefixCls);
|
||||
|
||||
const componentClassName = classNames(
|
||||
prefixCls,
|
||||
const Typography = React.forwardRef<
|
||||
HTMLElement,
|
||||
InternalTypographyProps<keyof JSX.IntrinsicElements>
|
||||
>(
|
||||
(
|
||||
{
|
||||
[`${prefixCls}-rtl`]: direction === 'rtl',
|
||||
prefixCls: customizePrefixCls,
|
||||
component: Component = 'article',
|
||||
className,
|
||||
setContentRef,
|
||||
children,
|
||||
direction: typographyDirection,
|
||||
...restProps
|
||||
},
|
||||
className,
|
||||
hashId,
|
||||
);
|
||||
return wrapSSR(
|
||||
<Component className={componentClassName} aria-label={ariaLabel} ref={mergedRef} {...restProps}>
|
||||
{children}
|
||||
</Component>,
|
||||
);
|
||||
};
|
||||
ref,
|
||||
) => {
|
||||
const { getPrefixCls, direction: contextDirection } = React.useContext(ConfigContext);
|
||||
|
||||
const direction = typographyDirection ?? contextDirection;
|
||||
|
||||
let mergedRef = ref;
|
||||
if (setContentRef) {
|
||||
warning(false, 'Typography', '`setContentRef` is deprecated. Please use `ref` instead.');
|
||||
mergedRef = composeRef(ref, setContentRef);
|
||||
}
|
||||
|
||||
// Style
|
||||
const [wrapSSR, hashId] = useStyle(prefixCls);
|
||||
|
||||
const prefixCls = getPrefixCls('typography', customizePrefixCls);
|
||||
const componentClassName = classNames(
|
||||
prefixCls,
|
||||
{
|
||||
[`${prefixCls}-rtl`]: direction === 'rtl',
|
||||
},
|
||||
className,
|
||||
hashId,
|
||||
);
|
||||
|
||||
return wrapSSR(
|
||||
// @ts-expect-error: Expression produces a union type that is too complex to represent.
|
||||
<Component className={componentClassName} ref={mergedRef} {...restProps}>
|
||||
{children}
|
||||
</Component>,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
const RefTypography = React.forwardRef(Typography);
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
RefTypography.displayName = 'Typography';
|
||||
Typography.displayName = 'Typography';
|
||||
}
|
||||
|
||||
// es default export should use const instead of let
|
||||
const ExportTypography = RefTypography as unknown as React.FC<TypographyProps>;
|
||||
|
||||
export default ExportTypography;
|
||||
export default Typography;
|
||||
|
@ -3,27 +3,23 @@
|
||||
exports[`Typography rtl render component should be rendered correctly in RTL direction 1`] = `
|
||||
<div
|
||||
class="ant-typography ant-typography-rtl"
|
||||
direction="rtl"
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`Typography rtl render component should be rendered correctly in RTL direction 2`] = `
|
||||
<article
|
||||
class="ant-typography ant-typography-rtl"
|
||||
direction="rtl"
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`Typography rtl render component should be rendered correctly in RTL direction 3`] = `
|
||||
<h1
|
||||
class="ant-typography ant-typography-rtl"
|
||||
direction="rtl"
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`Typography rtl render component should be rendered correctly in RTL direction 4`] = `
|
||||
<a
|
||||
class="ant-typography ant-typography-rtl"
|
||||
direction="rtl"
|
||||
/>
|
||||
`;
|
||||
|
@ -382,21 +382,14 @@ describe('Typography.Ellipsis', () => {
|
||||
|
||||
it('js ellipsis should show aria-label', () => {
|
||||
const { container: titleWrapper } = render(
|
||||
<Base
|
||||
component={undefined as unknown as string}
|
||||
title="bamboo"
|
||||
ellipsis={{ expandable: true }}
|
||||
/>,
|
||||
<Base component={undefined} title="bamboo" ellipsis={{ expandable: true }} />,
|
||||
);
|
||||
expect(titleWrapper.querySelector('.ant-typography')?.getAttribute('aria-label')).toEqual(
|
||||
'bamboo',
|
||||
);
|
||||
|
||||
const { container: tooltipWrapper } = render(
|
||||
<Base
|
||||
component={undefined as unknown as string}
|
||||
ellipsis={{ expandable: true, tooltip: 'little' }}
|
||||
/>,
|
||||
<Base component={undefined} ellipsis={{ expandable: true, tooltip: 'little' }} />,
|
||||
);
|
||||
expect(tooltipWrapper.querySelector('.ant-typography')?.getAttribute('aria-label')).toEqual(
|
||||
'little',
|
||||
@ -426,11 +419,7 @@ describe('Typography.Ellipsis', () => {
|
||||
|
||||
const ref = React.createRef<any>();
|
||||
const { container, baseElement } = render(
|
||||
<Base
|
||||
component={undefined as unknown as string}
|
||||
ellipsis={{ tooltip: 'This is tooltip', rows: 2 }}
|
||||
ref={ref}
|
||||
>
|
||||
<Base component={undefined} ellipsis={{ tooltip: 'This is tooltip', rows: 2 }} ref={ref}>
|
||||
Ant Design, a design language for background applications, is refined by Ant UED Team.
|
||||
</Base>,
|
||||
);
|
||||
|
@ -1,17 +0,0 @@
|
||||
version: '3'
|
||||
services:
|
||||
tests:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.ui-test
|
||||
volumes:
|
||||
- './node_modules:/app/node_modules'
|
||||
- './components:/app/components'
|
||||
- './tests:/app/tests'
|
||||
- './jest-stare:/app/jest-stare'
|
||||
- './dist:/app/dist'
|
||||
- '.jest.image.js:/app/.jest.image.js'
|
||||
- './jest-puppeteer.config.js:/app/jest-puppeteer.config.js'
|
||||
- './imageSnapshots:/app/imageSnapshots'
|
||||
- './imageDiffSnapshots:/app/imageDiffSnapshots'
|
||||
entrypoint: "npm run test-image:docker"
|
@ -91,8 +91,7 @@
|
||||
"test-node": "npm run version && jest --config .jest.node.js --cache=false",
|
||||
"tsc": "tsc --noEmit",
|
||||
"site:test": "jest --config .jest.site.js --cache=false --force-exit",
|
||||
"test-image": "npm run dist && docker-compose run tests",
|
||||
"test-image:docker": "node node_modules/puppeteer/install.js && jest --config .jest.image.js --no-cache -i",
|
||||
"test-image": "npm run dist && jest --config .jest.image.js --no-cache -i -u",
|
||||
"argos": "node ./scripts/argos-upload.js",
|
||||
"version": "node ./scripts/generate-version",
|
||||
"install-react-16": "npm i --no-save --legacy-peer-deps react@16 react-dom@16",
|
||||
@ -134,7 +133,7 @@
|
||||
"rc-motion": "^2.6.1",
|
||||
"rc-notification": "~5.0.0-alpha.9",
|
||||
"rc-pagination": "~3.1.17",
|
||||
"rc-picker": "~2.6.10",
|
||||
"rc-picker": "~2.6.11",
|
||||
"rc-progress": "~3.3.2",
|
||||
"rc-rate": "~2.9.0",
|
||||
"rc-resize-observer": "^1.2.0",
|
||||
@ -145,7 +144,7 @@
|
||||
"rc-switch": "~4.0.0",
|
||||
"rc-table": "~7.26.0",
|
||||
"rc-tabs": "~12.2.0",
|
||||
"rc-textarea": "~0.4.3",
|
||||
"rc-textarea": "~0.4.5",
|
||||
"rc-tooltip": "~5.2.0",
|
||||
"rc-tree": "~5.7.0",
|
||||
"rc-tree-select": "~5.5.0",
|
||||
@ -188,7 +187,7 @@
|
||||
"@types/warning": "^3.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.40.0",
|
||||
"@typescript-eslint/parser": "^5.40.0",
|
||||
"antd-img-crop": "4.2.5",
|
||||
"antd-img-crop": "^4.2.8",
|
||||
"array-move": "^4.0.0",
|
||||
"babel-plugin-add-react-displayname": "^0.0.5",
|
||||
"bisheng": "^3.7.0-alpha.4",
|
||||
|
@ -47,7 +47,7 @@ async function run() {
|
||||
token: process.env.ARGOS_TOKEN,
|
||||
parallel: {
|
||||
total: chunks.length,
|
||||
nonce: process.env.ARGOS_PARALLEL_NONCE,
|
||||
nonce: process.env.ARGOS_PARALLEL_NONCE || process.env.CIRCLE_BUILD_NUM,
|
||||
},
|
||||
});
|
||||
// eslint-disable-next-line no-console -- pipe stdout
|
||||
|
@ -36,6 +36,7 @@ const MAINTAINERS = [
|
||||
'kerm1it',
|
||||
'madccc',
|
||||
'MadCcc',
|
||||
'li-jia-nan',
|
||||
].map(author => author.toLowerCase());
|
||||
|
||||
const cwd = process.cwd();
|
||||
|
@ -48,12 +48,13 @@ class Demo extends React.Component {
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
const { codeExpand, copied, copyTooltipOpen } = this.state;
|
||||
const { codeExpand, copied, copyTooltipOpen, codeType } = this.state;
|
||||
const { expand, theme, showRiddleButton } = this.props;
|
||||
return (
|
||||
(codeExpand || expand) !== (nextState.codeExpand || nextProps.expand) ||
|
||||
copied !== nextState.copied ||
|
||||
copyTooltipOpen !== nextState.copyTooltipOpen ||
|
||||
codeType !== nextState.copyTooltipOpen ||
|
||||
nextProps.theme !== theme ||
|
||||
nextProps.showRiddleButton !== showRiddleButton
|
||||
);
|
||||
@ -141,7 +142,7 @@ class Demo extends React.Component {
|
||||
theme,
|
||||
showRiddleButton,
|
||||
} = props;
|
||||
const { copied, copyTooltipOpen } = state;
|
||||
const { copied, copyTooltipOpen, codeType } = state;
|
||||
if (!this.liveDemo) {
|
||||
this.liveDemo = meta.iframe ? (
|
||||
<BrowserFrame>
|
||||
@ -183,7 +184,18 @@ class Demo extends React.Component {
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
const tsconfig = `{
|
||||
"compilerOptions": {
|
||||
"jsx": "react-jsx",
|
||||
"target": "esnext",
|
||||
"module": "esnext",
|
||||
"esModuleInterop": true,
|
||||
"moduleResolution": "node",
|
||||
}
|
||||
}`;
|
||||
|
||||
const [sourceCode, sourceCodeTyped] = this.getSourceCode();
|
||||
const suffix = codeType === 'tsx' ? 'tsx' : 'js';
|
||||
|
||||
const dependencies = sourceCode.split('\n').reduce(
|
||||
(acc, line) => {
|
||||
@ -203,6 +215,10 @@ class Demo extends React.Component {
|
||||
);
|
||||
|
||||
dependencies['@ant-design/icons'] = 'latest';
|
||||
if (suffix === 'tsx') {
|
||||
dependencies['@types/react'] = '^18.0.0';
|
||||
dependencies['@types/react-dom'] = '^18.0.0';
|
||||
}
|
||||
dependencies.react = '^18.0.0';
|
||||
dependencies['react-dom'] = '^18.0.0';
|
||||
|
||||
@ -262,7 +278,7 @@ class Demo extends React.Component {
|
||||
};
|
||||
|
||||
// Reorder source code
|
||||
let parsedSourceCode = sourceCode;
|
||||
let parsedSourceCode = suffix === 'tsx' ? sourceCodeTyped : sourceCode;
|
||||
let importReactContent = "import React from 'react';";
|
||||
|
||||
const importReactReg = /import React(\D*)from 'react';/;
|
||||
@ -315,8 +331,8 @@ createRoot(document.getElementById('container')).render(<Demo />);
|
||||
files: {
|
||||
'package.json': { content: codesandboxPackage },
|
||||
'index.css': { content: indexCssContent },
|
||||
'index.js': { content: indexJsContent },
|
||||
'demo.js': { content: demoJsContent },
|
||||
[`index.${suffix}`]: { content: indexJsContent },
|
||||
[`demo.${suffix}`]: { content: demoJsContent },
|
||||
'index.html': {
|
||||
content: html,
|
||||
},
|
||||
@ -328,11 +344,14 @@ createRoot(document.getElementById('container')).render(<Demo />);
|
||||
dependencies,
|
||||
files: {
|
||||
'index.css': indexCssContent,
|
||||
'index.js': indexJsContent,
|
||||
'demo.js': demoJsContent,
|
||||
[`index.${suffix}`]: indexJsContent,
|
||||
[`demo.${suffix}`]: demoJsContent,
|
||||
'index.html': html,
|
||||
},
|
||||
};
|
||||
if (suffix === 'tsx') {
|
||||
stackblitzPrefillConfig.files['tsconfig.json'] = tsconfig;
|
||||
}
|
||||
|
||||
let codeBox = (
|
||||
<section className={codeBoxClass} id={meta.id}>
|
||||
@ -418,7 +437,9 @@ createRoot(document.getElementById('container')).render(<Demo />);
|
||||
className="code-box-code-action"
|
||||
onClick={() => {
|
||||
this.track({ type: 'stackblitz', demo: meta.id });
|
||||
stackblitzSdk.openProject(stackblitzPrefillConfig);
|
||||
stackblitzSdk.openProject(stackblitzPrefillConfig, {
|
||||
openFile: [`demo.${suffix}`],
|
||||
});
|
||||
}}
|
||||
>
|
||||
<ThunderboltOutlined className="code-box-stackblitz" />
|
||||
|
Loading…
Reference in New Issue
Block a user