|
|
|
import type { VNode } from 'vue';
|
|
|
|
import {
|
|
|
|
onMounted,
|
|
|
|
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';
|
|
|
|
|
|
|
|
const RESIZE_STATUS_NONE = 0;
|
|
|
|
const RESIZE_STATUS_RESIZING = 1;
|
|
|
|
const RESIZE_STATUS_RESIZED = 2;
|
|
|
|
|
|
|
|
const ResizableTextArea = defineComponent({
|
|
|
|
compatConfig: { MODE: 3 },
|
|
|
|
name: 'ResizableTextArea',
|
|
|
|
inheritAttrs: false,
|
|
|
|
props: textAreaProps(),
|
|
|
|
setup(props, { attrs, emit, expose }) {
|
|
|
|
let nextFrameActionId: any;
|
|
|
|
let resizeFrameId: any;
|
|
|
|
const textAreaRef = ref();
|
|
|
|
const textareaStyles = ref({});
|
|
|
|
const resizeStatus = ref(RESIZE_STATUS_NONE);
|
|
|
|
onBeforeUnmount(() => {
|
|
|
|
raf.cancel(nextFrameActionId);
|
|
|
|
raf.cancel(resizeFrameId);
|
|
|
|
});
|
|
|
|
|
|
|
|
// https://github.com/ant-design/ant-design/issues/21870
|
|
|
|
const fixFirefoxAutoScroll = () => {
|
|
|
|
try {
|
|
|
|
if (document.activeElement === textAreaRef.value) {
|
|
|
|
const currentStart = textAreaRef.value.selectionStart;
|
|
|
|
const currentEnd = textAreaRef.value.selectionEnd;
|
|
|
|
textAreaRef.value.setSelectionRange(currentStart, currentEnd);
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
// Fix error in Chrome:
|
|
|
|
// Failed to read the 'selectionStart' property from 'HTMLInputElement'
|
|
|
|
// 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 autoSize = props.autoSize || props.autosize;
|
|
|
|
if (autoSize) {
|
|
|
|
resizeOnNextFrame();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
warning(
|
|
|
|
props.autosize === undefined,
|
|
|
|
'Input.TextArea',
|
|
|
|
'autosize is deprecated, please use autoSize instead.',
|
|
|
|
);
|
|
|
|
|
|
|
|
const renderTextArea = () => {
|
|
|
|
const { prefixCls, autoSize, autosize, disabled } = props;
|
|
|
|
const otherProps = omit(props, [
|
|
|
|
'prefixCls',
|
|
|
|
'onPressEnter',
|
|
|
|
'autoSize',
|
|
|
|
'autosize',
|
|
|
|
'defaultValue',
|
|
|
|
'allowClear',
|
|
|
|
'type',
|
|
|
|
'lazy',
|
|
|
|
'maxlength',
|
|
|
|
'valueModifiers',
|
|
|
|
]);
|
|
|
|
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 textareaProps: any = {
|
|
|
|
...otherProps,
|
|
|
|
...attrs,
|
|
|
|
style,
|
|
|
|
class: cls,
|
|
|
|
};
|
|
|
|
if (!textareaProps.autofocus) {
|
|
|
|
delete textareaProps.autofocus;
|
|
|
|
}
|
|
|
|
if (textareaProps.rows === 0) {
|
|
|
|
delete textareaProps.rows;
|
|
|
|
}
|
|
|
|
return (
|
|
|
|
<ResizeObserver onResize={handleResize} disabled={!(autoSize || autosize)}>
|
|
|
|
{withDirectives((<textarea {...textareaProps} ref={textAreaRef} />) as VNode, [
|
|
|
|
[antInput],
|
|
|
|
])}
|
|
|
|
</ResizeObserver>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
watch(
|
|
|
|
() => props.value,
|
|
|
|
() => {
|
|
|
|
nextTick(() => {
|
|
|
|
resizeTextarea();
|
|
|
|
});
|
|
|
|
},
|
|
|
|
);
|
|
|
|
onMounted(() => {
|
|
|
|
nextTick(() => {
|
|
|
|
resizeTextarea();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
const instance = getCurrentInstance();
|
|
|
|
expose({
|
|
|
|
resizeTextarea,
|
|
|
|
textArea: textAreaRef,
|
|
|
|
instance,
|
|
|
|
});
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
return renderTextArea();
|
|
|
|
};
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
export default ResizableTextArea;
|