feat: textarea support word count (#3009)
parent
9ac18c5e52
commit
2fb52d6e26
|
@ -34,7 +34,6 @@ const ClearableLabeledInput = {
|
||||||
addonAfter: PropTypes.any,
|
addonAfter: PropTypes.any,
|
||||||
readonly: PropTypes.bool,
|
readonly: PropTypes.bool,
|
||||||
isFocused: PropTypes.bool,
|
isFocused: PropTypes.bool,
|
||||||
style: PropTypes.object,
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
renderClearIcon(prefixCls) {
|
renderClearIcon(prefixCls) {
|
||||||
|
@ -74,6 +73,7 @@ const ClearableLabeledInput = {
|
||||||
|
|
||||||
renderLabeledIcon(prefixCls, element) {
|
renderLabeledIcon(prefixCls, element) {
|
||||||
const props = this.$props;
|
const props = this.$props;
|
||||||
|
const { style } = this.$attrs;
|
||||||
const suffix = this.renderSuffix(prefixCls);
|
const suffix = this.renderSuffix(prefixCls);
|
||||||
if (!hasPrefixSuffix(this)) {
|
if (!hasPrefixSuffix(this)) {
|
||||||
return cloneElement(element, {
|
return cloneElement(element, {
|
||||||
|
@ -94,7 +94,7 @@ const ClearableLabeledInput = {
|
||||||
props.suffix && props.allowClear && this.$props.value,
|
props.suffix && props.allowClear && this.$props.value,
|
||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<span class={affixWrapperCls} style={props.style}>
|
<span class={affixWrapperCls} style={style}>
|
||||||
{prefix}
|
{prefix}
|
||||||
{cloneElement(element, {
|
{cloneElement(element, {
|
||||||
style: null,
|
style: null,
|
||||||
|
|
|
@ -6,11 +6,13 @@ import { hasProp, getOptionProps } from '../_util/props-util';
|
||||||
import { ConfigConsumerProps } from '../config-provider';
|
import { ConfigConsumerProps } from '../config-provider';
|
||||||
import { fixControlledValue, resolveOnChange } from './Input';
|
import { fixControlledValue, resolveOnChange } from './Input';
|
||||||
import PropTypes from '../_util/vue-types';
|
import PropTypes from '../_util/vue-types';
|
||||||
|
import classNames from '../_util/classNames';
|
||||||
|
|
||||||
const TextAreaProps = {
|
const TextAreaProps = {
|
||||||
...inputProps,
|
...inputProps,
|
||||||
autosize: PropTypes.oneOfType([Object, Boolean]),
|
autosize: PropTypes.oneOfType([Object, Boolean]),
|
||||||
autoSize: PropTypes.oneOfType([Object, Boolean]),
|
autoSize: PropTypes.oneOfType([Object, Boolean]),
|
||||||
|
showCount: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -100,9 +102,13 @@ export default {
|
||||||
|
|
||||||
renderTextArea(prefixCls) {
|
renderTextArea(prefixCls) {
|
||||||
const props = getOptionProps(this);
|
const props = getOptionProps(this);
|
||||||
|
const { style, class: customClass } = this.$attrs;
|
||||||
const resizeProps = {
|
const resizeProps = {
|
||||||
...props,
|
...props,
|
||||||
...this.$attrs,
|
...this.$attrs,
|
||||||
|
style: style && !props.showCount,
|
||||||
|
class: customClass && !props.showCount,
|
||||||
|
showCount: null,
|
||||||
prefixCls,
|
prefixCls,
|
||||||
onInput: this.handleChange,
|
onInput: this.handleChange,
|
||||||
onChange: this.handleChange,
|
onChange: this.handleChange,
|
||||||
|
@ -112,19 +118,44 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
render() {
|
render() {
|
||||||
const { stateValue, prefixCls: customizePrefixCls } = this;
|
const { stateValue, prefixCls: customizePrefixCls, maxlength, showCount } = this;
|
||||||
|
const { style, class: customClass } = this.$attrs;
|
||||||
const getPrefixCls = this.configProvider.getPrefixCls;
|
const getPrefixCls = this.configProvider.getPrefixCls;
|
||||||
const prefixCls = getPrefixCls('input', customizePrefixCls);
|
const prefixCls = getPrefixCls('input', customizePrefixCls);
|
||||||
|
let value = fixControlledValue(stateValue);
|
||||||
|
// Max length value
|
||||||
|
const hasMaxlength = Number(maxlength) > 0;
|
||||||
|
value = hasMaxlength ? value.slice(0, maxlength) : value;
|
||||||
const props = {
|
const props = {
|
||||||
...getOptionProps(this),
|
...getOptionProps(this),
|
||||||
...this.$attrs,
|
...this.$attrs,
|
||||||
prefixCls,
|
prefixCls,
|
||||||
inputType: 'text',
|
inputType: 'text',
|
||||||
value: fixControlledValue(stateValue),
|
|
||||||
element: this.renderTextArea(prefixCls),
|
element: this.renderTextArea(prefixCls),
|
||||||
handleReset: this.handleReset,
|
handleReset: this.handleReset,
|
||||||
};
|
};
|
||||||
return <ClearableLabeledInput {...props} ref={this.saveClearableInput} />;
|
|
||||||
|
let textareaNode = (
|
||||||
|
<ClearableLabeledInput {...props} value={value} ref={this.saveClearableInput} />
|
||||||
|
);
|
||||||
|
|
||||||
|
if (showCount) {
|
||||||
|
const valueLength = [...value].length;
|
||||||
|
const dataCount = `${valueLength}${hasMaxlength ? ` / ${maxlength}` : ''}`;
|
||||||
|
textareaNode = (
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
`${prefixCls}-textarea`,
|
||||||
|
`${prefixCls}-textarea-show-count`,
|
||||||
|
customClass,
|
||||||
|
)}
|
||||||
|
style={style}
|
||||||
|
data-count={dataCount}
|
||||||
|
>
|
||||||
|
{textareaNode}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return textareaNode;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,3 +7,5 @@ exports[`Input.Search should support suffix 1`] = `<span class="ant-input-search
|
||||||
exports[`TextArea should support disabled 1`] = `<textarea disabled="" class="ant-input ant-input-disabled"></textarea>`;
|
exports[`TextArea should support disabled 1`] = `<textarea disabled="" class="ant-input ant-input-disabled"></textarea>`;
|
||||||
|
|
||||||
exports[`TextArea should support maxlength 1`] = `<textarea maxlength="10" class="ant-input"></textarea>`;
|
exports[`TextArea should support maxlength 1`] = `<textarea maxlength="10" class="ant-input"></textarea>`;
|
||||||
|
|
||||||
|
exports[`TextArea should support showCount 1`] = `<div class="ant-input-textarea ant-input-textarea-show-count" data-count="3 / 10"><textarea maxlength="10" class="ant-input"></textarea></div>`;
|
||||||
|
|
|
@ -29,7 +29,7 @@ describe('Input', () => {
|
||||||
props: { allowClear: true, defaultValue: '111', disabled: true },
|
props: { allowClear: true, defaultValue: '111', disabled: true },
|
||||||
sync: false,
|
sync: false,
|
||||||
});
|
});
|
||||||
expect(wrapper.findAll('.ant-input-clear-icon').length).toBe(0);
|
expect(wrapper.findAll('.ant-input-clear-icon-hidden').length).toBeTruthy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -68,6 +68,17 @@ describe('TextArea', () => {
|
||||||
expect(wrapper.html()).toMatchSnapshot();
|
expect(wrapper.html()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should support showCount', async () => {
|
||||||
|
const wrapper = mount(TextArea, {
|
||||||
|
props: { showCount: true, defaultValue: '111', maxlength: 10 },
|
||||||
|
sync: false,
|
||||||
|
});
|
||||||
|
expect(wrapper.find('.ant-input-textarea-show-count')).toBeTruthy();
|
||||||
|
await asyncExpect(() => {
|
||||||
|
expect(wrapper.html()).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// describe('As Form Control', () => {
|
// describe('As Form Control', () => {
|
||||||
|
|
|
@ -57,4 +57,13 @@
|
||||||
margin: 8px 8px 0 0;
|
margin: 8px 8px 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.@{ant-prefix}-input-textarea {
|
||||||
|
&-show-count::after {
|
||||||
|
display: block;
|
||||||
|
color: @text-color-secondary;
|
||||||
|
text-align: right;
|
||||||
|
content: attr(data-count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@import './search-input';
|
@import './search-input';
|
||||||
|
|
|
@ -31,5 +31,6 @@ export declare class TextArea extends AntdComponent {
|
||||||
* @type boolean
|
* @type boolean
|
||||||
*/
|
*/
|
||||||
allowClear?: boolean;
|
allowClear?: boolean;
|
||||||
|
showCount?: boolean;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue