refactor: input

pull/5043/head
tangjinzhou 2021-12-15 20:48:24 +08:00
parent 7ec6831508
commit 5543ce6add
16 changed files with 810 additions and 491 deletions

View File

@ -39,7 +39,7 @@ export default defineComponent({
addonAfter: PropTypes.any,
readonly: PropTypes.looseBool,
focused: PropTypes.looseBool,
bordered: PropTypes.looseBool,
bordered: PropTypes.looseBool.def(true),
triggerFocus: { type: Function as PropType<() => void> },
},
setup(props, { slots, attrs }) {
@ -51,7 +51,7 @@ export default defineComponent({
}
};
const renderClearIcon = (prefixCls: string) => {
const { allowClear, value, disabled, readonly, handleReset } = props;
const { allowClear, value, disabled, readonly, handleReset, suffix = slots.suffix } = props;
if (!allowClear) {
return null;
}
@ -60,9 +60,12 @@ export default defineComponent({
return (
<CloseCircleFilled
onClick={handleReset}
// Do not trigger onBlur when clear input
onMousedown={e => e.preventDefault()}
class={classNames(
{
[`${className}-hidden`]: !needClear,
[`${className}-has-suffix`]: !!suffix,
},
className,
)}

View File

@ -29,7 +29,6 @@ export default defineComponent({
};
});
return () => {
const {} = props;
return (
<span
class={cls.value}

View File

@ -19,7 +19,6 @@ import { useInjectFormItemContext } from '../form/FormItemContext';
import omit from '../_util/omit';
import useConfigInject from '../_util/hooks/useConfigInject';
import type { ChangeEvent, FocusEventHandler } from '../_util/EventInterface';
import { controlDefaultValue } from '../_util/util';
export function fixControlledValue(value: string | number) {
if (typeof value === 'undefined' || value === null) {
@ -134,13 +133,21 @@ export default defineComponent({
let removePasswordTimeout: any;
const formItemContext = useInjectFormItemContext();
const { direction, prefixCls, size, autocomplete } = useConfigInject('input', props);
const stateValue = ref(props.value === controlDefaultValue ? props.defaultValue : props.value);
const stateValue = ref(props.value === undefined ? props.defaultValue : props.value);
const focused = ref(false);
watch(
() => props.value,
() => {
if (props.value !== controlDefaultValue) {
if (props.value !== undefined) {
stateValue.value = props.value;
}
},
);
watch(
() => props.disabled,
() => {
if (props.value !== undefined) {
stateValue.value = props.value;
}
},
@ -181,7 +188,7 @@ export default defineComponent({
expose({
focus,
blur,
inputRef,
input: inputRef,
stateValue,
setSelectionRange,
select,
@ -217,7 +224,7 @@ export default defineComponent({
if (stateValue.value === value) {
return;
}
if (props.value === controlDefaultValue) {
if (props.value === undefined) {
stateValue.value = value;
} else {
instance.update();
@ -234,9 +241,10 @@ export default defineComponent({
};
const handleChange = (e: ChangeEvent) => {
const { value, composing, isComposing } = e.target as any;
const { value, composing } = e.target as any;
// https://github.com/vueComponent/ant-design-vue/issues/2203
if (((isComposing || composing) && props.lazy) || stateValue.value === value) return;
if ((((e as any).isComposing || composing) && props.lazy) || stateValue.value === value)
return;
const newVal = e.target.value;
resolveOnChange(inputRef.value, e, triggerChange);
setValue(newVal, () => {
@ -270,6 +278,7 @@ export default defineComponent({
disabled,
bordered = true,
valueModifiers = {},
htmlSize,
} = props;
const otherProps = omit(props as InputProps & { inputType: any; placeholder: string }, [
'prefixCls',
@ -285,9 +294,11 @@ export default defineComponent({
'size',
'inputType',
'bordered',
'htmlSize',
]);
const inputProps = {
...otherProps,
...attrs,
autocomplete: autocomplete.value,
onChange: handleChange,
onInput: handleChange,
@ -302,6 +313,7 @@ export default defineComponent({
),
ref: inputRef,
key: 'ant-input',
size: htmlSize,
};
if (valueModifiers.lazy) {
delete inputProps.onInput;
@ -321,7 +333,7 @@ export default defineComponent({
inputType: 'input',
value: fixControlledValue(stateValue.value),
handleReset,
focused: focused.value,
focused: focused.value && props.disabled,
};
return (

View File

@ -1,125 +1,100 @@
import type { PropType, VNode } from 'vue';
import { nextTick, defineComponent, withDirectives } from 'vue';
import type { CSSProperties, VNode } from 'vue';
import {
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 BaseMixin from '../_util/BaseMixin';
import inputProps from './inputProps';
import PropTypes from '../_util/vue-types';
import { getOptionProps } from '../_util/props-util';
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;
export interface AutoSizeType {
minRows?: number;
maxRows?: number;
}
const TextAreaProps = {
...inputProps,
autosize: { type: [Boolean, Object] as PropType<AutoSizeType>, default: undefined },
autoSize: { type: [Boolean, Object] as PropType<AutoSizeType>, default: undefined },
onResize: PropTypes.func,
};
const ResizableTextArea = defineComponent({
name: 'ResizableTextArea',
mixins: [BaseMixin],
inheritAttrs: false,
props: TextAreaProps,
setup() {
return {
nextFrameActionId: undefined,
textArea: null,
resizeFrameId: undefined,
};
},
data() {
return {
textareaStyles: {},
resizeStatus: RESIZE_STATUS_NONE,
};
},
watch: {
value() {
nextTick(() => {
this.resizeTextarea();
});
},
},
mounted() {
this.resizeTextarea();
},
beforeUnmount() {
raf.cancel(this.nextFrameActionId);
raf.cancel(this.resizeFrameId);
},
methods: {
saveTextArea(textArea: HTMLTextAreaElement) {
this.textArea = textArea;
},
handleResize(size: { width: number; height: number }) {
const { resizeStatus } = this.$data;
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);
});
if (resizeStatus !== RESIZE_STATUS_NONE) {
return;
}
this.$emit('resize', size);
},
resizeOnNextFrame() {
raf.cancel(this.nextFrameActionId);
this.nextFrameActionId = raf(this.resizeTextarea);
},
resizeTextarea() {
const autoSize = this.$props.autoSize || this.$props.autosize;
if (!autoSize || !this.textArea) {
return;
}
const { minRows, maxRows } = autoSize;
const textareaStyles = calculateNodeHeight(this.textArea, false, minRows, maxRows);
this.setState({ textareaStyles, resizeStatus: RESIZE_STATUS_RESIZING }, () => {
raf.cancel(this.resizeFrameId);
this.resizeFrameId = raf(() => {
this.setState({ resizeStatus: RESIZE_STATUS_RESIZED }, () => {
this.resizeFrameId = raf(() => {
this.setState({ resizeStatus: RESIZE_STATUS_NONE });
this.fixFirefoxAutoScroll();
});
});
});
});
},
// https://github.com/ant-design/ant-design/issues/21870
fixFirefoxAutoScroll() {
const fixFirefoxAutoScroll = () => {
try {
if (document.activeElement === this.textArea) {
const currentStart = this.textArea.selectionStart;
const currentEnd = this.textArea.selectionEnd;
this.textArea.setSelectionRange(currentStart, currentEnd);
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
}
},
};
renderTextArea() {
const props: any = { ...getOptionProps(this), ...this.$attrs };
const { prefixCls, autoSize, autosize, disabled, class: className } = props;
const { textareaStyles, resizeStatus } = this.$data;
warning(
autosize === undefined,
'Input.TextArea',
'autosize is deprecated, please use autoSize instead.',
);
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',
@ -130,23 +105,19 @@ const ResizableTextArea = defineComponent({
'type',
'lazy',
]);
const cls = classNames(prefixCls, className, {
const cls = classNames(prefixCls, attrs.class, {
[`${prefixCls}-disabled`]: disabled,
});
// Fix https://github.com/ant-design/ant-design/issues/6776
// Make sure it could be reset when using form.getFieldDecorator
if ('value' in otherProps) {
otherProps.value = otherProps.value || '';
}
const style = {
...props.style,
...textareaStyles,
...(resizeStatus === RESIZE_STATUS_RESIZING
...(attrs.style as CSSProperties),
...textareaStyles.value,
...(resizeStatus.value === RESIZE_STATUS_RESIZING
? { overflowX: 'hidden', overflowY: 'hidden' }
: null),
};
const textareaProps: any = {
...otherProps,
...attrs,
style,
class: cls,
};
@ -154,17 +125,32 @@ const ResizableTextArea = defineComponent({
delete textareaProps.autofocus;
}
return (
<ResizeObserver onResize={this.handleResize} disabled={!(autoSize || autosize)}>
{withDirectives((<textarea {...textareaProps} ref={this.saveTextArea} />) as VNode, [
<ResizeObserver onResize={handleResize} disabled={!(autoSize || autosize)}>
{withDirectives((<textarea {...textareaProps} ref={textAreaRef} />) as VNode, [
[antInput],
])}
</ResizeObserver>
);
},
},
};
render() {
return this.renderTextArea();
watch(
() => props.value,
() => {
nextTick(() => {
resizeTextarea();
});
},
);
const instance = getCurrentInstance();
expose({
resizeTextarea,
textArea: textAreaRef,
instance,
});
return () => {
return renderTextArea();
};
},
});

View File

@ -49,7 +49,7 @@ export default defineComponent({
};
const onMousedown: MouseEventHandler = e => {
if (document.activeElement === inputRef.value?.inputRef.value) {
if (document.activeElement === inputRef.value?.input) {
e.preventDefault();
}
};
@ -84,17 +84,21 @@ export default defineComponent({
enterButtonAsElement.type.__ANT_BUTTON;
if (isAntdButton || enterButtonAsElement.tagName === 'button') {
button = cloneElement(enterButtonAsElement, {
onMousedown,
onClick: onSearch,
key: 'enterButton',
...(isAntdButton
? {
class: btnClassName,
size: size.value,
}
: {}),
});
button = cloneElement(
enterButtonAsElement,
{
onMousedown,
onClick: onSearch,
key: 'enterButton',
...(isAntdButton
? {
class: btnClassName,
size: size.value,
}
: {}),
},
false,
);
} else {
button = (
<Button

View File

@ -1,184 +1,219 @@
import { defineComponent, inject, nextTick } from 'vue';
import {
computed,
defineComponent,
getCurrentInstance,
nextTick,
onMounted,
ref,
watch,
watchEffect,
} from 'vue';
import ClearableLabeledInput from './ClearableLabeledInput';
import ResizableTextArea from './ResizableTextArea';
import inputProps from './inputProps';
import { hasProp, getOptionProps } from '../_util/props-util';
import { defaultConfigProvider } from '../config-provider';
import { fixControlledValue, resolveOnChange } from './Input';
import { textAreaProps } from './inputProps';
import type { InputFocusOptions } from './Input';
import { fixControlledValue, resolveOnChange, triggerFocus } from './Input';
import classNames from '../_util/classNames';
import PropTypes, { withUndefined } from '../_util/vue-types';
import { useInjectFormItemContext } from '../form/FormItemContext';
import type { FocusEventHandler } from '../_util/EventInterface';
import useConfigInject from '../_util/hooks/useConfigInject';
import omit from '../_util/omit';
const TextAreaProps = {
...inputProps,
autosize: withUndefined(PropTypes.oneOfType([Object, Boolean])),
autoSize: withUndefined(PropTypes.oneOfType([Object, Boolean])),
showCount: PropTypes.looseBool,
onCompositionstart: PropTypes.func,
onCompositionend: PropTypes.func,
valueModifiers: Object,
};
function fixEmojiLength(value: string, maxLength: number) {
return [...(value || '')].slice(0, maxLength).join('');
}
export default defineComponent({
name: 'ATextarea',
inheritAttrs: false,
props: {
...TextAreaProps,
},
setup() {
props: textAreaProps,
setup(props, { attrs, expose, emit }) {
const formItemContext = useInjectFormItemContext();
return {
configProvider: inject('configProvider', defaultConfigProvider),
resizableTextArea: null,
clearableInput: null,
formItemContext,
};
},
data() {
const value = typeof this.value === 'undefined' ? this.defaultValue : this.value;
return {
stateValue: typeof value === 'undefined' ? '' : value,
};
},
watch: {
value(val: string) {
this.stateValue = val;
},
},
mounted() {
nextTick(() => {
if (process.env.NODE_ENV === 'test') {
if (this.autofocus) {
this.focus();
}
}
const stateValue = ref(props.value === undefined ? props.defaultValue : props.value);
const resizableTextArea = ref();
const { prefixCls, size, direction } = useConfigInject('input', props);
const showCount = computed(() => {
return (props.showCount as any) === '' || props.showCount || false;
});
},
methods: {
setValue(value: string, callback?: Function) {
if (!hasProp(this, 'value')) {
this.stateValue = value;
// Max length value
const hasMaxLength = computed(() => Number(props.maxlength) > 0);
const compositing = ref(false);
const instance = getCurrentInstance();
watch(
() => props.value,
() => {
if ('value' in instance.vnode.props || {}) {
stateValue.value = props.value ?? '';
}
},
);
const focus = (option?: InputFocusOptions) => {
triggerFocus(resizableTextArea.value?.textArea, option);
};
const blur = () => {
resizableTextArea.value?.textArea?.blur();
};
const setValue = (value: string | number, callback?: Function) => {
if (stateValue.value === value) {
return;
}
if (props.value === undefined) {
stateValue.value = value;
} else {
(this as any).$forceUpdate();
resizableTextArea.value?.instance.update?.();
}
nextTick(() => {
callback && callback();
});
},
handleKeyDown(e: KeyboardEvent) {
};
const handleKeyDown = (e: KeyboardEvent) => {
if (e.keyCode === 13) {
this.$emit('pressEnter', e);
emit('pressEnter', e);
}
this.$emit('keydown', e);
},
triggerChange(e: Event) {
this.$emit('update:value', (e.target as any).value);
this.$emit('change', e);
this.$emit('input', e);
this.formItemContext.onFieldChange();
},
handleChange(e: Event) {
const { value, composing, isComposing } = e.target as any;
if (((isComposing || composing) && this.lazy) || this.stateValue === value) return;
emit('keydown', e);
};
this.setValue((e.target as HTMLTextAreaElement).value, () => {
this.resizableTextArea?.resizeTextarea();
const onBlur: FocusEventHandler = e => {
const { onBlur } = props;
onBlur?.(e);
formItemContext.onFieldBlur();
};
const triggerChange = (e: Event) => {
emit('update:value', (e.target as HTMLInputElement).value);
emit('change', e);
emit('input', e);
formItemContext.onFieldChange();
};
const handleReset = (e: MouseEvent) => {
resolveOnChange(resizableTextArea.value.textArea, e, triggerChange);
setValue('', () => {
focus();
});
resolveOnChange(this.resizableTextArea.textArea, e, this.triggerChange);
},
};
focus() {
this.resizableTextArea.textArea.focus();
},
blur() {
this.resizableTextArea.textArea.blur();
},
saveTextArea(resizableTextArea: any) {
this.resizableTextArea = resizableTextArea;
},
saveClearableInput(clearableInput: HTMLTextAreaElement) {
this.clearableInput = clearableInput;
},
handleReset(e: Event) {
this.setValue('', () => {
this.resizableTextArea.renderTextArea();
this.focus();
});
resolveOnChange(this.resizableTextArea.textArea, e, this.triggerChange);
},
handleBlur(e: Event) {
this.$emit('blur', e);
this.formItemContext.onFieldBlur();
},
renderTextArea(prefixCls: string) {
const props = getOptionProps(this);
const { style, class: customClass } = this.$attrs;
const handleChange = (e: Event) => {
const { value, composing } = e.target as any;
compositing.value = (e as any).isComposing || composing;
if ((compositing.value && props.lazy) || stateValue.value === value) return;
let triggerValue = (e.currentTarget as any).value;
if (hasMaxLength.value) {
triggerValue = fixEmojiLength(triggerValue, props.maxlength!);
}
resolveOnChange(e.currentTarget as any, e, triggerChange, triggerValue);
setValue(triggerValue);
};
const renderTextArea = () => {
const { style, class: customClass } = attrs;
const { bordered = true } = props;
const resizeProps = {
...props,
...this.$attrs,
style: !props.showCount && style,
class: !props.showCount && customClass,
...omit(props, ['allowClear']),
...attrs,
style: showCount.value && style,
class: {
[`${prefixCls.value}-borderless`]: !bordered,
[`${customClass}`]: customClass && !showCount.value,
[`${prefixCls.value}-sm`]: size.value === 'small',
[`${prefixCls.value}-lg`]: size.value === 'large',
},
showCount: null,
prefixCls,
onInput: this.handleChange,
onBlur: this.handleBlur,
onChange: this.handleChange,
onKeydown: this.handleKeyDown,
prefixCls: prefixCls.value,
onInput: handleChange,
onChange: handleChange,
onBlur,
onKeydown: handleKeyDown,
};
if (this.valueModifiers?.lazy) {
if (props.valueModifiers?.lazy) {
delete resizeProps.onInput;
}
return (
<ResizableTextArea
{...resizeProps}
id={resizeProps.id ?? this.formItemContext.id.value}
ref={this.saveTextArea}
id={resizeProps.id ?? formItemContext.id.value}
ref={resizableTextArea}
maxlength={props.maxlength}
/>
);
},
},
render() {
const { stateValue, prefixCls: customizePrefixCls, maxlength, showCount } = this;
const { style, class: customClass } = this.$attrs;
const getPrefixCls = this.configProvider.getPrefixCls;
const prefixCls = getPrefixCls('input', customizePrefixCls);
let value = fixControlledValue(stateValue) as string;
// Max length value
const hasMaxlength = Number(maxlength) > 0;
value = hasMaxlength ? value.slice(0, maxlength) : value;
const props: any = {
...getOptionProps(this),
...this.$attrs,
prefixCls,
inputType: 'text',
element: this.renderTextArea(prefixCls),
handleReset: this.handleReset,
};
let textareaNode = (
<ClearableLabeledInput {...props} value={value} ref={this.saveClearableInput} />
);
onMounted(() => {
if (process.env.NODE_ENV === 'test') {
if (props.autofocus) {
focus();
}
}
});
expose({
focus,
blur,
resizableTextArea,
});
const mergedValue = ref('');
watchEffect(() => {
let val = fixControlledValue(stateValue.value) as string;
if (
!compositing.value &&
hasMaxLength.value &&
(props.value === null || props.value === undefined)
) {
// fix #27612 value '😂'.length === 2 emoji
val = fixEmojiLength(val, props.maxlength);
}
mergedValue.value = val;
});
return () => {
const { maxlength, bordered = true } = props;
const { style, class: customClass } = attrs;
if (showCount) {
const valueLength = [...value].length;
const dataCount = `${valueLength}${hasMaxlength ? ` / ${maxlength}` : ''}`;
textareaNode = (
<div
class={classNames(
`${prefixCls}-textarea`,
`${prefixCls}-textarea-show-count`,
customClass,
)}
style={style}
data-count={dataCount}
>
{textareaNode}
</div>
const inputProps: any = {
...props,
...attrs,
prefixCls: prefixCls.value,
inputType: 'text',
handleReset,
direction: direction.value,
bordered,
style: showCount.value ? undefined : style,
};
let textareaNode = (
<ClearableLabeledInput
{...inputProps}
value={mergedValue.value}
v-slots={{ element: renderTextArea }}
/>
);
}
return textareaNode;
if (showCount.value) {
const valueLength = [...mergedValue.value].length;
let dataCount = '';
if (typeof showCount.value === 'object') {
dataCount = showCount.value.formatter({ count: valueLength, maxlength });
} else {
dataCount = `${valueLength}${hasMaxLength.value ? ` / ${maxlength}` : ''}`;
}
textareaNode = (
<div
class={classNames(
`${prefixCls.value}-textarea`,
{
[`${prefixCls.value}-textarea-rtl`]: direction.value === 'rtl',
},
`${prefixCls.value}-textarea-show-count`,
customClass,
)}
style={style}
data-count={dataCount}
>
{textareaNode}
</div>
);
}
return textareaNode;
};
},
});

View File

@ -1,19 +1,21 @@
// Thanks to https://github.com/andreypopp/react-textarea-autosize/
import type { CSSProperties } from 'vue';
/**
* calculateNodeHeight(uiTextNode, useCache = false)
*/
const HIDDEN_TEXTAREA_STYLE = `
min-height:0 !important;
max-height:none !important;
height:0 !important;
visibility:hidden !important;
overflow:hidden !important;
position:absolute !important;
z-index:-1000 !important;
top:0 !important;
right:0 !important
min-height:0 !important;
max-height:none !important;
height:0 !important;
visibility:hidden !important;
overflow:hidden !important;
position:absolute !important;
z-index:-1000 !important;
top:0 !important;
right:0 !important
`;
const SIZING_STYLE = [
@ -33,6 +35,7 @@ const SIZING_STYLE = [
'padding-right',
'border-width',
'box-sizing',
'word-break',
];
export interface NodeType {
@ -90,9 +93,11 @@ export default function calculateNodeHeight(
useCache = false,
minRows: number | null = null,
maxRows: number | null = null,
) {
): CSSProperties {
if (!hiddenTextarea) {
hiddenTextarea = document.createElement('textarea');
hiddenTextarea.setAttribute('tab-index', '-1');
hiddenTextarea.setAttribute('aria-hidden', 'true');
document.body.appendChild(hiddenTextarea);
}
@ -120,7 +125,7 @@ export default function calculateNodeHeight(
let minHeight = Number.MIN_SAFE_INTEGER;
let maxHeight = Number.MAX_SAFE_INTEGER;
let height = hiddenTextarea.scrollHeight;
let overflowY: string;
let overflowY: any;
if (boxSizing === 'border-box') {
// border-box: add border, since height = content + padding + border

View File

@ -1,7 +1,7 @@
import type { ExtractPropTypes, PropType } from 'vue';
import PropTypes from '../_util/vue-types';
import type { SizeType } from '../config-provider';
import { controlDefaultValue } from '../_util/util';
import omit from '../_util/omit';
export const inputDefaultValue = Symbol() as unknown as string;
const inputProps = {
id: PropTypes.string,
@ -10,7 +10,7 @@ const inputProps = {
defaultValue: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
value: {
type: [String, Number, Symbol] as PropType<string | number>,
default: controlDefaultValue,
default: undefined,
},
placeholder: {
type: [String, Number] as PropType<string | number>,
@ -57,6 +57,7 @@ const inputProps = {
maxlength: PropTypes.number,
loading: PropTypes.looseBool,
bordered: PropTypes.looseBool,
htmlSize: Number,
onPressEnter: PropTypes.func,
onKeydown: PropTypes.func,
onKeyup: PropTypes.func,
@ -69,3 +70,25 @@ const inputProps = {
};
export default inputProps;
export type InputProps = Partial<ExtractPropTypes<typeof inputProps>>;
export interface AutoSizeType {
minRows?: number;
maxRows?: number;
}
interface ShowCountProps {
formatter: (args: { count: number; maxlength?: number }) => string;
}
const textAreaProps = {
...omit(inputProps, ['prefix', 'addonBefore', 'addonAfter', 'suffix']),
autosize: { type: [Boolean, Object] as PropType<AutoSizeType>, default: undefined },
autoSize: { type: [Boolean, Object] as PropType<AutoSizeType>, default: undefined },
showCount: { type: [Boolean, Object] as PropType<ShowCountProps> },
onResize: { type: Function as PropType<(size: { width: number; height: number }) => void> },
onCompositionstart: PropTypes.func,
onCompositionend: PropTypes.func,
valueModifiers: Object,
};
export { textAreaProps };
export type TextAreaProps = Partial<ExtractPropTypes<typeof textAreaProps>>;

View File

@ -0,0 +1,20 @@
// Fix Input component height issue in IE11
@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) {
.@{ant-prefix}-input {
height: @input-height-base;
&-lg {
height: @input-height-lg;
}
&-sm {
height: @input-height-sm;
}
&-affix-wrapper {
> input.@{ant-prefix}-input {
height: auto;
}
}
}
}

View File

@ -1,5 +1,6 @@
@import './index';
@import './mixin';
@import (reference) '../../style/themes/index';
@input-prefix-cls: ~'@{ant-prefix}-input';
@input-affix-margin: 4px;
@ -8,6 +9,19 @@
.input();
display: inline-flex;
&:not(&-disabled):hover {
.hover();
z-index: 1;
.@{ant-prefix}-input-search-with-button & {
z-index: 0;
}
}
&-focused,
&:focus {
z-index: 1;
}
&-disabled {
.@{ant-prefix}-input[disabled] {
background: transparent;
@ -20,7 +34,7 @@
outline: none;
&:focus {
box-shadow: none;
box-shadow: none !important;
}
}

View File

@ -0,0 +1,43 @@
@import (reference) '../../style/themes/index';
@input-prefix-cls: ~'@{ant-prefix}-input';
// ========================= Input =========================
.@{iconfont-css-prefix}.@{ant-prefix}-input-clear-icon {
margin: 0;
color: @disabled-color;
font-size: @font-size-sm;
vertical-align: -1px;
// https://github.com/ant-design/ant-design/pull/18151
// https://codesandbox.io/s/wizardly-sun-u10br
cursor: pointer;
transition: color 0.3s;
&:hover {
color: @text-color-secondary;
}
&:active {
color: @text-color;
}
&-hidden {
visibility: hidden;
}
&-has-suffix {
margin: 0 @input-affix-margin;
}
}
// ======================= TextArea ========================
.@{ant-prefix}-input-affix-wrapper-textarea-with-clear-btn {
padding: 0 !important;
border: 0 !important;
.@{ant-prefix}-input-clear-icon {
position: absolute;
top: 8px;
right: 8px;
z-index: 1;
}
}

View File

@ -1,77 +1,68 @@
@import '../../style/themes/index';
@import '../../style/mixins/index';
@import './affix';
@import './mixin';
@import './affix';
@import './allow-clear';
@input-prefix-cls: ~'@{ant-prefix}-input';
// Input styles
.@{ant-prefix}-input {
.@{input-prefix-cls} {
.reset-component();
.input();
}
//== Style for input-group: input with label, with button or dropdown...
.@{ant-prefix}-input-group {
.reset-component();
.input-group(~'@{ant-prefix}-input');
&-wrapper {
display: inline-block;
width: 100%;
text-align: start;
vertical-align: top; // https://github.com/ant-design/ant-design/issues/6403
//== Style for input-group: input with label, with button or dropdown...
&-group {
.reset-component();
.input-group(~'@{input-prefix-cls}');
&-wrapper {
display: inline-block;
width: 100%;
text-align: start;
vertical-align: top; // https://github.com/ant-design/ant-design/issues/6403
}
}
}
// Input with affix: prefix or suffix
.@{ant-prefix}-input-affix-wrapper {
.reset-component();
.input-affix-wrapper(~'@{ant-prefix}-input');
}
.@{ant-prefix}-input-password-icon {
color: @text-color-secondary;
cursor: pointer;
transition: all 0.3s;
&:hover {
color: @input-icon-hover-color;
}
}
.@{ant-prefix}-input-clear-icon {
.clear-icon();
vertical-align: 0;
}
.@{ant-prefix}-input-clear-icon-hidden {
visibility: hidden;
}
.@{ant-prefix}-input-textarea-clear-icon-hidden {
visibility: hidden;
}
.@{ant-prefix}-input-affix-wrapper-textarea-with-clear-btn {
padding: 0 !important;
.@{ant-prefix}-input {
padding: @input-padding-vertical-base @input-padding-horizontal-base;
}
}
.@{ant-prefix}-input-textarea-clear-icon {
.clear-icon();
position: absolute;
top: 0;
right: 0;
margin: 8px 8px 0 0;
}
.@{ant-prefix}-input-textarea {
&-show-count::after {
display: block;
&-password-icon {
color: @text-color-secondary;
text-align: right;
content: attr(data-count);
cursor: pointer;
transition: all 0.3s;
&:hover {
color: @input-icon-hover-color;
}
}
&[type='color'] {
height: @input-height-base;
&.@{input-prefix-cls}-lg {
height: @input-height-lg;
}
&.@{input-prefix-cls}-sm {
height: @input-height-sm;
padding-top: 3px;
padding-bottom: 3px;
}
}
&-textarea-show-count {
// https://github.com/ant-design/ant-design/issues/33049
> .@{input-prefix-cls} {
height: 100%;
}
&::after {
float: right;
color: @text-color-secondary;
white-space: nowrap;
content: attr(data-count);
pointer-events: none;
}
}
}
@import './search-input';
@import './rtl';
@import './IE11';

View File

@ -1,8 +1,6 @@
@import '../../style/themes/index';
@import '../../style/mixins/index';
@input-affix-margin: 4px;
@input-affix-width: 19px;
@input-affix-with-clear-btn-width: 38px;
// size mixins for input
@ -16,23 +14,37 @@
}
// input status
// == when focus or actived
.active(@color: @outline-color) {
border-color: ~`colorPalette('@{color}', 5) `;
// == when focus or active
.active(@borderColor: @primary-color; @hoverBorderColor: @primary-color-hover; @outlineColor: @primary-color-outline) {
& when (@theme = dark) {
border-color: @borderColor;
}
& when (not (@theme = dark) and not (@theme = variable)) {
border-color: @hoverBorderColor;
}
& when not (@theme = variable) {
box-shadow: @input-outline-offset @outline-blur-size @outline-width
fade(@borderColor, @outline-fade);
}
& when (@theme = variable) {
border-color: @hoverBorderColor;
box-shadow: @input-outline-offset @outline-blur-size @outline-width @outlineColor;
}
border-right-width: @border-width-base !important;
outline: 0;
box-shadow: @input-outline-offset @outline-blur-size @outline-width fade(@color, 20%);
}
// == when hoverd
// == when hover
.hover(@color: @input-hover-border-color) {
border-color: @color;
border-right-width: @border-width-base !important;
}
.disabled() {
color: @disabled-color;
color: @input-disabled-color;
background-color: @input-disabled-bg;
border-color: @input-border-color;
box-shadow: none;
cursor: not-allowed;
opacity: 1;
@ -46,6 +58,7 @@
position: relative;
display: inline-block;
width: 100%;
min-width: 0;
padding: @input-padding-vertical-base @input-padding-horizontal-base;
color: @input-color;
font-size: @font-size-base;
@ -61,7 +74,8 @@
.hover();
}
&:focus {
&:focus,
&-focused {
.active();
}
@ -73,6 +87,19 @@
.disabled();
}
&-borderless {
&,
&:hover,
&:focus,
&-focused,
&-disabled,
&[disabled] {
background-color: transparent;
border: none;
box-shadow: none;
}
}
// Reset height for `textarea`s
textarea& {
max-width: 100%; // prevent textearea resize from coming out of its container
@ -151,6 +178,9 @@
&:hover {
z-index: 1;
border-right-width: 1px;
.@{ant-prefix}-input-search-with-button & {
z-index: 0;
}
}
}
@ -185,15 +215,15 @@
}
}
// Expand addon icon click area
// https://github.com/ant-design/ant-design/issues/3714
> i:only-child::after {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
content: '';
// https://github.com/ant-design/ant-design/issues/31333
.@{ant-prefix}-cascader-picker {
margin: -9px (-@control-padding-horizontal);
background-color: transparent;
.@{ant-prefix}-cascader-input {
text-align: left;
border: 0;
box-shadow: none;
}
}
}
@ -262,6 +292,23 @@
height: @input-height-sm;
}
.@{inputClass}-affix-wrapper {
&:not(:last-child) {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
.@{ant-prefix}-input-search & {
border-top-left-radius: @border-radius-base;
border-bottom-left-radius: @border-radius-base;
}
}
&:not(:first-child),
.@{ant-prefix}-input-search &:not(:first-child) {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
}
&&-compact {
display: block;
.clearfix();
@ -293,6 +340,10 @@
display: inline-flex;
}
& > .@{ant-prefix}-picker-range {
display: inline-flex;
}
& > *:not(:last-child) {
margin-right: -@border-width-base;
border-right-width: @border-width-base;
@ -305,11 +356,8 @@
// reset border for Select, DatePicker, AutoComplete, Cascader, Mention, TimePicker, Input
& > .@{ant-prefix}-select > .@{ant-prefix}-select-selector,
& > .@{ant-prefix}-calendar-picker .@{ant-prefix}-input,
& > .@{ant-prefix}-select-auto-complete .@{ant-prefix}-input,
& > .@{ant-prefix}-cascader-picker .@{ant-prefix}-input,
& > .@{ant-prefix}-mention-wrapper .@{ant-prefix}-mention-editor,
& > .@{ant-prefix}-time-picker .@{ant-prefix}-time-picker-input,
& > .@{ant-prefix}-input-group-wrapper .@{ant-prefix}-input {
border-right-width: @border-width-base;
border-radius: 0;
@ -327,25 +375,23 @@
z-index: 1;
}
// update z-index for arrow icon
& > .@{ant-prefix}-select > .@{ant-prefix}-select-arrow {
z-index: 1; // https://github.com/ant-design/ant-design/issues/20371
}
& > *:first-child,
& > .@{ant-prefix}-select:first-child > .@{ant-prefix}-select-selector,
& > .@{ant-prefix}-calendar-picker:first-child .@{ant-prefix}-input,
& > .@{ant-prefix}-select-auto-complete:first-child .@{ant-prefix}-input,
& > .@{ant-prefix}-cascader-picker:first-child .@{ant-prefix}-input,
& > .@{ant-prefix}-mention-wrapper:first-child .@{ant-prefix}-mention-editor,
& > .@{ant-prefix}-time-picker:first-child .@{ant-prefix}-time-picker-input {
& > .@{ant-prefix}-cascader-picker:first-child .@{ant-prefix}-input {
border-top-left-radius: @border-radius-base;
border-bottom-left-radius: @border-radius-base;
}
& > *:last-child,
& > .@{ant-prefix}-select:last-child > .@{ant-prefix}-select-selector,
& > .@{ant-prefix}-calendar-picker:last-child .@{ant-prefix}-input,
& > .@{ant-prefix}-select-auto-complete:last-child .@{ant-prefix}-input,
& > .@{ant-prefix}-cascader-picker:last-child .@{ant-prefix}-input,
& > .@{ant-prefix}-cascader-picker-focused:last-child .@{ant-prefix}-input,
& > .@{ant-prefix}-mention-wrapper:last-child .@{ant-prefix}-mention-editor,
& > .@{ant-prefix}-time-picker:last-child .@{ant-prefix}-time-picker-input {
& > .@{ant-prefix}-cascader-picker-focused:last-child .@{ant-prefix}-input {
border-right-width: @border-width-base;
border-top-right-radius: @border-radius-base;
border-bottom-right-radius: @border-radius-base;
@ -355,107 +401,24 @@
& > .@{ant-prefix}-select-auto-complete .@{ant-prefix}-input {
vertical-align: top;
}
}
}
.input-affix-wrapper(@inputClass) {
position: relative;
display: inline-flex;
border: @border-width-base @border-style-base @input-border-color;
border-radius: @border-radius-base;
padding: @input-padding-vertical-base @input-padding-horizontal-base;
width: 100%;
text-align: start;
background-color: @input-bg;
background-image: none;
color: @input-color;
font-size: @font-size-base;
line-height: @line-height-base;
.@{ant-prefix}-input-group-wrapper + .@{ant-prefix}-input-group-wrapper {
margin-left: -1px;
.@{ant-prefix}-input-affix-wrapper {
border-radius: 0;
}
}
&:hover {
.hover();
}
.@{ant-prefix}-input-group-wrapper:not(:last-child) {
&.@{ant-prefix}-input-search > .@{ant-prefix}-input-group {
& > .@{ant-prefix}-input-group-addon > .@{ant-prefix}-input-search-button {
border-radius: 0;
}
&-disabled {
.disabled();
}
&-focused {
.active();
}
// Size
&-lg {
.input-lg();
}
&-sm {
.input-sm();
}
.@{inputClass} {
position: relative;
text-align: inherit;
border: none;
padding: 0;
&:focus {
border: none;
outline: none;
box-shadow: none;
& > .@{ant-prefix}-input {
border-radius: @border-radius-base 0 0 @border-radius-base;
}
}
}
}
// Should not break align of icon & text
// https://github.com/ant-design/ant-design/issues/18087
// https://github.com/ant-design/ant-design/issues/17414
// https://github.com/ant-design/ant-design/pull/17684
// https://codesandbox.io/embed/pensive-paper-di2wk
// https://codesandbox.io/embed/nifty-benz-gb7ml
.@{inputClass}-prefix,
.@{inputClass}-suffix {
display: flex;
align-items: center;
color: @input-color;
white-space: nowrap;
:not(.anticon) {
line-height: @line-height-base;
}
}
.@{inputClass}-disabled ~ .@{inputClass}-suffix {
.anticon {
color: @disabled-color;
cursor: not-allowed;
}
}
.@{inputClass}-prefix {
margin-right: @input-affix-margin;
}
.@{inputClass}-suffix {
margin-left: @input-affix-margin;
}
}
.clear-icon() {
color: @disabled-color;
font-size: @font-size-sm;
// https://github.com/ant-design/ant-design/pull/18151
// https://codesandbox.io/s/wizardly-sun-u10br
cursor: pointer;
transition: color 0.3s;
margin: 0 @input-affix-margin;
&:hover {
color: @text-color-secondary;
}
&:active {
color: @text-color;
}
+ i {
margin-left: 6px;
}
}

View File

@ -0,0 +1,206 @@
@import '../../style/themes/index';
@import '../../style/mixins/index';
//== Style for input-group: input with label, with button or dropdown...
.@{ant-prefix}-input-group {
&-wrapper {
&-rtl {
direction: rtl;
}
}
&-rtl {
direction: rtl;
}
}
// affix
@input-affix-margin: 4px;
.@{ant-prefix}-input {
&-affix-wrapper&-affix-wrapper-rtl {
> input.@{ant-prefix}-input {
border: none;
outline: none;
}
}
&-affix-wrapper-rtl {
.@{ant-prefix}-input-prefix {
margin: 0 0 0 @input-affix-margin;
}
.@{ant-prefix}-input-suffix {
margin: 0 @input-affix-margin 0 0;
}
}
&-textarea {
&-rtl {
direction: rtl;
}
&-rtl&-show-count::after {
text-align: left;
}
}
}
// allow-clear
.@{ant-prefix}-input-clear-icon {
&-has-suffix {
.@{ant-prefix}-input-affix-wrapper-rtl & {
margin-right: 0;
margin-left: @input-affix-margin;
}
}
.@{ant-prefix}-input-affix-wrapper-rtl & {
right: auto;
left: 8px;
}
}
// mixin
@input-rtl-cls: ~'@{ant-prefix}-input-rtl';
.active() {
.@{input-rtl-cls} & {
border-right-width: 0;
border-left-width: @border-width-base !important;
}
}
.hover() {
.@{input-rtl-cls} & {
border-right-width: 0;
border-left-width: @border-width-base !important;
}
}
.input() {
&-rtl {
direction: rtl;
}
}
// label input
.input-group(@inputClass) {
> .@{inputClass}-rtl:first-child,
&-rtl &-addon:first-child {
border-radius: 0 @border-radius-base @border-radius-base 0;
}
&-addon:first-child {
.@{inputClass}-group-rtl & {
border-right: @border-width-base @border-style-base @input-border-color;
border-left: 0;
}
}
&-addon:last-child {
.@{inputClass}-group-rtl & {
border-right: 0;
border-left: @border-width-base @border-style-base @input-border-color;
}
}
> .@{inputClass}:last-child,
&-addon:last-child {
.@{inputClass}-group-rtl& {
border-radius: @border-radius-base 0 0 @border-radius-base;
}
}
.@{inputClass}-affix-wrapper {
&:not(:first-child) {
.@{inputClass}-group-rtl& {
border-radius: @border-radius-base 0 0 @border-radius-base;
}
}
&:not(:last-child) {
.@{inputClass}-group-rtl& {
border-radius: 0 @border-radius-base @border-radius-base 0;
}
}
}
&&-compact {
& > *:not(:last-child) {
.@{inputClass}-group-rtl& {
margin-right: 0;
margin-left: -@border-width-base;
border-left-width: @border-width-base;
}
}
& > *:first-child,
& > .@{ant-prefix}-select:first-child > .@{ant-prefix}-select-selector,
& > .@{ant-prefix}-select-auto-complete:first-child .@{ant-prefix}-input,
& > .@{ant-prefix}-cascader-picker:first-child .@{ant-prefix}-input {
.@{inputClass}-group-rtl& {
border-radius: 0 @border-radius-base @border-radius-base 0;
}
}
& > *:last-child,
& > .@{ant-prefix}-select:last-child > .@{ant-prefix}-select-selector,
& > .@{ant-prefix}-select-auto-complete:last-child .@{ant-prefix}-input,
& > .@{ant-prefix}-cascader-picker:last-child .@{ant-prefix}-input,
& > .@{ant-prefix}-cascader-picker-focused:last-child .@{ant-prefix}-input {
.@{inputClass}-group-rtl& {
border-left-width: @border-width-base;
border-radius: @border-radius-base 0 0 @border-radius-base;
}
}
.@{ant-prefix}-input-group-wrapper-rtl + .@{ant-prefix}-input-group-wrapper-rtl {
margin-right: -1px;
margin-left: 0;
}
.@{ant-prefix}-input-group-wrapper-rtl:not(:last-child) {
&.@{ant-prefix}-input-search > .@{ant-prefix}-input-group {
& > .@{ant-prefix}-input {
border-radius: 0 @border-radius-base @border-radius-base 0;
}
}
}
}
}
// search-input
@search-prefix: ~'@{ant-prefix}-input-search';
@search-rtl-cls: ~'@{search-prefix}-rtl';
.@{search-prefix}-rtl {
direction: rtl;
.@{ant-prefix}-input {
&:hover,
&:focus {
+ .@{ant-prefix}-input-group-addon .@{search-prefix}-button:not(.@{ant-prefix}-btn-primary) {
border-right-color: @input-hover-border-color;
border-left-color: @border-color-base;
}
}
}
> .@{ant-prefix}-input-group {
> .@{ant-prefix}-input-affix-wrapper {
&:hover,
&-focused {
border-right-color: @input-hover-border-color;
}
}
> .@{ant-prefix}-input-group-addon {
right: -1px;
left: auto;
.@{search-prefix}-button {
border-radius: @border-radius-base 0 0 @border-radius-base;
}
}
}
}

View File

@ -11,6 +11,9 @@
// -------- Colors -----------
@primary-color: @blue-6;
@primary-color-hover: color(~`colorPalette('@{primary-color}', 5) `);
@primary-color-active: color(~`colorPalette('@{primary-color}', 7) `);
@primary-color-outline: fade(@primary-color, @outline-fade);
@info-color: @primary-color;
@success-color: @green-6;
@processing-color: @blue-6;
@ -141,6 +144,7 @@
@outline-blur-size: 0;
@outline-width: 2px;
@outline-color: @primary-color;
@outline-fade: 20%;
@background-color-light: hsv(0, 0, 98%); // background of header and selected item
@background-color-base: hsv(0, 0, 96%); // Default grey background color
@ -377,16 +381,26 @@
// Input
// ---
@input-height-base: 32px;
@input-height-lg: 40px;
@input-height-sm: 24px;
@input-height-base: @height-base;
@input-height-lg: @height-lg;
@input-height-sm: @height-sm;
@input-padding-horizontal: @control-padding-horizontal - 1px;
@input-padding-horizontal-base: @input-padding-horizontal;
@input-padding-horizontal-sm: @control-padding-horizontal-sm - 1px;
@input-padding-horizontal-lg: @input-padding-horizontal;
@input-padding-vertical-base: 4px;
@input-padding-vertical-sm: 0px;
@input-padding-vertical-lg: 6.5px;
@input-padding-vertical-base: max(
(round(((@input-height-base - @font-size-base * @line-height-base) / 2) * 10) / 10) -
@border-width-base,
3px
);
@input-padding-vertical-sm: max(
(round(((@input-height-sm - @font-size-base * @line-height-base) / 2) * 10) / 10) -
@border-width-base,
0
);
@input-padding-vertical-lg: (
ceil(((@input-height-lg - @font-size-lg * @line-height-base) / 2) * 10) / 10
) - @border-width-base;
@input-placeholder-color: hsv(0, 0, 75%);
@input-color: @text-color;
@input-icon-color: @input-color;
@ -402,6 +416,7 @@
@input-disabled-bg: @disabled-bg;
@input-outline-offset: 0 0;
@input-icon-hover-color: fade(@black, 85%);
@input-disabled-color: @disabled-color;
// Mentions
// ---