feat: typography add enterEnterIcon triggerType

feat-css-var
tangjinzhou 2022-03-08 22:27:49 +08:00
parent f1384d16a5
commit e777bc1743
11 changed files with 196 additions and 65 deletions

View File

@ -52,6 +52,7 @@ export interface EditConfig {
onEnd?: () => void;
maxlength?: number;
autoSize?: boolean | AutoSizeType;
triggerType?: ('icon' | 'text')[];
}
export interface EllipsisConfig {
@ -99,7 +100,7 @@ const Base = defineComponent<InternalBlockProps>({
inheritAttrs: false,
emits: ['update:content'],
setup(props, { slots, attrs, emit }) {
const { prefixCls } = useConfigInject('typography', props);
const { prefixCls, direction } = useConfigInject('typography', props);
const state = reactive({
edit: false,
@ -362,12 +363,12 @@ const Base = defineComponent<InternalBlockProps>({
function renderEdit() {
if (!props.editable) return;
const { tooltip } = props.editable as EditConfig;
const { tooltip, triggerType = ['icon'] } = props.editable as EditConfig;
const icon = slots.editableIcon ? slots.editableIcon() : <EditOutlined role="button" />;
const title = slots.editableTooltip ? slots.editableTooltip() : state.editStr;
const ariaLabel = typeof title === 'string' ? title : '';
return (
return triggerType.indexOf('icon') !== -1 ? (
<Tooltip key="edit" title={tooltip === false ? '' : title}>
<TransButton
ref={editIcon}
@ -378,7 +379,7 @@ const Base = defineComponent<InternalBlockProps>({
{icon}
</TransButton>
</Tooltip>
);
) : null;
}
function renderCopy() {
@ -428,6 +429,8 @@ const Base = defineComponent<InternalBlockProps>({
onChange={onContentChange}
onCancel={onEditCancel}
onEnd={onEnd}
direction={direction.value}
v-slots={{ enterIcon: slots.editableEnterIcon }}
/>
);
}
@ -437,7 +440,7 @@ const Base = defineComponent<InternalBlockProps>({
}
return () => {
const { editing } = editable.value;
const { editing, triggerType = ['icon'] } = editable.value;
const children =
props.ellipsis || props.editable
? props.content !== undefined
@ -538,7 +541,7 @@ const Base = defineComponent<InternalBlockProps>({
[`${prefixCls.value}-${type}`]: type,
[`${prefixCls.value}-disabled`]: disabled,
[`${prefixCls.value}-ellipsis`]: rows,
[`${prefixCls.value}-single-line`]: rows === 1,
[`${prefixCls.value}-single-line`]: rows === 1 && !state.isEllipsis,
[`${prefixCls.value}-ellipsis-single-line`]: cssTextOverflow,
[`${prefixCls.value}-ellipsis-multiple-line`]: cssLineClamp,
},
@ -549,6 +552,8 @@ const Base = defineComponent<InternalBlockProps>({
WebkitLineClamp: cssLineClamp ? rows : undefined,
}}
aria-label={ariaLabel}
direction={direction.value}
onClick={triggerType.indexOf('text') !== -1 ? onEditClick : () => {}}
{...textProps}
>
{showTooltip ? (

View File

@ -2,7 +2,9 @@ import KeyCode from '../_util/KeyCode';
import PropTypes from '../_util/vue-types';
import TextArea from '../input/TextArea';
import EnterOutlined from '@ant-design/icons-vue/EnterOutlined';
import { defineComponent, ref, reactive, watch, onMounted } from 'vue';
import type { PropType } from 'vue';
import { defineComponent, ref, reactive, watch, onMounted, computed } from 'vue';
import type { Direction } from '../config-provider';
const Editable = defineComponent({
name: 'Editable',
@ -16,9 +18,10 @@ const Editable = defineComponent({
onEnd: PropTypes.func,
onChange: PropTypes.func,
originContent: PropTypes.string,
direction: String as PropType<Direction>,
},
emits: ['save', 'cancel', 'end', 'change'],
setup(props, { emit }) {
setup(props, { emit, slots }) {
const state = reactive({
current: props.value || '',
lastKeyCode: undefined,
@ -102,9 +105,13 @@ const Editable = defineComponent({
function confirmChange() {
emit('save', state.current.trim());
}
const textAreaClassName = computed(() => ({
[`${props.prefixCls}`]: true,
[`${props.prefixCls}-edit-content`]: true,
[`${props.prefixCls}-rtl`]: props.direction === 'rtl',
}));
return () => (
<div class={`${props.prefixCls} ${props.prefixCls}-edit-content`}>
<div class={textAreaClassName.value}>
<TextArea
ref={saveTextAreaRef}
maxlength={props.maxlength}
@ -117,7 +124,11 @@ const Editable = defineComponent({
onBlur={onBlur}
autoSize={props.autoSize === undefined || props.autoSize}
/>
<EnterOutlined class={`${props.prefixCls}-edit-content-confirm`} />
{slots.enterIcon ? (
slots.enterIcon({ className: `${props.prefixCls}-edit-content-confirm` })
) : (
<EnterOutlined class={`${props.prefixCls}-edit-content-confirm`} />
)}
</div>
);
},

View File

@ -3,8 +3,10 @@ import type { HTMLAttributes } from 'vue';
import { defineComponent } from 'vue';
import useConfigInject from '../_util/hooks/useConfigInject';
import classNames from '../_util/classNames';
import type { Direction } from '../config-provider';
export interface TypographyProps extends HTMLAttributes {
direction?: Direction;
prefixCls?: string;
}
@ -16,16 +18,24 @@ const Typography = defineComponent<InternalTypographyProps>({
name: 'ATypography',
inheritAttrs: false,
setup(props, { slots, attrs }) {
const { prefixCls } = useConfigInject('typography', props);
const { prefixCls, direction } = useConfigInject('typography', props);
return () => {
const {
prefixCls: _prefixCls,
class: _className,
direction: _direction,
component: Component = 'article' as any,
...restProps
} = { ...props, ...attrs };
return (
<Component class={classNames(prefixCls.value, attrs.class)} {...restProps}>
<Component
class={classNames(
prefixCls.value,
{ [`${prefixCls.value}-rtl`]: direction.value === 'rtl' },
attrs.class,
)}
{...restProps}
>
{slots.default?.()}
</Component>
);

View File

@ -68,7 +68,9 @@ exports[`renders ./components/typography/demo/basic.vue correctly 1`] = `
exports[`renders ./components/typography/demo/ellipsis.vue correctly 1`] = `
<button type="button" role="switch" aria-checked="true" class="ant-switch-checked ant-switch">
<!----><span class="ant-switch-inner"><!----></span>
<div class="ant-switch-handle">
<!---->
</div><span class="ant-switch-inner"><!----></span>
</button>
<div class="ant-typography ant-typography-ellipsis ant-typography-single-line ant-typography-ellipsis-single-line"> 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
@ -78,15 +80,14 @@ exports[`renders ./components/typography/demo/ellipsis.vue correctly 1`] = `
Design, a design language for background applications, is refined by Ant UED Team.
<!---->
</div>
<div class="ant-typography ant-typography-ellipsis"><span title="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." aria-hidden="true">...</span>
<!----><a class="ant-typography-expand" aria-label="Expand">more</a>
</div><span class="ant-typography ant-typography-ellipsis ant-typography-single-line" style="width: 100px;"><!----><span><span title="Ant Design, a design language for background applications, is refined by Ant UED Team." aria-hidden="true">...</span>
<!----></span></span>
<div class="ant-typography ant-typography-ellipsis">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.
<!---->
</div><span class="ant-typography ant-typography-ellipsis ant-typography-single-line" style="width: 100px;">Ant Design, a design language for background applications, is refined by Ant UED Team.<!----></span>
`;
exports[`renders ./components/typography/demo/interactive.vue correctly 1`] = `
@ -99,6 +100,21 @@ exports[`renders ./components/typography/demo/interactive.vue correctly 1`] = `
<!---->
<!---->
<div role="button" tabindex="0" class="ant-typography-edit" aria-label="" style="padding: 0px; line-height: inherit; display: inline-block; border: 0px; background: transparent;"><span role="img" aria-label="highlight" class="anticon anticon-highlight"><svg focusable="false" class="" data-icon="highlight" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M957.6 507.4L603.2 158.2a7.9 7.9 0 00-11.2 0L353.3 393.4a8.03 8.03 0 00-.1 11.3l.1.1 40 39.4-117.2 115.3a8.03 8.03 0 00-.1 11.3l.1.1 39.5 38.9-189.1 187H72.1c-4.4 0-8.1 3.6-8.1 8V860c0 4.4 3.6 8 8 8h344.9c2.1 0 4.1-.8 5.6-2.3l76.1-75.6 40.4 39.8a7.9 7.9 0 0011.2 0l117.1-115.6 40.1 39.5a7.9 7.9 0 0011.2 0l238.7-235.2c3.4-3 3.4-8 .3-11.2zM389.8 796.2H229.6l134.4-133 80.1 78.9-54.3 54.1zm154.8-62.1L373.2 565.2l68.6-67.6 171.4 168.9-68.6 67.6zM713.1 658L450.3 399.1 597.6 254l262.8 259-147.3 145z"></path></svg></span></div>
</div> Trigger edit with:&nbsp;&nbsp; <div class="ant-radio-group ant-radio-group-outline ant-radio-group-default"><label class="ant-radio-wrapper ant-radio-wrapper-checked"><span class="ant-radio ant-radio-checked"><input type="radio" class="ant-radio-input" value="icon"><span class="ant-radio-inner"></span></span><span>icon</span></label><label class="ant-radio-wrapper"><span class="ant-radio"><input type="radio" class="ant-radio-input" value="text"><span class="ant-radio-inner"></span></span><span>text</span></label><label class="ant-radio-wrapper"><span class="ant-radio"><input type="radio" class="ant-radio-input" value="both"><span class="ant-radio-inner"></span></span><span>both</span></label></div>
<div class="ant-typography">Text or icon as trigger - click to start editing.
<!---->
<!---->
<div role="button" tabindex="0" class="ant-typography-edit" aria-label="" style="padding: 0px; line-height: inherit; display: inline-block; border: 0px; background: transparent;"><span role="img" aria-label="edit" class="anticon anticon-edit"><svg focusable="false" class="" data-icon="edit" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><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"></path></svg></span></div>
</div>
<div class="ant-typography">Editable text with a custom enter icon in edit field.
<!---->
<!---->
<div role="button" tabindex="0" class="ant-typography-edit" aria-label="" style="padding: 0px; line-height: inherit; display: inline-block; border: 0px; background: transparent;"><span role="img" aria-label="highlight" class="anticon anticon-highlight"><svg focusable="false" class="" data-icon="highlight" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M957.6 507.4L603.2 158.2a7.9 7.9 0 00-11.2 0L353.3 393.4a8.03 8.03 0 00-.1 11.3l.1.1 40 39.4-117.2 115.3a8.03 8.03 0 00-.1 11.3l.1.1 39.5 38.9-189.1 187H72.1c-4.4 0-8.1 3.6-8.1 8V860c0 4.4 3.6 8 8 8h344.9c2.1 0 4.1-.8 5.6-2.3l76.1-75.6 40.4 39.8a7.9 7.9 0 0011.2 0l117.1-115.6 40.1 39.5a7.9 7.9 0 0011.2 0l238.7-235.2c3.4-3 3.4-8 .3-11.2zM389.8 796.2H229.6l134.4-133 80.1 78.9-54.3 54.1zm154.8-62.1L373.2 565.2l68.6-67.6 171.4 168.9-68.6 67.6zM713.1 658L450.3 399.1 597.6 254l262.8 259-147.3 145z"></path></svg></span></div>
</div>
<div class="ant-typography">Editable text with no enter icon in edit field.
<!---->
<!---->
<div role="button" tabindex="0" class="ant-typography-edit" aria-label="" style="padding: 0px; line-height: inherit; display: inline-block; border: 0px; background: transparent;"><span role="img" aria-label="highlight" class="anticon anticon-highlight"><svg focusable="false" class="" data-icon="highlight" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M957.6 507.4L603.2 158.2a7.9 7.9 0 00-11.2 0L353.3 393.4a8.03 8.03 0 00-.1 11.3l.1.1 40 39.4-117.2 115.3a8.03 8.03 0 00-.1 11.3l.1.1 39.5 38.9-189.1 187H72.1c-4.4 0-8.1 3.6-8.1 8V860c0 4.4 3.6 8 8 8h344.9c2.1 0 4.1-.8 5.6-2.3l76.1-75.6 40.4 39.8a7.9 7.9 0 0011.2 0l117.1-115.6 40.1 39.5a7.9 7.9 0 0011.2 0l238.7-235.2c3.4-3 3.4-8 .3-11.2zM389.8 796.2H229.6l134.4-133 80.1 78.9-54.3 54.1zm154.8-62.1L373.2 565.2l68.6-67.6 171.4 168.9-68.6 67.6zM713.1 658L450.3 399.1 597.6 254l262.8 259-147.3 145z"></path></svg></span></div>
</div>
<div class="ant-typography">Hide Edit tooltip.
<!---->
@ -141,7 +157,7 @@ exports[`renders ./components/typography/demo/suffix.vue correctly 1`] = `
<div class="ant-slider-handle" style="left: 0%; transform: translateX(-50%);" role="slider" tabindex="0" aria-valuemin="1" aria-valuemax="10" aria-valuenow="1" aria-disabled="false"></div>
<div class="ant-slider-mark"></div>
</div>
<div class="ant-typography ant-typography-ellipsis ant-typography-single-line" title="To be, or not to be, that is a question: Whether it is nobler in the mind to suffer. The slings and arrows of outrageous fortune Or to take arms against a sea of troubles, And by opposing end them? To die: to sleep; No more; and by a sleep to say we end The heart-ache and the thousand natural shocks That flesh is heir to, 'tis a consummation Devoutly to be wish'd. To die, to sleep To sleep- perchance to dream: ay, there's the rub! For in that sleep of death what dreams may come When we have shuffled off this mortal coil, Must give us pause. There 's the respect That makes calamity of so long life--William Shakespeare"><span title="To be, or not to be, that is a question: Whether it is nobler in the mind to suffer. The slings and arrows of outrageous fortune Or to take arms against a sea of troubles, And by opposing end them? To die: to sleep; No more; and by a sleep to say we end The heart-ache and the thousand natural shocks That flesh is heir to, 'tis a consummation Devoutly to be wish'd. To die, to sleep To sleep- perchance to dream: ay, there's the rub! For in that sleep of death what dreams may come When we have shuffled off this mortal coil, Must give us pause. There 's the respect That makes calamity of so long life--William Shakespeare" aria-hidden="true">...</span>--William Shakespeare<a class="ant-typography-expand" aria-label="Expand">Expand</a></div>
<div class="ant-typography ant-typography-ellipsis ant-typography-single-line" title="To be, or not to be, that is a question: Whether it is nobler in the mind to suffer. The slings and arrows of outrageous fortune Or to take arms against a sea of troubles, And by opposing end them? To die: to sleep; No more; and by a sleep to say we end The heart-ache and the thousand natural shocks That flesh is heir to, 'tis a consummation Devoutly to be wish'd. To die, to sleep To sleep- perchance to dream: ay, there's the rub! For in that sleep of death what dreams may come When we have shuffled off this mortal coil, Must give us pause. There 's the respect That makes calamity of so long life--William Shakespeare">To be, or not to be, that is a question: Whether it is nobler in the mind to suffer. The slings and arrows of outrageous fortune Or to take arms against a sea of troubles, And by opposing end them? To die: to sleep; No more; and by a sleep to say we end The heart-ache and the thousand natural shocks That flesh is heir to, 'tis a consummation Devoutly to be wish'd. To die, to sleep To sleep- perchance to dream: ay, there's the rub! For in that sleep of death what dreams may come When we have shuffled off this mortal coil, Must give us pause. There 's the respect That makes calamity of so long life--William Shakespeare</div>
`;
exports[`renders ./components/typography/demo/text.vue correctly 1`] = `

View File

@ -18,19 +18,27 @@ describe('Typography', () => {
const LINE_STR_COUNT = 20;
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
// Mock offsetHeight
const originOffsetHeight = Object.getOwnPropertyDescriptor(
HTMLElement.prototype,
'offsetHeight',
).get;
Object.defineProperty(HTMLElement.prototype, 'offsetHeight', {
get() {
const mockGetBoundingClientRect = jest.spyOn(HTMLElement.prototype, 'getBoundingClientRect');
beforeAll(() => {
Object.defineProperty(HTMLElement.prototype, 'offsetHeight', {
get() {
let html = this.innerHTML;
html = html.replace(/<[^>]*>/g, '');
const lines = Math.ceil(html.length / LINE_STR_COUNT);
return lines * 16;
},
});
mockGetBoundingClientRect.mockImplementation(function fn() {
let html = this.innerHTML;
html = html.replace(/<[^>]*>/g, '');
const lines = Math.ceil(html.length / LINE_STR_COUNT);
return lines * 16;
},
return { height: lines * 16 };
});
});
// Mock getComputedStyle

View File

@ -20,6 +20,33 @@ Provide additional interactive capacity of editable and copyable.
<template #editableIcon><HighlightOutlined /></template>
<template #editableTooltip>click to edit text</template>
</a-typography-paragraph>
Trigger edit with:&nbsp;&nbsp;
<a-radio-group
:value="stateToRadio()"
@change="e => (chooseTrigger = radioToState(e.target.value))"
>
<a-radio value="icon">icon</a-radio>
<a-radio value="text">text</a-radio>
<a-radio value="both">both</a-radio>
</a-radio-group>
<a-typography-paragraph
v-model:content="clickTriggerStr"
:editable="{ triggerType: chooseTrigger }"
>
<template #editableTooltip>click to edit text</template>
</a-typography-paragraph>
<a-typography-paragraph v-model:content="customEnterIconStr" editable>
<template #editableIcon><HighlightOutlined /></template>
<template #editableTooltip>click to edit text</template>
<template #editableEnterIcon="{ className }">
<CheckOutlined :class="className" />
</template>
</a-typography-paragraph>
<a-typography-paragraph v-model:content="noEnterIconStr" editable>
<template #editableIcon><HighlightOutlined /></template>
<template #editableTooltip>click to edit text</template>
<template #editableEnterIcon>{{ null }}</template>
</a-typography-paragraph>
<a-typography-paragraph v-model:content="hideTooltipStr" :editable="{ tooltip: false }" />
<a-typography-paragraph
v-model:content="lengthLimitedStr"
@ -46,24 +73,56 @@ Provide additional interactive capacity of editable and copyable.
</template>
<script lang="ts">
import { defineComponent, ref, watch } from 'vue';
import { HighlightOutlined, SmileOutlined, SmileFilled } from '@ant-design/icons-vue';
import {
HighlightOutlined,
SmileOutlined,
SmileFilled,
CheckOutlined,
} from '@ant-design/icons-vue';
export default defineComponent({
components: {
HighlightOutlined,
SmileOutlined,
SmileFilled,
CheckOutlined,
},
setup() {
const editableStr = ref('This is an editable text.');
watch(editableStr, () => {
console.log('editableStr', editableStr.value);
});
const chooseTrigger = ref<('icon' | 'text')[]>(['icon']);
const radioToState = (input: string): ('icon' | 'text')[] => {
switch (input) {
case 'text':
return ['text'];
case 'both':
return ['icon', 'text'];
case 'icon':
default:
return ['icon'];
}
};
const stateToRadio = () => {
if (chooseTrigger.value.indexOf('text') !== -1) {
return chooseTrigger.value.indexOf('icon') !== -1 ? 'both' : 'text';
}
return 'icon';
};
return {
radioToState,
stateToRadio,
editableStr,
customIconStr: ref('Custom Edit icon and replace tooltip text.'),
hideTooltipStr: ref('Hide Edit tooltip.'),
lengthLimitedStr: ref('This is an editable text with limited length.'),
clickTriggerStr: ref('Text or icon as trigger - click to start editing.'),
chooseTrigger,
customEnterIconStr: ref('Editable text with a custom enter icon in edit field.'),
noEnterIconStr: ref('Editable text with no enter icon in edit field.'),
};
},
});

View File

@ -69,6 +69,7 @@ Basic text writing, including headings, body text, lists, and more.
| Name | Description | Property | Default | Version |
| --- | --- | --- | --- | --- |
| editableIcon | Custom editable icon | - | &lt;EditOutlined /> | |
| editableEnterIcon | Custom "enter" icon in the edit field | `{className: string}` | `<EnterOutlined />` | 3.0 |
| editableTooltip | Custom tooltip text, hide when `editable.tooltip = false` | - | `Edit` | |
| copyableIcon | Custom copy icon | `{ copied: boolean }` | `copied ? <CheckOutlined /> : <CopyOutlined />` | |
| copyableTooltip | Custom tooltip text, hide when `copyable.tooltip = false` | `{ copied: boolean }` | `copied ? 'Copied' : 'Copy'` | |
@ -103,6 +104,7 @@ Basic text writing, including headings, body text, lists, and more.
onChange: function(string),
onCancel: function,
onEnd: function,
triggerType: ('icon' | 'text')[],
}
```
@ -118,6 +120,7 @@ Basic text writing, including headings, body text, lists, and more.
| onStart | Called when enter editable state | function | - | |
| onCancel | Called when type ESC to exit editable state | function | - | |
| onEnd | Called when type ENTER to exit editable state | function | - | |
| triggerType | Edit mode trigger - icon, text or both (not specifying icon as trigger hides it) | Array&lt;`icon`\|`text`> | \[`icon`] | |
### ellipsis

View File

@ -70,6 +70,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/GOM1KQ24O/Typography.svg
| 名称 | 说明 | 参数 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| editableIcon | 自定义编辑图标 | - | &lt;EditOutlined /> | |
| enterEnterIcon | 在编辑段中自定义“enter”图标 | `{className: string}` | `<EnterOutlined />` | 3.0 |
| editableTooltip | 自定义提示文本,当 `editable.tooltip = false` 时关闭 | - | `编辑` | |
| copyableIcon | 自定义拷贝图标 | `{ copied: boolean }` | `copied ? <CheckOutlined /> : <CopyOutlined />` | |
| copyableTooltip | 自定义提示文案,当 `copyable.tooltip = false` 时关闭 | `{ copied: boolean }` | `copied ? '复制成功' : '复制'` | |
@ -104,6 +105,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/GOM1KQ24O/Typography.svg
onChange: function(string),
onCancel: function,
onEnd: function,
triggerType: ('icon' | 'text')[],
}
```
@ -119,6 +121,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/GOM1KQ24O/Typography.svg
| onStart | 进入编辑中状态时触发 | function | - | |
| onCancel | 按 ESC 退出编辑状态时触发 | function | - | |
| onEnd | 按 ENTER 结束编辑状态时触发 | function | - | |
| triggerType | Edit mode trigger - icon, text or both (not specifying icon as trigger hides it) | Array&lt;`icon`\|`text`> | \[`icon`] | |
### ellipsis

View File

@ -22,6 +22,7 @@
&&-danger {
color: @error-color;
a&:active,
a&:focus,
a&:hover {
@ -45,18 +46,22 @@
h1 {
.typography-title-1();
}
h2&,
h2 {
.typography-title-2();
}
h3&,
h3 {
.typography-title-3();
}
h4&,
h4 {
.typography-title-4();
}
h5&,
h5 {
.typography-title-5();
@ -93,6 +98,7 @@
a&-ellipsis,
span&-ellipsis {
display: inline-block;
max-width: 100%;
}
a&,
@ -184,7 +190,6 @@
div& {
left: -@input-padding-horizontal - 1px;
margin-top: -@input-padding-vertical-base - 1px;
// stylelint-disable-next-line function-calc-no-invalid
margin-bottom: calc(1em - @input-padding-vertical-base - 1px);
}
@ -198,6 +203,7 @@
// Fix Editable Textarea flash in Firefox
textarea {
/* stylelint-disable-next-line property-no-vendor-prefix */
-moz-transition: none;
}
}
@ -205,7 +211,7 @@
// list
ul,
ol {
margin: 0 0 1em 0;
margin: 0 0 1em;
padding: 0;
li {
@ -275,9 +281,11 @@
}
&-ellipsis-multiple-line {
/* stylelint-disable-next-line value-no-vendor-prefix */
display: -webkit-box;
overflow: hidden;
-webkit-line-clamp: 3;
/*! autoprefixer: ignore next */
-webkit-box-orient: vertical;
}

View File

@ -23,14 +23,6 @@ const wrapperStyle: CSSProperties = {
lineHeight: 'inherit',
};
function pxToNumber(value: string | null) {
if (!value) return 0;
const match = value.match(/^\d*(\.\d*)?/);
return match ? Number(match[0]) : 0;
}
function styleToString(style: CSSStyleDeclaration) {
// There are some different behavior between Firefox & Chrome.
// We have to handle this ourself.
@ -38,8 +30,43 @@ function styleToString(style: CSSStyleDeclaration) {
return styleNames.map(name => `${name}: ${style.getPropertyValue(name)};`).join('');
}
function resetDomStyles(target: HTMLElement, origin: HTMLElement) {
target.setAttribute('aria-hidden', 'true');
const originStyle = window.getComputedStyle(origin);
const originCSS = styleToString(originStyle);
// Set shadow
target.setAttribute('style', originCSS);
target.style.position = 'fixed';
target.style.left = '0';
target.style.height = 'auto';
target.style.minHeight = 'auto';
target.style.maxHeight = 'auto';
target.style.paddingTop = '0';
target.style.paddingBottom = '0';
target.style.borderTopWidth = '0';
target.style.borderBottomWidth = '0';
target.style.top = '-999999px';
target.style.zIndex = '-1000';
// clean up css overflow
target.style.textOverflow = 'clip';
target.style.whiteSpace = 'normal';
(target.style as any).webkitLineClamp = 'none';
}
function getRealLineHeight(originElement: HTMLElement) {
const heightContainer = document.createElement('div');
resetDomStyles(heightContainer, originElement);
heightContainer.appendChild(document.createTextNode('text'));
document.body.appendChild(heightContainer);
// The element real height is always less than multiple of line-height
// Use getBoundingClientRect to get actual single row height of the element
const realHeight = heightContainer.getBoundingClientRect().height;
document.body.removeChild(heightContainer);
return realHeight;
}
export default (
originEle: HTMLElement,
originElement: HTMLElement,
option: Option,
content: string,
fixedContent: VNodeTypes[],
@ -56,30 +83,10 @@ export default (
}
const { rows, suffix = '' } = option;
// Get origin style
const originStyle = window.getComputedStyle(originEle);
const originCSS = styleToString(originStyle);
const lineHeight = pxToNumber(originStyle.lineHeight);
const maxHeight = Math.round(
lineHeight * (rows + 1) +
pxToNumber(originStyle.paddingTop) +
pxToNumber(originStyle.paddingBottom),
);
const lineHeight = getRealLineHeight(originElement);
const maxHeight = Math.round(lineHeight * rows * 100) / 100;
// Set shadow
ellipsisContainer.setAttribute('style', originCSS);
ellipsisContainer.style.position = 'fixed';
ellipsisContainer.style.left = '0';
ellipsisContainer.style.height = 'auto';
ellipsisContainer.style.minHeight = 'auto';
ellipsisContainer.style.maxHeight = 'auto';
ellipsisContainer.style.top = '-999999px';
ellipsisContainer.style.zIndex = '-1000';
// clean up css overflow
ellipsisContainer.style.textOverflow = 'clip';
ellipsisContainer.style.whiteSpace = 'normal';
ellipsisContainer.style.webkitLineClamp = 'none';
resetDomStyles(ellipsisContainer, originElement);
// Render in the fake container
const vm = createApp({
@ -100,7 +107,8 @@ export default (
// Check if ellipsis in measure div is height enough for content
function inRange() {
return ellipsisContainer.offsetHeight < maxHeight;
const currentHeight = Math.round(ellipsisContainer.getBoundingClientRect().height * 100) / 100;
return currentHeight - 0.1 <= maxHeight; // -.1 for firefox
}
// Skip ellipsis if already match