feat: typography add enterEnterIcon triggerType
parent
f1384d16a5
commit
e777bc1743
|
@ -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 ? (
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
},
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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: <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`] = `
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
<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.'),
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
|
@ -69,6 +69,7 @@ Basic text writing, including headings, body text, lists, and more.
|
|||
| Name | Description | Property | Default | Version |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| editableIcon | Custom editable icon | - | <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<`icon`\|`text`> | \[`icon`] | |
|
||||
|
||||
### ellipsis
|
||||
|
||||
|
|
|
@ -70,6 +70,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/GOM1KQ24O/Typography.svg
|
|||
| 名称 | 说明 | 参数 | 默认值 | 版本 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| editableIcon | 自定义编辑图标 | - | <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<`icon`\|`text`> | \[`icon`] | |
|
||||
|
||||
### ellipsis
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue