feat: add text config for editable (#37761)

This commit is contained in:
Zheeeng 2022-10-17 17:00:33 +08:00 committed by GitHub
parent 2559b2fb1b
commit d96ec8aa06
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 730 additions and 13 deletions

View File

@ -36,6 +36,7 @@ interface CopyConfig {
}
interface EditConfig {
text?: string;
editing?: boolean;
icon?: React.ReactNode;
tooltip?: boolean | React.ReactNode;
@ -342,11 +343,11 @@ const Base = React.forwardRef((props: InternalBlockProps, ref: any) => {
// ========================== Tooltip ===========================
let tooltipProps: TooltipProps = {};
if (ellipsisConfig.tooltip === true) {
tooltipProps = { title: children };
tooltipProps = { title: editConfig.text ?? children };
} else if (React.isValidElement(ellipsisConfig.tooltip)) {
tooltipProps = { title: ellipsisConfig.tooltip };
} else if (typeof ellipsisConfig.tooltip === 'object') {
tooltipProps = { title: children, ...ellipsisConfig.tooltip };
tooltipProps = { title: editConfig.text ?? children, ...ellipsisConfig.tooltip };
} else {
tooltipProps = { title: ellipsisConfig.tooltip };
}
@ -357,6 +358,10 @@ const Base = React.forwardRef((props: InternalBlockProps, ref: any) => {
return undefined;
}
if (isValid(editConfig.text)) {
return editConfig.text;
}
if (isValid(children)) {
return children;
}
@ -377,7 +382,7 @@ const Base = React.forwardRef((props: InternalBlockProps, ref: any) => {
if (editing) {
return (
<Editable
value={typeof children === 'string' ? children : ''}
value={editConfig.text ?? (typeof children === 'string' ? children : '')}
onSave={onEditChange}
onCancel={onEditCancel}
onEnd={editConfig.onEnd}

View File

@ -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';
interface EditableProps {
@ -38,7 +39,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>();
@ -125,7 +126,7 @@ const Editable: React.FC<EditableProps> = ({
return (
<div className={textAreaClassName} style={style}>
<TextArea
ref={ref as any}
ref={ref}
maxLength={maxLength}
value={current}
onChange={onChange}

View File

@ -1140,6 +1140,132 @@ Array [
</div>
</div>
</div>,
<div
aria-label="This is a loooooooooooooooooooooooooooooooong editable text with suffix."
class="ant-typography ant-typography-ellipsis ant-typography-single-line"
>
This is a loooooooooooooooooooooooooooooooong editable text
with suffix.
<div
aria-label="Edit"
class="ant-typography-edit"
role="button"
style="border: 0px; background: transparent; padding: 0px; line-height: inherit; display: inline-block;"
tabindex="0"
>
<span
aria-label="edit"
class="anticon anticon-edit"
role="button"
>
<svg
aria-hidden="true"
data-icon="edit"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M257.7 752c2 0 4-.2 6-.5L431.9 722c2-.4 3.9-1.3 5.3-2.8l423.9-423.9a9.96 9.96 0 000-14.1L694.9 114.9c-1.9-1.9-4.4-2.9-7.1-2.9s-5.2 1-7.1 2.9L256.8 538.8c-1.5 1.5-2.4 3.3-2.8 5.3l-29.5 168.2a33.5 33.5 0 009.4 29.8c6.6 6.4 14.9 9.9 23.8 9.9zm67.4-174.4L687.8 215l73.3 73.3-362.7 362.6-88.9 15.7 15.6-89zM880 836H144c-17.7 0-32 14.3-32 32v36c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-36c0-17.7-14.3-32-32-32z"
/>
</svg>
</span>
</div>
<div>
<div
class="ant-tooltip ant-zoom-big-fast-appear ant-zoom-big-fast-appear-prepare ant-zoom-big-fast"
style="opacity: 0;"
>
<div
class="ant-tooltip-content"
>
<div
class="ant-tooltip-arrow"
>
<span
class="ant-tooltip-arrow-content"
/>
</div>
<div
class="ant-tooltip-inner"
role="tooltip"
>
Edit
</div>
</div>
</div>
</div>
<span
aria-hidden="true"
style="position: fixed; display: block; left: 0px; top: 0px; z-index: -9999; visibility: hidden; pointer-events: none; font-size: 14px; word-break: keep-all; white-space: nowrap;"
>
lg
</span>
<span
aria-hidden="true"
style="position: fixed; display: block; left: 0px; top: 0px; z-index: -9999; visibility: hidden; pointer-events: none; font-size: 14px; width: 0px; white-space: normal; margin: 0px; padding: 0px;"
>
<span
aria-hidden="true"
>
...
</span>
with suffix.
<div
aria-label="Edit"
class="ant-typography-edit"
role="button"
style="border: 0px; background: transparent; padding: 0px; line-height: inherit; display: inline-block;"
tabindex="0"
>
<span
aria-label="edit"
class="anticon anticon-edit"
role="button"
>
<svg
aria-hidden="true"
data-icon="edit"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M257.7 752c2 0 4-.2 6-.5L431.9 722c2-.4 3.9-1.3 5.3-2.8l423.9-423.9a9.96 9.96 0 000-14.1L694.9 114.9c-1.9-1.9-4.4-2.9-7.1-2.9s-5.2 1-7.1 2.9L256.8 538.8c-1.5 1.5-2.4 3.3-2.8 5.3l-29.5 168.2a33.5 33.5 0 009.4 29.8c6.6 6.4 14.9 9.9 23.8 9.9zm67.4-174.4L687.8 215l73.3 73.3-362.7 362.6-88.9 15.7 15.6-89zM880 836H144c-17.7 0-32 14.3-32 32v36c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-36c0-17.7-14.3-32-32-32z"
/>
</svg>
</span>
</div>
<div>
<div
class="ant-tooltip ant-zoom-big-fast-appear ant-zoom-big-fast-appear-prepare ant-zoom-big-fast"
style="opacity: 0;"
>
<div
class="ant-tooltip-content"
>
<div
class="ant-tooltip-arrow"
>
<span
class="ant-tooltip-arrow-content"
/>
</div>
<div
class="ant-tooltip-inner"
role="tooltip"
>
Edit
</div>
</div>
</div>
</div>
</span>
</div>,
<div
class="ant-typography"
>

View File

@ -852,6 +852,84 @@ Array [
</span>
</div>
</div>,
<div
aria-label="This is a loooooooooooooooooooooooooooooooong editable text with suffix."
class="ant-typography ant-typography-ellipsis ant-typography-single-line"
>
This is a loooooooooooooooooooooooooooooooong editable text
with suffix.
<div
aria-label="Edit"
class="ant-typography-edit"
role="button"
style="border: 0px; background: transparent; padding: 0px; line-height: inherit; display: inline-block;"
tabindex="0"
>
<span
aria-label="edit"
class="anticon anticon-edit"
role="button"
>
<svg
aria-hidden="true"
data-icon="edit"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M257.7 752c2 0 4-.2 6-.5L431.9 722c2-.4 3.9-1.3 5.3-2.8l423.9-423.9a9.96 9.96 0 000-14.1L694.9 114.9c-1.9-1.9-4.4-2.9-7.1-2.9s-5.2 1-7.1 2.9L256.8 538.8c-1.5 1.5-2.4 3.3-2.8 5.3l-29.5 168.2a33.5 33.5 0 009.4 29.8c6.6 6.4 14.9 9.9 23.8 9.9zm67.4-174.4L687.8 215l73.3 73.3-362.7 362.6-88.9 15.7 15.6-89zM880 836H144c-17.7 0-32 14.3-32 32v36c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-36c0-17.7-14.3-32-32-32z"
/>
</svg>
</span>
</div>
<span
aria-hidden="true"
style="position: fixed; display: block; left: 0px; top: 0px; z-index: -9999; visibility: hidden; pointer-events: none; font-size: 14px; word-break: keep-all; white-space: nowrap;"
>
lg
</span>
<span
aria-hidden="true"
style="position: fixed; display: block; left: 0px; top: 0px; z-index: -9999; visibility: hidden; pointer-events: none; font-size: 14px; width: 0px; white-space: normal; margin: 0px; padding: 0px;"
>
<span
aria-hidden="true"
>
...
</span>
with suffix.
<div
aria-label="Edit"
class="ant-typography-edit"
role="button"
style="border: 0px; background: transparent; padding: 0px; line-height: inherit; display: inline-block;"
tabindex="0"
>
<span
aria-label="edit"
class="anticon anticon-edit"
role="button"
>
<svg
aria-hidden="true"
data-icon="edit"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M257.7 752c2 0 4-.2 6-.5L431.9 722c2-.4 3.9-1.3 5.3-2.8l423.9-423.9a9.96 9.96 0 000-14.1L694.9 114.9c-1.9-1.9-4.4-2.9-7.1-2.9s-5.2 1-7.1 2.9L256.8 538.8c-1.5 1.5-2.4 3.3-2.8 5.3l-29.5 168.2a33.5 33.5 0 009.4 29.8c6.6 6.4 14.9 9.9 23.8 9.9zm67.4-174.4L687.8 215l73.3 73.3-362.7 362.6-88.9 15.7 15.6-89zM880 836H144c-17.7 0-32 14.3-32 32v36c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-36c0-17.7-14.3-32-32-32z"
/>
</svg>
</span>
</div>
</span>
</div>,
<div
class="ant-typography"
>

View File

@ -0,0 +1,440 @@
import { spyElementPrototypes } from 'rc-util/lib/test/domHook';
import React from 'react';
import { act } from 'react-dom/test-utils';
import { fireEvent, render, sleep, triggerResize, waitFor } from '../../../tests/utils';
import type { EllipsisConfig } from '../Base';
import Base from '../Base';
// eslint-disable-next-line no-unused-vars
jest.mock('copy-to-clipboard');
jest.mock('../../_util/styleChecker', () => ({
isStyleSupport: () => true,
}));
describe('Typography.Ellipsis', () => {
const LINE_STR_COUNT = 20;
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
let mockRectSpy: ReturnType<typeof spyElementPrototypes>;
let getWidthTimes = 0;
beforeAll(() => {
mockRectSpy = spyElementPrototypes(HTMLElement, {
offsetHeight: {
get() {
let html = this.innerHTML;
html = html.replace(/<[^>]*>/g, '');
const lines = Math.ceil(html.length / LINE_STR_COUNT);
return lines * 16;
},
},
offsetWidth: {
get: () => {
getWidthTimes += 1;
return 100;
},
},
getBoundingClientRect() {
let html = this.innerHTML;
html = html.replace(/<[^>]*>/g, '');
const lines = Math.ceil(html.length / LINE_STR_COUNT);
return { height: lines * 16 };
},
});
});
afterEach(() => {
errorSpy.mockReset();
getWidthTimes = 0;
});
afterAll(() => {
errorSpy.mockRestore();
mockRectSpy.mockRestore();
});
const fullStr =
'Bamboo is Little Light Bamboo is Little Light Bamboo is Little Light Bamboo is Little Light Bamboo is Little Light';
it('should trigger update', async () => {
const ref = React.createRef<any>();
const onEllipsis = jest.fn();
const { container, rerender, unmount } = render(
<Base ellipsis={{ onEllipsis }} component="p" editable ref={ref}>
{fullStr}
</Base>,
);
triggerResize(ref.current);
await sleep(20);
expect(container.firstChild?.textContent).toEqual('Bamboo is Little ...');
expect(onEllipsis).toHaveBeenCalledWith(true);
onEllipsis.mockReset();
// Second resize
rerender(
<Base ellipsis={{ rows: 2, onEllipsis }} component="p" editable>
{fullStr}
</Base>,
);
expect(container.textContent).toEqual('Bamboo is Little Light Bamboo is Litt...');
expect(onEllipsis).not.toHaveBeenCalled();
// Third resize
rerender(
<Base ellipsis={{ rows: 99, onEllipsis }} component="p" editable>
{fullStr}
</Base>,
);
expect(container.querySelector('p')?.textContent).toEqual(fullStr);
expect(onEllipsis).toHaveBeenCalledWith(false);
unmount();
});
it('support css multiple lines', async () => {
const { container: wrapper } = render(
<Base ellipsis={{ rows: 2 }} component="p">
{fullStr}
</Base>,
);
expect(
wrapper.querySelectorAll('.ant-typography-ellipsis-multiple-line').length,
).toBeGreaterThan(0);
expect(
(
wrapper.querySelector<HTMLDivElement>('.ant-typography-ellipsis-multiple-line')
?.style as any
)?.WebkitLineClamp,
).toEqual('2');
});
it('string with parentheses', async () => {
const parenthesesStr = `Ant Design, a design language (for background applications, is refined by
Ant UED Team. Ant Design, a design language for background applications,
is refined by Ant UED Team. Ant Design, a design language for background
applications, is refined by Ant UED Team. Ant Design, a design language
for background applications, is refined by Ant UED Team. Ant Design, a
design language for background applications, is refined by Ant UED Team.
Ant Design, a design language for background applications, is refined by
Ant UED Team.`;
const ref = React.createRef<any>();
const onEllipsis = jest.fn();
const { container: wrapper, unmount } = render(
<Base ellipsis={{ onEllipsis }} component="p" editable ref={ref}>
{parenthesesStr}
</Base>,
);
triggerResize(ref.current);
await sleep(20);
expect(wrapper.firstChild?.textContent).toEqual('Ant Design, a des...');
const ellipsisSpans = wrapper.querySelectorAll('span[aria-hidden]');
expect(ellipsisSpans[ellipsisSpans.length - 1].textContent).toEqual('...');
onEllipsis.mockReset();
unmount();
});
it('should middle ellipsis', async () => {
const suffix = '--suffix';
const ref = React.createRef<any>();
const { container: wrapper, unmount } = render(
<Base ellipsis={{ rows: 1, suffix }} component="p" ref={ref}>
{fullStr}
</Base>,
);
triggerResize(ref.current);
await sleep(20);
expect(wrapper.querySelector('p')?.textContent).toEqual('Bamboo is...--suffix');
unmount();
});
it('should front or middle ellipsis', async () => {
const suffix = '--The information is very important';
const ref = React.createRef<any>();
const {
container: wrapper,
rerender,
unmount,
} = render(
<Base ellipsis={{ rows: 1, suffix }} component="p" ref={ref}>
{fullStr}
</Base>,
);
triggerResize(ref.current);
await sleep(20);
expect(wrapper.querySelector('p')?.textContent).toEqual(
'...--The information is very important',
);
rerender(
<Base ellipsis={{ rows: 2, suffix }} component="p">
{fullStr}
</Base>,
);
expect(wrapper.querySelector('p')?.textContent).toEqual(
'Ba...--The information is very important',
);
rerender(
<Base ellipsis={{ rows: 99, suffix }} component="p">
{fullStr}
</Base>,
);
expect(wrapper.querySelector('p')?.textContent).toEqual(fullStr + suffix);
unmount();
});
it('connect children', async () => {
const bamboo = 'Bamboo';
const is = ' is ';
const ref = React.createRef<any>();
const { container: wrapper } = render(
<Base ellipsis component="p" editable ref={ref}>
{bamboo}
{is}
<code>Little</code>
<code>Light</code>
</Base>,
);
triggerResize(ref.current);
await sleep(20);
expect(wrapper.textContent).toEqual('Bamboo is Little...');
});
it('should expandable work', async () => {
const onExpand = jest.fn();
const { container: wrapper } = render(
<Base ellipsis={{ expandable: true, onExpand }} component="p" copyable editable>
{fullStr}
</Base>,
);
fireEvent.click(wrapper.querySelector('.ant-typography-expand')!);
expect(onExpand).toHaveBeenCalled();
expect(wrapper.querySelector('p')?.textContent).toEqual(fullStr);
});
it('should have custom expand style', async () => {
const symbol = 'more';
const { container } = render(
<Base ellipsis={{ expandable: true, symbol }} component="p">
{fullStr}
</Base>,
);
expect(container.querySelector('.ant-typography-expand')?.textContent).toEqual('more');
});
describe('native css ellipsis', () => {
it('can use css ellipsis', () => {
const { container } = render(<Base ellipsis component="p" />);
expect(container.querySelector('.ant-typography-ellipsis-single-line')).toBeTruthy();
});
// https://github.com/ant-design/ant-design/issues/36786
it('Tooltip should recheck on parent visible change', () => {
const originIntersectionObserver = global.IntersectionObserver;
let elementChangeCallback: () => void;
const observeFn = jest.fn();
const disconnectFn = jest.fn();
(global as any).IntersectionObserver = class MockIntersectionObserver {
constructor(callback: () => IntersectionObserverCallback) {
elementChangeCallback = callback;
}
observe = observeFn;
disconnect = disconnectFn;
};
const { container, unmount } = render(<Base ellipsis component="p" />);
expect(observeFn).toHaveBeenCalled();
// Hide first
act(() => {
elementChangeCallback?.();
});
// Trigger visible should trigger recheck
getWidthTimes = 0;
Object.defineProperty(container.querySelector('.ant-typography'), 'offsetParent', {
get: () => document.body,
});
act(() => {
elementChangeCallback?.();
});
expect(getWidthTimes).toBeGreaterThan(0);
unmount();
expect(disconnectFn).toHaveBeenCalled();
global.IntersectionObserver = originIntersectionObserver;
});
it('should calculate padding', () => {
const { container } = render(
<Base ellipsis component="p" style={{ paddingTop: '12px', paddingBottom: '12px' }} />,
);
expect(container.querySelector('.ant-typography-ellipsis-single-line')).toBeTruthy();
});
});
describe('should tooltip support', () => {
let domSpy: ReturnType<typeof spyElementPrototypes>;
beforeAll(() => {
domSpy = spyElementPrototypes(HTMLElement, {
offsetWidth: {
get: () => 100,
},
scrollWidth: {
get: () => 200,
},
});
});
afterAll(() => {
domSpy.mockRestore();
});
async function getWrapper(tooltip?: EllipsisConfig['tooltip']) {
const ref = React.createRef<any>();
const wrapper = render(
<Base ellipsis={{ tooltip }} component="p" ref={ref}>
{fullStr}
</Base>,
);
triggerResize(ref.current);
await sleep(20);
return wrapper;
}
it('boolean', async () => {
const { container, baseElement } = await getWrapper(true);
fireEvent.mouseEnter(container.firstChild!);
await waitFor(() => {
expect(baseElement.querySelector('.ant-tooltip-open')).not.toBeNull();
});
});
it('customize', async () => {
const { container, baseElement } = await getWrapper('Bamboo is Light');
fireEvent.mouseEnter(container.firstChild!);
await waitFor(() => {
expect(baseElement.querySelector('.ant-tooltip-open')).not.toBeNull();
});
});
it('tooltip props', async () => {
const { container, baseElement } = await getWrapper({
title: 'This is tooltip',
className: 'tooltip-class-name',
});
fireEvent.mouseEnter(container.firstChild!);
await waitFor(() => {
expect(container.querySelector('.tooltip-class-name')).toBeTruthy();
expect(baseElement.querySelector('.ant-tooltip-open')).not.toBeNull();
});
});
it('tooltip title true', async () => {
const { container, baseElement } = await getWrapper({
title: true,
className: 'tooltip-class-name',
});
fireEvent.mouseEnter(container.firstChild!);
await waitFor(() => {
expect(container.querySelector('.tooltip-class-name')).toBeTruthy();
expect(baseElement.querySelector('.ant-tooltip-open')).not.toBeNull();
});
});
it('tooltip element', async () => {
const { container, baseElement } = await getWrapper(
<div className="tooltip-class-name">title</div>,
);
fireEvent.mouseEnter(container.firstChild!);
await waitFor(() => {
expect(container.querySelector('.tooltip-class-name')).toBeTruthy();
expect(baseElement.querySelector('.ant-tooltip-open')).not.toBeNull();
});
});
});
it('js ellipsis should show aria-label', () => {
const { container: titleWrapper } = render(
<Base
component={undefined as unknown as string}
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' }}
/>,
);
expect(tooltipWrapper.querySelector('.ant-typography')?.getAttribute('aria-label')).toEqual(
'little',
);
});
it('should display tooltip if line clamp', async () => {
mockRectSpy = spyElementPrototypes(HTMLElement, {
scrollHeight: {
get() {
let html = this.innerHTML;
html = html.replace(/<[^>]*>/g, '');
const lines = Math.ceil(html.length / LINE_STR_COUNT);
return lines * 16;
},
},
offsetHeight: {
get: () => 32,
},
offsetWidth: {
get: () => 100,
},
scrollWidth: {
get: () => 100,
},
});
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}
>
Ant Design, a design language for background applications, is refined by Ant UED Team.
</Base>,
);
triggerResize(ref.current);
await sleep(20);
fireEvent.mouseEnter(container.firstChild!);
await waitFor(() => {
expect(baseElement.querySelector('.ant-tooltip-open')).not.toBeNull();
});
mockRectSpy.mockRestore();
});
});

View File

@ -200,6 +200,43 @@ describe('Typography.Ellipsis', () => {
unmount();
});
it('should use editConfig.text over children in editing mode ', async () => {
const suffix = '--The information is very important';
const ref = React.createRef<any>();
const { container: wrapper, unmount } = render(
<Base
ellipsis={{ rows: 1, suffix }}
component="p"
editable={{ text: fullStr + suffix }}
ref={ref}
>
{fullStr}
</Base>,
);
fireEvent.click(wrapper.querySelector('.ant-typography-edit')!);
expect(wrapper.querySelector('textarea')?.textContent).toEqual(fullStr + suffix);
unmount();
});
it('should use children as the fallback of editConfig.text in editing mode', async () => {
const suffix = '--The information is very important';
const ref = React.createRef<any>();
const { container: wrapper, unmount } = render(
<Base ellipsis={{ rows: 1, suffix }} component="p" ref={ref} editable>
{fullStr}
</Base>,
);
fireEvent.click(wrapper.querySelector('.ant-typography-edit')!);
expect(wrapper.querySelector('textarea')?.textContent).toEqual(fullStr);
unmount();
});
it('connect children', async () => {
const bamboo = 'Bamboo';
const is = ' is ';

View File

@ -396,6 +396,15 @@ describe('Typography', () => {
expect(onEnd).toHaveBeenCalledTimes(1);
});
it('should trigger onStart when type Start', () => {
const onStart = jest.fn();
const { container: wrapper } = render(<Paragraph editable={{ onStart }}>Bamboo</Paragraph>);
fireEvent.click(wrapper.querySelectorAll('.ant-typography-edit')[0]);
fireEvent.keyDown(wrapper.querySelector('textarea')!, { keyCode: KeyCode.A });
fireEvent.keyUp(wrapper.querySelector('textarea')!, { keyCode: KeyCode.A });
expect(onStart).toHaveBeenCalledTimes(1);
});
it('should trigger onCancel when type ESC', () => {
const onCancel = jest.fn();
const { container: wrapper } = render(

View File

@ -16,12 +16,19 @@ Provide additional interactive capacity of editable and copyable.
```tsx
import { CheckOutlined, HighlightOutlined, SmileFilled, SmileOutlined } from '@ant-design/icons';
import { Divider, Radio, Typography } from 'antd';
import React, { useState } from 'react';
import React, { useState, useMemo } from 'react';
const { Paragraph } = Typography;
const App: React.FC = () => {
const [editableStr, setEditableStr] = useState('This is an editable text.');
const [editableStrWithSuffix, setEditableStrWithSuffix] = useState(
'This is a loooooooooooooooooooooooooooooooong editable text with suffix.',
);
const [editableStrWithSuffixStartPart, editableStrWithSuffixSuffixPart] = useMemo(
() => [editableStrWithSuffix.slice(0, -12), editableStrWithSuffix.slice(-12)],
[editableStrWithSuffix],
);
const [customIconStr, setCustomIconStr] = useState('Custom Edit icon and replace tooltip text.');
const [clickTriggerStr, setClickTriggerStr] = useState(
'Text or icon as trigger - click to start editing.',
@ -60,6 +67,17 @@ const App: React.FC = () => {
return (
<>
<Paragraph editable={{ onChange: setEditableStr }}>{editableStr}</Paragraph>
<Paragraph
editable={{
onChange: setEditableStrWithSuffix,
text: editableStrWithSuffix,
}}
ellipsis={{
suffix: editableStrWithSuffixSuffixPart,
}}
>
{editableStrWithSuffixStartPart}
</Paragraph>
<Paragraph
editable={{
icon: <HighlightOutlined />,
@ -69,8 +87,7 @@ const App: React.FC = () => {
>
{customIconStr}
</Paragraph>
Trigger edit with:{' '}
<Radio.Group
Trigger edit with: <Radio.Group
onChange={e => setChooseTrigger(radioToState(e.target.value))}
value={stateToRadio()}
>

View File

@ -93,9 +93,10 @@ Basic text writing, including headings, body text, lists, and more.
editing: boolean,
maxLength: number,
autoSize: boolean | { minRows: number, maxRows: number },
onStart: function,
text: string,
onChange: function(string),
onCancel: function,
onStart: function,
onEnd: function,
triggerType: ('icon' | 'text')[],
enterIcon: ReactNode,
@ -108,9 +109,10 @@ Basic text writing, including headings, body text, lists, and more.
| icon | Custom editable icon | ReactNode | &lt;EditOutlined /> | 4.6.0 |
| maxLength | `maxLength` attribute of textarea | number | - | 4.4.0 |
| tooltip | Custom tooltip text, hide when it is false | boolean \| ReactNode | `Edit` | 4.6.0 |
| onStart | Called when enter editable state | function | - | |
| text | Edit text, specify the editing content instead of using the children implicitly | string | - | 4.24.0 |
| onChange | Called when input at textarea | function(value: string) | - | |
| onCancel | Called when type ESC to exit editable state | function | - | |
| onStart | Called when enter editable state | function | - | |
| onEnd | Called when type ENTER to exit editable state | function | - | 4.14.0 |
| triggerType | Edit mode trigger - icon, text or both (not specifying icon as trigger hides it) | Array&lt;`icon`\|`text`> | \[`icon`] | |
| enterIcon | Custom "enter" icon in the edit field (passing `null` removes the icon) | ReactNode | `<EnterOutlined />` | 4.17.0 |

View File

@ -94,9 +94,10 @@ cover: https://gw.alipayobjects.com/zos/alicdn/GOM1KQ24O/Typography.svg
editing: boolean,
maxLength: number,
autoSize: boolean | { minRows: number, maxRows: number },
onStart: function,
text: string,
onChange: function(string),
onCancel: function,
onStart: function,
onEnd: function,
triggerType: ('icon' | 'text')[],
enterIcon: ReactNode,
@ -109,10 +110,11 @@ cover: https://gw.alipayobjects.com/zos/alicdn/GOM1KQ24O/Typography.svg
| icon | 自定义编辑图标 | ReactNode | &lt;EditOutlined /> | 4.6.0 |
| maxLength | 编辑中文本域最大长度 | number | - | 4.4.0 |
| tooltip | 自定义提示文本,为 false 时关闭 | boolean \| ReactNode | `编辑` | 4.6.0 |
| onCancel | 按 ESC 退出编辑状态时触发 | function | - | |
| text | 显式地指定编辑文案,为空时将隐式地使用 children | string | - | 4.24.0 |
| onChange | 文本域编辑时触发 | function(value: string) | - | |
| onEnd | 按 ENTER 结束编辑状态时触发 | function | - | 4.14.0 |
| onCancel | 按 ESC 退出编辑状态时触发 | function | - | |
| onStart | 进入编辑中状态时触发 | function | - | |
| onEnd | 按 ENTER 结束编辑状态时触发 | function | - | 4.14.0 |
| triggerType | 编辑模式触发器类型,图标、文本或者两者都设置(不设置图标作为触发器时它会隐藏) | Array&lt;`icon`\|`text`> | \[`icon`] | |
| enterIcon | 在编辑段中自定义“enter”图标传递“null”将删除图标 | ReactNode | `<EnterOutlined />` | 4.17.0 |