TextArea.js 11 KB

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