From 73f0a29acf6d3ee1d347b86694f66983ca4bade7 Mon Sep 17 00:00:00 2001 From: tangjinzhou <415800467@qq.com> Date: Mon, 18 Dec 2023 14:31:53 +0800 Subject: [PATCH] fix: textarea cursor error, close #7121 --- components/input/ResizableTextArea.tsx | 160 +++++++++++++---------- components/input/calculateNodeHeight.tsx | 48 ++++--- 2 files changed, 120 insertions(+), 88 deletions(-) diff --git a/components/input/ResizableTextArea.tsx b/components/input/ResizableTextArea.tsx index b9c7a3320..862a7cb8a 100644 --- a/components/input/ResizableTextArea.tsx +++ b/components/input/ResizableTextArea.tsx @@ -1,26 +1,26 @@ -import type { VNode } from 'vue'; +import type { VNode, CSSProperties } from 'vue'; import { - onMounted, + computed, + watchEffect, getCurrentInstance, watch, onBeforeUnmount, ref, - nextTick, defineComponent, withDirectives, } from 'vue'; import ResizeObserver from '../vc-resize-observer'; import classNames from '../_util/classNames'; -import calculateNodeHeight from './calculateNodeHeight'; import raf from '../_util/raf'; import warning from '../_util/warning'; import antInput from '../_util/antInputDirective'; import omit from '../_util/omit'; import { textAreaProps } from './inputProps'; +import calculateAutoSizeStyle from './calculateNodeHeight'; -const RESIZE_STATUS_NONE = 0; -const RESIZE_STATUS_RESIZING = 1; -const RESIZE_STATUS_RESIZED = 2; +const RESIZE_START = 0; +const RESIZE_MEASURING = 1; +const RESIZE_STABLE = 2; const ResizableTextArea = defineComponent({ compatConfig: { MODE: 3 }, @@ -32,7 +32,7 @@ const ResizableTextArea = defineComponent({ let resizeFrameId: any; const textAreaRef = ref(); const textareaStyles = ref({}); - const resizeStatus = ref(RESIZE_STATUS_NONE); + const resizeStatus = ref(RESIZE_STABLE); onBeforeUnmount(() => { raf.cancel(nextFrameActionId); raf.cancel(resizeFrameId); @@ -44,7 +44,9 @@ const ResizableTextArea = defineComponent({ if (document.activeElement === textAreaRef.value) { const currentStart = textAreaRef.value.selectionStart; const currentEnd = textAreaRef.value.selectionEnd; + const scrollTop = textAreaRef.value.scrollTop; textAreaRef.value.setSelectionRange(currentStart, currentEnd); + textAreaRef.value.scrollTop = scrollTop; } } catch (e) { // Fix error in Chrome: @@ -52,41 +54,82 @@ const ResizableTextArea = defineComponent({ // http://stackoverflow.com/q/21177489/3040605 } }; - - const resizeTextarea = () => { - const autoSize = props.autoSize || props.autosize; - if (!autoSize || !textAreaRef.value) { - return; - } - const { minRows, maxRows } = autoSize; - textareaStyles.value = calculateNodeHeight(textAreaRef.value, false, minRows, maxRows); - resizeStatus.value = RESIZE_STATUS_RESIZING; - raf.cancel(resizeFrameId); - resizeFrameId = raf(() => { - resizeStatus.value = RESIZE_STATUS_RESIZED; - resizeFrameId = raf(() => { - resizeStatus.value = RESIZE_STATUS_NONE; - fixFirefoxAutoScroll(); - }); - }); - }; - - const resizeOnNextFrame = () => { - raf.cancel(nextFrameActionId); - nextFrameActionId = raf(resizeTextarea); - }; - - const handleResize = (size: { width: number; height: number }) => { - if (resizeStatus.value !== RESIZE_STATUS_NONE) { - return; - } - emit('resize', size); - + const minRows = ref(); + const maxRows = ref(); + watchEffect(() => { const autoSize = props.autoSize || props.autosize; if (autoSize) { - resizeOnNextFrame(); + minRows.value = autoSize.minRows; + maxRows.value = autoSize.maxRows; + } else { + minRows.value = undefined; + maxRows.value = undefined; + } + }); + const needAutoSize = computed(() => !!(props.autoSize || props.autosize)); + const startResize = () => { + resizeStatus.value = RESIZE_START; + }; + watch( + [() => props.value, minRows, maxRows, needAutoSize], + () => { + if (needAutoSize.value) { + startResize(); + } + }, + { immediate: true, flush: 'post' }, + ); + const autoSizeStyle = ref(); + watch( + [resizeStatus, textAreaRef], + () => { + if (!textAreaRef.value) return; + if (resizeStatus.value === RESIZE_START) { + resizeStatus.value = RESIZE_MEASURING; + } else if (resizeStatus.value === RESIZE_MEASURING) { + const textareaStyles = calculateAutoSizeStyle( + textAreaRef.value, + false, + minRows.value, + maxRows.value, + ); + resizeStatus.value = RESIZE_STABLE; + autoSizeStyle.value = textareaStyles; + } else { + fixFirefoxAutoScroll(); + } + }, + { immediate: true, flush: 'post' }, + ); + const instance = getCurrentInstance(); + const resizeRafRef = ref(); + const cleanRaf = () => { + raf.cancel(resizeRafRef.value); + }; + const onInternalResize = (size: { width: number; height: number }) => { + if (resizeStatus.value === RESIZE_STABLE) { + emit('resize', size); + + if (needAutoSize.value) { + cleanRaf(); + resizeRafRef.value = raf(() => { + startResize(); + }); + } } }; + onBeforeUnmount(() => { + cleanRaf(); + }); + const resizeTextarea = () => { + startResize(); + }; + + expose({ + resizeTextarea, + textArea: textAreaRef, + instance, + }); warning( props.autosize === undefined, 'Input.TextArea', @@ -94,7 +137,7 @@ const ResizableTextArea = defineComponent({ ); const renderTextArea = () => { - const { prefixCls, autoSize, autosize, disabled } = props; + const { prefixCls, disabled } = props; const otherProps = omit(props, [ 'prefixCls', 'onPressEnter', @@ -110,19 +153,20 @@ const ResizableTextArea = defineComponent({ const cls = classNames(prefixCls, attrs.class, { [`${prefixCls}-disabled`]: disabled, }); - const style = [ - attrs.style, - textareaStyles.value, - resizeStatus.value === RESIZE_STATUS_RESIZING - ? { overflowX: 'hidden', overflowY: 'hidden' } - : null, - ]; + const mergedAutoSizeStyle = needAutoSize.value ? autoSizeStyle.value : null; + const style = [attrs.style, textareaStyles.value, mergedAutoSizeStyle]; const textareaProps: any = { ...otherProps, ...attrs, style, class: cls, }; + if (resizeStatus.value === RESIZE_START || resizeStatus.value === RESIZE_MEASURING) { + style.push({ + overflowX: 'hidden', + overflowY: 'hidden', + }); + } if (!textareaProps.autofocus) { delete textareaProps.autofocus; } @@ -130,7 +174,7 @@ const ResizableTextArea = defineComponent({ delete textareaProps.rows; } return ( - + {withDirectives((