From 5771e505c72fb66aaff334884bb57c4acd39378b Mon Sep 17 00:00:00 2001 From: tanjinzhou <415800467@qq.com> Date: Mon, 15 Mar 2021 18:06:50 +0800 Subject: [PATCH] feat: update typography --- components/input/calculateNodeHeight.tsx | 1 + components/typography/Base.tsx | 124 ++++++++++-------- components/typography/Editable.tsx | 22 ++-- components/typography/Text.tsx | 1 - components/typography/__tests__/index.test.js | 2 +- components/typography/util.tsx | 64 ++++----- examples/App.vue | 2 +- 7 files changed, 109 insertions(+), 107 deletions(-) diff --git a/components/input/calculateNodeHeight.tsx b/components/input/calculateNodeHeight.tsx index 7f220c4e4..5364d8337 100644 --- a/components/input/calculateNodeHeight.tsx +++ b/components/input/calculateNodeHeight.tsx @@ -155,5 +155,6 @@ export default function calculateNodeHeight( minHeight: `${minHeight}px`, maxHeight: `${maxHeight}px`, overflowY, + resize: 'none', }; } diff --git a/components/typography/Base.tsx b/components/typography/Base.tsx index 80cbc1568..99fad0310 100644 --- a/components/typography/Base.tsx +++ b/components/typography/Base.tsx @@ -26,8 +26,8 @@ import { watchEffect, nextTick, CSSProperties, - toRaw, computed, + toRaw, } from 'vue'; import { AutoSizeType } from '../input/ResizableTextArea'; import useConfigInject from '../_util/hooks/useConfigInject'; @@ -37,29 +37,19 @@ export type BaseType = 'secondary' | 'success' | 'warning' | 'danger'; const isLineClampSupport = isStyleSupport('webkitLineClamp'); const isTextOverflowSupport = isStyleSupport('textOverflow'); -function toArray(value: any) { - let ret = value; - if (value === undefined) { - ret = []; - } else if (!Array.isArray(value)) { - ret = [value]; - } - return ret; -} - interface CopyConfig { text?: string; onCopy?: () => void; - icon?: VNodeTypes; - tooltips?: boolean | VNodeTypes; + tooltip?: boolean; } interface EditConfig { editing?: boolean; - icon?: VNodeTypes; - tooltip?: boolean | VNodeTypes; + tooltip?: boolean; onStart?: () => void; onChange?: (value: string) => void; + onCancel?: () => void; + onEnd?: () => void; maxlength?: number; autoSize?: boolean | AutoSizeType; } @@ -71,7 +61,7 @@ export interface EllipsisConfig { symbol?: VNodeTypes; onExpand?: EventHandlerNonNull; onEllipsis?: (ellipsis: boolean) => void; - tooltip?: boolean | VNodeTypes; + tooltip?: boolean; } export interface BlockProps extends TypographyProps { @@ -107,7 +97,8 @@ const ELLIPSIS_STR = '...'; const Base = defineComponent({ name: 'Base', inheritAttrs: false, - setup(props, { slots, attrs }) { + emits: ['update:content'], + setup(props, { slots, attrs, emit }) { const { prefixCls } = useConfigInject('typography', props); const state = reactive({ @@ -127,6 +118,8 @@ const Base = defineComponent({ copyId: undefined, rafId: undefined, prevProps: undefined, + + originContent: '', }); const contentRef = ref(); @@ -186,7 +179,7 @@ const Base = defineComponent({ } function getChildrenText(): string { - return props.ellipsis || props.editable ? props.content : contentRef.value.text; + return props.ellipsis || props.editable ? props.content : contentRef.value?.$el?.innerText; } // =============== Expand =============== @@ -197,15 +190,21 @@ const Base = defineComponent({ } // ================ Edit ================ function onEditClick() { + state.originContent = props.content; triggerEdit(true); } function onEditChange(value: string) { - const { onChange } = editable.value; - onChange?.(value); - + onContentChange(value); triggerEdit(false); } + function onContentChange(value: string) { + const { onChange } = editable.value; + if (value !== props.content) { + onChange?.(value); + emit('update:content', value); + } + } function onEditCancel() { triggerEdit(false); @@ -290,7 +289,14 @@ const Base = defineComponent({ const syncEllipsis = () => { const { ellipsisText, isEllipsis } = state; const { rows, suffix, onEllipsis } = ellipsis.value; - if (!rows || rows < 0 || !contentRef.value?.$el || state.expanded) return; + if ( + !rows || + rows < 0 || + !contentRef.value?.$el || + state.expanded || + props.content === undefined + ) + return; // Do not measure if css already support ellipsis if (canUseCSSEllipsis.value) return; @@ -365,9 +371,9 @@ const Base = defineComponent({ function renderEdit() { if (!props.editable) return; - const { icon, tooltip } = props.editable as EditConfig; - - const title = tooltip || state.editStr; + const { tooltip } = props.editable as EditConfig; + const icon = slots.editableIcon ? slots.editableIcon() : ; + const title = slots.editableTooltip ? slots.editableTooltip() : state.editStr; const ariaLabel = typeof title === 'string' ? title : ''; return ( @@ -378,7 +384,7 @@ const Base = defineComponent({ onClick={onEditClick} aria-label={ariaLabel} > - {icon || } + {icon} ); @@ -387,17 +393,19 @@ const Base = defineComponent({ function renderCopy() { if (!props.copyable) return; - const { tooltips } = props.copyable as CopyConfig; - let tooltipNodes = toArray(tooltips) as VNodeTypes[]; - if (tooltipNodes.length === 0) { - tooltipNodes = [state.copyStr, state.copiedStr]; - } - const title = state.copied ? tooltipNodes[1] : tooltipNodes[0]; + const { tooltip } = props.copyable as CopyConfig; + const defaultTitle = state.copied ? state.copiedStr : state.copyStr; + const title = slots.copyableTooltip + ? slots.copyableTooltip({ copied: state.copied }) + : defaultTitle; const ariaLabel = typeof title === 'string' ? title : ''; - const icons = toArray((props.copyable as CopyConfig).icon); + const defaultIcon = state.copied ? : ; + const icon = slots.copyableIcon + ? slots.copyableIcon({ copied: !!state.copied }) + : defaultIcon; return ( - + ({ onClick={onCopyClick} aria-label={ariaLabel} > - {state.copied ? icons[1] || : icons[0] || } + {icon} ); @@ -422,9 +430,11 @@ const Base = defineComponent({ style={style} prefixCls={prefixCls.value} value={props.content} + originContent={state.originContent} maxlength={maxlength} autoSize={autoSize} onSave={onEditChange} + onChange={onContentChange} onCancel={onEditCancel} /> ); @@ -438,7 +448,9 @@ const Base = defineComponent({ const { editing } = editable.value; const children = props.ellipsis || props.editable - ? props.content + ? 'content' in props + ? props.content + : slots.default?.() : slots.default ? slots.default() : props.content; @@ -450,7 +462,7 @@ const Base = defineComponent({ { - const { type, disabled, title, content, class: className, style, ...restProps } = { + const { type, disabled, content, class: className, style, ...restProps } = { ...props, ...attrs, }; @@ -485,26 +497,25 @@ const Base = defineComponent({ // Only use js ellipsis when css ellipsis not support if (rows && state.isEllipsis && !state.expanded && !cssEllipsis) { - ariaLabel = title; - if (!title) { - ariaLabel = content; + const { title } = restProps; + let restContent = title || ''; + + if (!title && (typeof children === 'string' || typeof children === 'number')) { + restContent = String(children); } + + // show rest content as title on symbol + restContent = restContent?.slice(String(state.ellipsisContent || '').length); // We move full content to outer element to avoid repeat read the content by accessibility textNode = ( - + ); - // If provided tooltip, we need wrap with span to let Tooltip inject events - if (tooltip) { - textNode = ( - - {textNode} - - ); - } } else { textNode = ( <> @@ -516,6 +527,9 @@ const Base = defineComponent({ textNode = wrapperDecorations(props, textNode); + const showTooltip = + tooltip && rows && state.isEllipsis && !state.expanded && !cssEllipsis; + const title = slots.ellipsisTooltip ? slots.ellipsisTooltip() : tooltip; return ( ({ aria-label={ariaLabel} {...textProps} > - {textNode} + {showTooltip ? ( + + {textNode} + + ) : ( + textNode + )} {renderOperations()} diff --git a/components/typography/Editable.tsx b/components/typography/Editable.tsx index 9d5c9f9fe..a46557d6f 100644 --- a/components/typography/Editable.tsx +++ b/components/typography/Editable.tsx @@ -12,8 +12,11 @@ const Editable = defineComponent({ autoSize: PropTypes.oneOfType([PropTypes.looseBool, PropTypes.object]), onSave: PropTypes.func, onCancel: PropTypes.func, + onEnd: PropTypes.func, + onChange: PropTypes.func, + originContent: PropTypes.string, }, - emits: ['save', 'cancel'], + emits: ['save', 'cancel', 'end', 'change'], setup(props, { emit }) { const state = reactive({ current: props.value || '', @@ -21,9 +24,6 @@ const Editable = defineComponent({ inComposition: false, cancelFlag: false, }); - - let cancelFlag: boolean = false; - watch( () => props.value, current => { @@ -49,6 +49,7 @@ const Editable = defineComponent({ function onChange({ target: { value } }) { state.current = value.replace(/[\r\n]/g, ''); + emit('change', state.current); } function onCompositionStart() { @@ -61,6 +62,9 @@ const Editable = defineComponent({ function onKeyDown(e: KeyboardEvent) { const { keyCode } = e; + if (keyCode === KeyCode.ENTER) { + e.preventDefault(); + } // We don't record keyCode when IME is using if (state.inComposition) return; @@ -81,18 +85,16 @@ const Editable = defineComponent({ ) { if (keyCode === KeyCode.ENTER) { confirmChange(); + emit('end'); } else if (keyCode === KeyCode.ESC) { - // avoid chrome trigger blur - cancelFlag = true; + state.current = props.originContent; emit('cancel'); } } } function onBlur() { - if (!cancelFlag) { - confirmChange(); - } + confirmChange(); } function confirmChange() { @@ -100,7 +102,7 @@ const Editable = defineComponent({ } return () => ( -
+