TextArea.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
  2. import _extends from "@babel/runtime/helpers/esm/extends";
  3. import { resolveDirective as _resolveDirective, createVNode as _createVNode } from "vue";
  4. import { computed, defineComponent, getCurrentInstance, nextTick, shallowRef, watch, watchEffect } from 'vue';
  5. import ClearableLabeledInput from './ClearableLabeledInput';
  6. import ResizableTextArea from './ResizableTextArea';
  7. import { textAreaProps } from './inputProps';
  8. import { fixControlledValue, resolveOnChange, triggerFocus } from '../vc-input/utils/commonUtils';
  9. import classNames from '../_util/classNames';
  10. import { FormItemInputContext, useInjectFormItemContext } from '../form/FormItemContext';
  11. import useConfigInject from '../config-provider/hooks/useConfigInject';
  12. import omit from '../_util/omit';
  13. import { getMergedStatus, getStatusClassNames } from '../_util/statusUtils';
  14. // CSSINJS
  15. import useStyle from './style';
  16. import { useInjectDisabled } from '../config-provider/DisabledContext';
  17. function fixEmojiLength(value, maxLength) {
  18. return [...(value || '')].slice(0, maxLength).join('');
  19. }
  20. function setTriggerValue(isCursorInEnd, preValue, triggerValue, maxLength) {
  21. let newTriggerValue = triggerValue;
  22. if (isCursorInEnd) {
  23. // 光标在尾部,直接截断
  24. newTriggerValue = fixEmojiLength(triggerValue, maxLength);
  25. } else if ([...(preValue || '')].length < triggerValue.length && [...(triggerValue || '')].length > maxLength) {
  26. // 光标在中间,如果最后的值超过最大值,则采用原先的值
  27. newTriggerValue = preValue;
  28. }
  29. return newTriggerValue;
  30. }
  31. export default defineComponent({
  32. compatConfig: {
  33. MODE: 3
  34. },
  35. name: 'ATextarea',
  36. inheritAttrs: false,
  37. props: textAreaProps(),
  38. setup(props, _ref) {
  39. let {
  40. attrs,
  41. expose,
  42. emit
  43. } = _ref;
  44. var _a;
  45. const formItemContext = useInjectFormItemContext();
  46. const formItemInputContext = FormItemInputContext.useInject();
  47. const mergedStatus = computed(() => getMergedStatus(formItemInputContext.status, props.status));
  48. const stateValue = shallowRef((_a = props.value) !== null && _a !== void 0 ? _a : props.defaultValue);
  49. const resizableTextArea = shallowRef();
  50. const mergedValue = shallowRef('');
  51. const {
  52. prefixCls,
  53. size,
  54. direction
  55. } = useConfigInject('input', props);
  56. // Style
  57. const [wrapSSR, hashId] = useStyle(prefixCls);
  58. const disabled = useInjectDisabled();
  59. const showCount = computed(() => {
  60. return props.showCount === '' || props.showCount || false;
  61. });
  62. // Max length value
  63. const hasMaxLength = computed(() => Number(props.maxlength) > 0);
  64. const compositing = shallowRef(false);
  65. const oldCompositionValueRef = shallowRef();
  66. const oldSelectionStartRef = shallowRef(0);
  67. const onInternalCompositionStart = e => {
  68. compositing.value = true;
  69. // 拼音输入前保存一份旧值
  70. oldCompositionValueRef.value = mergedValue.value;
  71. // 保存旧的光标位置
  72. oldSelectionStartRef.value = e.currentTarget.selectionStart;
  73. emit('compositionstart', e);
  74. };
  75. const onInternalCompositionEnd = e => {
  76. var _a;
  77. compositing.value = false;
  78. let triggerValue = e.currentTarget.value;
  79. if (hasMaxLength.value) {
  80. const isCursorInEnd = oldSelectionStartRef.value >= props.maxlength + 1 || oldSelectionStartRef.value === ((_a = oldCompositionValueRef.value) === null || _a === void 0 ? void 0 : _a.length);
  81. triggerValue = setTriggerValue(isCursorInEnd, oldCompositionValueRef.value, triggerValue, props.maxlength);
  82. }
  83. // Patch composition onChange when value changed
  84. if (triggerValue !== mergedValue.value) {
  85. setValue(triggerValue);
  86. resolveOnChange(e.currentTarget, e, triggerChange, triggerValue);
  87. }
  88. emit('compositionend', e);
  89. };
  90. const instance = getCurrentInstance();
  91. watch(() => props.value, () => {
  92. var _a;
  93. if ('value' in instance.vnode.props || {}) {
  94. stateValue.value = (_a = props.value) !== null && _a !== void 0 ? _a : '';
  95. }
  96. });
  97. const focus = option => {
  98. var _a;
  99. triggerFocus((_a = resizableTextArea.value) === null || _a === void 0 ? void 0 : _a.textArea, option);
  100. };
  101. const blur = () => {
  102. var _a, _b;
  103. (_b = (_a = resizableTextArea.value) === null || _a === void 0 ? void 0 : _a.textArea) === null || _b === void 0 ? void 0 : _b.blur();
  104. };
  105. const setValue = (value, callback) => {
  106. if (stateValue.value === value) {
  107. return;
  108. }
  109. if (props.value === undefined) {
  110. stateValue.value = value;
  111. } else {
  112. nextTick(() => {
  113. var _a, _b, _c;
  114. if (resizableTextArea.value.textArea.value !== mergedValue.value) {
  115. (_c = (_a = resizableTextArea.value) === null || _a === void 0 ? void 0 : (_b = _a.instance).update) === null || _c === void 0 ? void 0 : _c.call(_b);
  116. }
  117. });
  118. }
  119. nextTick(() => {
  120. callback && callback();
  121. });
  122. };
  123. const handleKeyDown = e => {
  124. if (e.keyCode === 13) {
  125. emit('pressEnter', e);
  126. }
  127. emit('keydown', e);
  128. };
  129. const onBlur = e => {
  130. const {
  131. onBlur
  132. } = props;
  133. onBlur === null || onBlur === void 0 ? void 0 : onBlur(e);
  134. formItemContext.onFieldBlur();
  135. };
  136. const triggerChange = e => {
  137. emit('update:value', e.target.value);
  138. emit('change', e);
  139. emit('input', e);
  140. formItemContext.onFieldChange();
  141. };
  142. const handleReset = e => {
  143. resolveOnChange(resizableTextArea.value.textArea, e, triggerChange);
  144. setValue('', () => {
  145. focus();
  146. });
  147. };
  148. const handleChange = e => {
  149. let triggerValue = e.target.value;
  150. if (stateValue.value === triggerValue) return;
  151. if (hasMaxLength.value) {
  152. // 1. 复制粘贴超过maxlength的情况 2.未超过maxlength的情况
  153. const target = e.target;
  154. const isCursorInEnd = target.selectionStart >= props.maxlength + 1 || target.selectionStart === triggerValue.length || !target.selectionStart;
  155. triggerValue = setTriggerValue(isCursorInEnd, mergedValue.value, triggerValue, props.maxlength);
  156. }
  157. resolveOnChange(e.currentTarget, e, triggerChange, triggerValue);
  158. setValue(triggerValue);
  159. };
  160. const renderTextArea = () => {
  161. var _a, _b;
  162. const {
  163. class: customClass
  164. } = attrs;
  165. const {
  166. bordered = true
  167. } = props;
  168. const resizeProps = _extends(_extends(_extends({}, omit(props, ['allowClear'])), attrs), {
  169. class: [{
  170. [`${prefixCls.value}-borderless`]: !bordered,
  171. [`${customClass}`]: customClass && !showCount.value,
  172. [`${prefixCls.value}-sm`]: size.value === 'small',
  173. [`${prefixCls.value}-lg`]: size.value === 'large'
  174. }, getStatusClassNames(prefixCls.value, mergedStatus.value), hashId.value],
  175. disabled: disabled.value,
  176. showCount: null,
  177. prefixCls: prefixCls.value,
  178. onInput: handleChange,
  179. onChange: handleChange,
  180. onBlur,
  181. onKeydown: handleKeyDown,
  182. onCompositionstart: onInternalCompositionStart,
  183. onCompositionend: onInternalCompositionEnd
  184. });
  185. if ((_a = props.valueModifiers) === null || _a === void 0 ? void 0 : _a.lazy) {
  186. delete resizeProps.onInput;
  187. }
  188. return _createVNode(ResizableTextArea, _objectSpread(_objectSpread({}, resizeProps), {}, {
  189. "id": (_b = resizeProps === null || resizeProps === void 0 ? void 0 : resizeProps.id) !== null && _b !== void 0 ? _b : formItemContext.id.value,
  190. "ref": resizableTextArea,
  191. "maxlength": props.maxlength,
  192. "lazy": props.lazy
  193. }), null);
  194. };
  195. expose({
  196. focus,
  197. blur,
  198. resizableTextArea
  199. });
  200. watchEffect(() => {
  201. let val = fixControlledValue(stateValue.value);
  202. if (!compositing.value && hasMaxLength.value && (props.value === null || props.value === undefined)) {
  203. // fix #27612 将value转为数组进行截取,解决 '😂'.length === 2 等emoji表情导致的截取乱码的问题
  204. val = fixEmojiLength(val, props.maxlength);
  205. }
  206. mergedValue.value = val;
  207. });
  208. return () => {
  209. var _a;
  210. const {
  211. maxlength,
  212. bordered = true,
  213. hidden
  214. } = props;
  215. const {
  216. style,
  217. class: customClass
  218. } = attrs;
  219. const inputProps = _extends(_extends(_extends({}, props), attrs), {
  220. prefixCls: prefixCls.value,
  221. inputType: 'text',
  222. handleReset,
  223. direction: direction.value,
  224. bordered,
  225. style: showCount.value ? undefined : style,
  226. hashId: hashId.value,
  227. disabled: (_a = props.disabled) !== null && _a !== void 0 ? _a : disabled.value
  228. });
  229. let textareaNode = _createVNode(ClearableLabeledInput, _objectSpread(_objectSpread({}, inputProps), {}, {
  230. "value": mergedValue.value,
  231. "status": props.status
  232. }), {
  233. element: renderTextArea
  234. });
  235. if (showCount.value || formItemInputContext.hasFeedback) {
  236. const valueLength = [...mergedValue.value].length;
  237. let dataCount = '';
  238. if (typeof showCount.value === 'object') {
  239. dataCount = showCount.value.formatter({
  240. value: mergedValue.value,
  241. count: valueLength,
  242. maxlength
  243. });
  244. } else {
  245. dataCount = `${valueLength}${hasMaxLength.value ? ` / ${maxlength}` : ''}`;
  246. }
  247. textareaNode = _createVNode("div", {
  248. "hidden": hidden,
  249. "class": classNames(`${prefixCls.value}-textarea`, {
  250. [`${prefixCls.value}-textarea-rtl`]: direction.value === 'rtl',
  251. [`${prefixCls.value}-textarea-show-count`]: showCount.value,
  252. [`${prefixCls.value}-textarea-in-form-item`]: formItemInputContext.isFormItemInput
  253. }, `${prefixCls.value}-textarea-show-count`, customClass, hashId.value),
  254. "style": style,
  255. "data-count": typeof dataCount !== 'object' ? dataCount : undefined
  256. }, [textareaNode, formItemInputContext.hasFeedback && _createVNode("span", {
  257. "class": `${prefixCls.value}-textarea-suffix`
  258. }, [formItemInputContext.feedbackIcon])]);
  259. }
  260. return wrapSSR(textareaNode);
  261. };
  262. }
  263. });