refactor: input

This commit is contained in:
tangjinzhou 2021-12-14 23:50:22 +08:00
parent cd6c6ff048
commit 7ec6831508
11 changed files with 859 additions and 542 deletions

View File

@ -24,6 +24,8 @@ export default (
virtual: ComputedRef<boolean>;
dropdownMatchSelectWidth: ComputedRef<boolean | number>;
getPopupContainer: ComputedRef<ConfigProviderProps['getPopupContainer']>;
getPrefixCls: ConfigProviderProps['getPrefixCls'];
autocomplete: ComputedRef<string>;
} => {
const configProvider = inject<UnwrapRef<ConfigProviderProps>>(
'configProvider',
@ -48,6 +50,7 @@ export default (
() => props.dropdownMatchSelectWidth ?? configProvider.dropdownMatchSelectWidth,
);
const size = computed(() => props.size || configProvider.componentSize);
const autocomplete = computed(() => props.autocomplete || configProvider.input?.autocomplete);
return {
configProvider,
prefixCls,
@ -63,5 +66,7 @@ export default (
virtual,
dropdownMatchSelectWidth,
rootPrefixCls,
getPrefixCls: configProvider.getPrefixCls,
autocomplete,
};
};

View File

@ -1,6 +1,6 @@
import type { VueNode } from './type';
export const isFunction = val => typeof val === 'function';
export const controlDefaultValue = Symbol('controlDefaultValue') as any;
export const isArray = Array.isArray;
export const isString = val => typeof val === 'string';
export const isSymbol = val => typeof val === 'symbol';

View File

@ -35,7 +35,7 @@ export default defineComponent({
__ANT_BUTTON: true,
props,
slots: ['icon'],
emits: ['click'],
emits: ['click', 'mousedown'],
setup(props, { slots, attrs, emit }) {
const { prefixCls, autoInsertSpaceInButton, direction } = useConfigInject('btn', props);

View File

@ -160,6 +160,9 @@ export const configProviderProps = {
csp: {
type: Object as PropType<CSPConfig>,
},
input: {
type: Object as PropType<{ autocomplete: string }>,
},
autoInsertSpaceInButton: PropTypes.looseBool,
locale: {
type: Object as PropType<Locale>,

View File

@ -3,22 +3,23 @@ import CloseCircleFilled from '@ant-design/icons-vue/CloseCircleFilled';
import { getInputClassName } from './Input';
import PropTypes from '../_util/vue-types';
import { cloneElement } from '../_util/vnode';
import { getComponent } from '../_util/props-util';
import type { VNode } from 'vue';
import { defineComponent } from 'vue';
import type { PropType, VNode } from 'vue';
import { ref, defineComponent } from 'vue';
import { tuple } from '../_util/type';
import type { Direction, SizeType } from '../config-provider';
import type { MouseEventHandler } from '../_util/EventInterface';
export function hasPrefixSuffix(instance: any) {
return !!(
getComponent(instance, 'prefix') ||
getComponent(instance, 'suffix') ||
instance.$props.allowClear
);
export function hasPrefixSuffix(propsAndSlots: any) {
return !!(propsAndSlots.prefix || propsAndSlots.suffix || propsAndSlots.allowClear);
}
function hasAddon(propsAndSlots: any) {
return !!(propsAndSlots.addonBefore || propsAndSlots.addonAfter);
}
const ClearableInputType = ['text', 'input'];
const ClearableLabeledInput = defineComponent({
export default defineComponent({
name: 'ClearableLabeledInput',
inheritAttrs: false,
props: {
@ -27,93 +28,125 @@ const ClearableLabeledInput = defineComponent({
value: PropTypes.any,
defaultValue: PropTypes.any,
allowClear: PropTypes.looseBool,
element: PropTypes.VNodeChild,
element: PropTypes.any,
handleReset: PropTypes.func,
disabled: PropTypes.looseBool,
size: PropTypes.oneOf(tuple('small', 'large', 'default')),
suffix: PropTypes.VNodeChild,
prefix: PropTypes.VNodeChild,
addonBefore: PropTypes.VNodeChild,
addonAfter: PropTypes.VNodeChild,
direction: { type: String as PropType<Direction> },
size: { type: String as PropType<SizeType> },
suffix: PropTypes.any,
prefix: PropTypes.any,
addonBefore: PropTypes.any,
addonAfter: PropTypes.any,
readonly: PropTypes.looseBool,
isFocused: PropTypes.looseBool,
focused: PropTypes.looseBool,
bordered: PropTypes.looseBool,
triggerFocus: { type: Function as PropType<() => void> },
},
methods: {
renderClearIcon(prefixCls: string) {
const { allowClear, value, disabled, readonly, inputType, handleReset } = this.$props;
setup(props, { slots, attrs }) {
const containerRef = ref();
const onInputMouseUp: MouseEventHandler = e => {
if (containerRef.value?.contains(e.target as Element)) {
const { triggerFocus } = props;
triggerFocus?.();
}
};
const renderClearIcon = (prefixCls: string) => {
const { allowClear, value, disabled, readonly, handleReset } = props;
if (!allowClear) {
return null;
}
const showClearIcon =
!disabled && !readonly && value !== undefined && value !== null && value !== '';
const className =
inputType === ClearableInputType[0]
? `${prefixCls}-textarea-clear-icon`
: `${prefixCls}-clear-icon`;
const needClear = !disabled && !readonly && value;
const className = `${prefixCls}-clear-icon`;
return (
<CloseCircleFilled
onClick={handleReset}
class={classNames(className, {
[`${className}-hidden`]: !showClearIcon,
})}
class={classNames(
{
[`${className}-hidden`]: !needClear,
},
className,
)}
role="button"
/>
);
},
};
renderSuffix(prefixCls: string) {
const { suffix, allowClear } = this.$props;
const renderSuffix = (prefixCls: string) => {
const { suffix = slots.suffix?.(), allowClear } = props;
if (suffix || allowClear) {
return (
<span class={`${prefixCls}-suffix`}>
{this.renderClearIcon(prefixCls)}
{renderClearIcon(prefixCls)}
{suffix}
</span>
);
}
return null;
},
};
renderLabeledIcon(prefixCls: string, element: VNode): VNode {
const props = this.$props;
const { style } = this.$attrs;
const suffix = this.renderSuffix(prefixCls);
if (!hasPrefixSuffix(this)) {
const renderLabeledIcon = (prefixCls: string, element: VNode) => {
const {
focused,
value,
prefix = slots.prefix?.(),
size,
suffix = slots.suffix?.(),
disabled,
allowClear,
direction,
readonly,
bordered,
addonAfter = slots.addonAfter,
addonBefore = slots.addonBefore,
} = props;
const suffixNode = renderSuffix(prefixCls);
if (!hasPrefixSuffix({ prefix, suffix, allowClear })) {
return cloneElement(element, {
value: props.value,
value,
});
}
const prefix = props.prefix ? (
<span class={`${prefixCls}-prefix`}>{props.prefix}</span>
) : null;
const prefixNode = prefix ? <span class={`${prefixCls}-prefix`}>{prefix}</span> : null;
const affixWrapperCls = classNames(this.$attrs?.class, `${prefixCls}-affix-wrapper`, {
[`${prefixCls}-affix-wrapper-focused`]: props.isFocused,
[`${prefixCls}-affix-wrapper-disabled`]: props.disabled,
[`${prefixCls}-affix-wrapper-sm`]: props.size === 'small',
[`${prefixCls}-affix-wrapper-lg`]: props.size === 'large',
[`${prefixCls}-affix-wrapper-input-with-clear-btn`]:
props.suffix && props.allowClear && this.$props.value,
const affixWrapperCls = classNames(`${prefixCls}-affix-wrapper`, {
[`${prefixCls}-affix-wrapper-focused`]: focused,
[`${prefixCls}-affix-wrapper-disabled`]: disabled,
[`${prefixCls}-affix-wrapper-sm`]: size === 'small',
[`${prefixCls}-affix-wrapper-lg`]: size === 'large',
[`${prefixCls}-affix-wrapper-input-with-clear-btn`]: suffix && allowClear && value,
[`${prefixCls}-affix-wrapper-rtl`]: direction === 'rtl',
[`${prefixCls}-affix-wrapper-readonly`]: readonly,
[`${prefixCls}-affix-wrapper-borderless`]: !bordered,
// className will go to addon wrapper
[`${attrs.class}`]: !hasAddon({ addonAfter, addonBefore }) && attrs.class,
});
return (
<span class={affixWrapperCls} style={style}>
{prefix}
<span
ref={containerRef}
class={affixWrapperCls}
style={attrs.style}
onMouseup={onInputMouseUp}
>
{prefixNode}
{cloneElement(element, {
style: null,
value: props.value,
class: getInputClassName(prefixCls, props.size, props.disabled),
value,
class: getInputClassName(prefixCls, bordered, size, disabled),
})}
{suffix}
{suffixNode}
</span>
) as VNode;
},
);
};
renderInputWithLabel(prefixCls: string, labeledElement: VNode) {
const { addonBefore, addonAfter, size } = this.$props;
const { style, class: className } = this.$attrs;
const renderInputWithLabel = (prefixCls: string, labeledElement: VNode) => {
const {
addonBefore = slots.addonBefore?.(),
addonAfter = slots.addonAfter?.(),
size,
direction,
} = props;
// Not wrap when there is not addons
if (!addonBefore && !addonAfter) {
if (!hasAddon({ addonBefore, addonAfter })) {
return labeledElement;
}
@ -124,19 +157,24 @@ const ClearableLabeledInput = defineComponent({
) : null;
const addonAfterNode = addonAfter ? <span class={addonClassName}>{addonAfter}</span> : null;
const mergedWrapperClassName = classNames(`${prefixCls}-wrapper`, {
[wrapperClassName]: addonBefore || addonAfter,
const mergedWrapperClassName = classNames(`${prefixCls}-wrapper`, wrapperClassName, {
[`${wrapperClassName}-rtl`]: direction === 'rtl',
});
const mergedGroupClassName = classNames(className, `${prefixCls}-group-wrapper`, {
[`${prefixCls}-group-wrapper-sm`]: size === 'small',
[`${prefixCls}-group-wrapper-lg`]: size === 'large',
});
const mergedGroupClassName = classNames(
`${prefixCls}-group-wrapper`,
{
[`${prefixCls}-group-wrapper-sm`]: size === 'small',
[`${prefixCls}-group-wrapper-lg`]: size === 'large',
[`${prefixCls}-group-wrapper-rtl`]: direction === 'rtl',
},
attrs.class,
);
// Need another wrapper for changing display:table to display:inline-block
// and put style prop in wrapper
return (
<span class={mergedGroupClassName} style={style}>
<span class={mergedGroupClassName} style={attrs.style}>
<span class={mergedWrapperClassName}>
{addonBeforeNode}
{cloneElement(labeledElement, { style: null })}
@ -144,41 +182,49 @@ const ClearableLabeledInput = defineComponent({
</span>
</span>
);
},
};
renderTextAreaWithClearIcon(prefixCls: string, element: VNode) {
const { value, allowClear } = this.$props;
const { style, class: className } = this.$attrs;
const renderTextAreaWithClearIcon = (prefixCls: string, element: VNode) => {
const {
value,
allowClear,
direction,
bordered,
addonAfter = slots.addonAfter,
addonBefore = slots.addonBefore,
} = props;
if (!allowClear) {
return cloneElement(element, { value });
return cloneElement(element, {
value,
});
}
const affixWrapperCls = classNames(
className,
`${prefixCls}-affix-wrapper`,
`${prefixCls}-affix-wrapper-textarea-with-clear-btn`,
{
[`${prefixCls}-affix-wrapper-rtl`]: direction === 'rtl',
[`${prefixCls}-affix-wrapper-borderless`]: !bordered,
// className will go to addon wrapper
[`${attrs.class}`]: !hasAddon({ addonAfter, addonBefore }) && attrs.class,
},
);
return (
<span class={affixWrapperCls} style={style}>
<span class={affixWrapperCls} style={attrs.style}>
{cloneElement(element, {
style: null,
value,
})}
{this.renderClearIcon(prefixCls)}
{renderClearIcon(prefixCls)}
</span>
);
},
};
renderClearableLabeledInput() {
const { prefixCls, inputType, element } = this.$props as any;
return () => {
const { prefixCls, inputType, element = slots.element?.() } = props;
if (inputType === ClearableInputType[0]) {
return this.renderTextAreaWithClearIcon(prefixCls, element);
return renderTextAreaWithClearIcon(prefixCls, element);
}
return this.renderInputWithLabel(prefixCls, this.renderLabeledIcon(prefixCls, element));
},
},
render() {
return this.renderClearableLabeledInput();
return renderInputWithLabel(prefixCls, renderLabeledIcon(prefixCls, element));
};
},
});
export default ClearableLabeledInput;

View File

@ -1,36 +1,46 @@
import { defineComponent, inject } from 'vue';
import type { PropType } from 'vue';
import { computed, defineComponent } from 'vue';
import PropTypes from '../_util/vue-types';
import { getSlot } from '../_util/props-util';
import { defaultConfigProvider } from '../config-provider';
import { tuple } from '../_util/type';
import type { SizeType } from '../config-provider';
import type { FocusEventHandler, MouseEventHandler } from '../_util/EventInterface';
import useConfigInject from '../_util/hooks/useConfigInject';
export default defineComponent({
name: 'AInputGroup',
props: {
prefixCls: PropTypes.string,
size: PropTypes.oneOf(tuple('small', 'large', 'default')),
size: { type: String as PropType<SizeType> },
compact: PropTypes.looseBool,
onMouseenter: { type: Function as PropType<MouseEventHandler> },
onMouseleave: { type: Function as PropType<MouseEventHandler> },
onFocus: { type: Function as PropType<FocusEventHandler> },
onBlur: { type: Function as PropType<FocusEventHandler> },
},
setup() {
return {
configProvider: inject('configProvider', defaultConfigProvider),
setup(props, { slots }) {
const { prefixCls, direction } = useConfigInject('input-group', props);
const cls = computed(() => {
const pre = prefixCls.value;
return {
[`${pre}`]: true,
[`${pre}-lg`]: props.size === 'large',
[`${pre}-sm`]: props.size === 'small',
[`${pre}-compact`]: props.compact,
[`${pre}-rtl`]: direction.value === 'rtl',
};
});
return () => {
const {} = props;
return (
<span
class={cls.value}
onMouseenter={props.onMouseEnter}
onMouseleave={props.onMouseLeave}
onFocus={props.onFocus}
onBlur={props.onBlur}
>
{slots.default?.()}
</span>
);
};
},
computed: {
classes() {
const { prefixCls: customizePrefixCls, size, compact = false, configProvider } = this as any;
const getPrefixCls = configProvider.getPrefixCls;
const prefixCls = getPrefixCls('input-group', customizePrefixCls);
return {
[`${prefixCls}`]: true,
[`${prefixCls}-lg`]: size === 'large',
[`${prefixCls}-sm`]: size === 'small',
[`${prefixCls}-compact`]: compact,
};
},
},
render() {
return <span class={this.classes}>{getSlot(this)}</span>;
},
});

View File

@ -1,13 +1,25 @@
import type { VNode } from 'vue';
import { defineComponent, inject, nextTick, withDirectives } from 'vue';
import {
getCurrentInstance,
onBeforeUnmount,
onMounted,
watch,
ref,
defineComponent,
nextTick,
withDirectives,
} from 'vue';
import antInputDirective from '../_util/antInputDirective';
import classNames from '../_util/classNames';
import type { InputProps } from './inputProps';
import inputProps from './inputProps';
import { hasProp, getComponent, getOptionProps } from '../_util/props-util';
import { defaultConfigProvider } from '../config-provider';
import type { Direction, SizeType } from '../config-provider';
import ClearableLabeledInput from './ClearableLabeledInput';
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) {
@ -16,142 +28,250 @@ export function fixControlledValue(value: string | number) {
return value;
}
export function resolveOnChange(target: HTMLInputElement, e: Event, onChange?: Function) {
if (onChange) {
const event = e as any;
if (e.type === 'click') {
// click clear icon
//event = Object.create(e);
Object.defineProperty(event, 'target', {
writable: true,
});
Object.defineProperty(event, 'currentTarget', {
writable: true,
});
event.target = target;
event.currentTarget = target;
const originalInputValue = target.value;
// change target ref value cause e.target.value should be '' when clear input
target.value = '';
onChange(event);
// reset target ref value
target.value = originalInputValue;
return;
}
onChange(event);
export function resolveOnChange(
target: HTMLInputElement,
e: Event,
onChange: Function,
targetValue?: string,
) {
if (!onChange) {
return;
}
const event: any = e;
const originalInputValue = target.value;
if (e.type === 'click') {
Object.defineProperty(event, 'target', {
writable: true,
});
Object.defineProperty(event, 'currentTarget', {
writable: true,
});
// click clear icon
//event = Object.create(e);
event.target = target;
event.currentTarget = target;
// change target ref value cause e.target.value should be '' when clear input
target.value = '';
onChange(event);
// reset target ref value
target.value = originalInputValue;
return;
}
// Trigger by composition event, this means we need force change the input value
if (targetValue !== undefined) {
Object.defineProperty(event, 'target', {
writable: true,
});
Object.defineProperty(event, 'currentTarget', {
writable: true,
});
event.target = target;
event.currentTarget = target;
target.value = targetValue;
onChange(event);
return;
}
onChange(event);
}
export function getInputClassName(prefixCls: string, size: string, disabled: boolean) {
export function getInputClassName(
prefixCls: string,
bordered: boolean,
size?: SizeType,
disabled?: boolean,
direction?: Direction,
) {
return classNames(prefixCls, {
[`${prefixCls}-sm`]: size === 'small',
[`${prefixCls}-lg`]: size === 'large',
[`${prefixCls}-disabled`]: disabled,
[`${prefixCls}-rtl`]: direction === 'rtl',
[`${prefixCls}-borderless`]: !bordered,
});
}
export interface InputFocusOptions extends FocusOptions {
cursor?: 'start' | 'end' | 'all';
}
export function triggerFocus(
element?: HTMLInputElement | HTMLTextAreaElement,
option?: InputFocusOptions,
) {
if (!element) return;
element.focus(option);
// Selection content
const { cursor } = option || {};
if (cursor) {
const len = element.value.length;
switch (cursor) {
case 'start':
element.setSelectionRange(0, 0);
break;
case 'end':
element.setSelectionRange(len, len);
break;
default:
element.setSelectionRange(0, len);
}
}
}
export default defineComponent({
name: 'AInput',
inheritAttrs: false,
props: {
...inputProps,
},
setup() {
setup(props, { slots, attrs, expose, emit }) {
const inputRef = ref();
const clearableInputRef = ref();
let removePasswordTimeout: any;
const formItemContext = useInjectFormItemContext();
return {
configProvider: inject('configProvider', defaultConfigProvider),
removePasswordTimeout: undefined,
input: null,
clearableInput: null,
formItemContext,
};
},
data() {
const props = this.$props;
const value = typeof props.value === 'undefined' ? props.defaultValue : props.value;
return {
stateValue: typeof value === 'undefined' ? '' : value,
isFocused: false,
};
},
watch: {
value(val) {
this.stateValue = val;
},
},
mounted() {
nextTick(() => {
if (process.env.NODE_ENV === 'test') {
if (this.autofocus) {
this.focus();
const { direction, prefixCls, size, autocomplete } = useConfigInject('input', props);
const stateValue = ref(props.value === controlDefaultValue ? props.defaultValue : props.value);
const focused = ref(false);
watch(
() => props.value,
() => {
if (props.value !== controlDefaultValue) {
stateValue.value = props.value;
}
}
this.clearPasswordValueAttribute();
},
);
const clearPasswordValueAttribute = () => {
// https://github.com/ant-design/ant-design/issues/20541
removePasswordTimeout = setTimeout(() => {
if (
inputRef.value?.getAttribute('type') === 'password' &&
inputRef.value.hasAttribute('value')
) {
inputRef.value.removeAttribute('value');
}
});
};
const focus = (option?: InputFocusOptions) => {
triggerFocus(inputRef.value, option);
};
const blur = () => {
inputRef.value?.blur();
};
const setSelectionRange = (
start: number,
end: number,
direction?: 'forward' | 'backward' | 'none',
) => {
inputRef.value?.setSelectionRange(start, end, direction);
};
const select = () => {
inputRef.value?.select();
};
expose({
focus,
blur,
inputRef,
stateValue,
setSelectionRange,
select,
});
},
beforeUnmount() {
if (this.removePasswordTimeout) {
clearTimeout(this.removePasswordTimeout);
}
},
methods: {
handleInputFocus(e: Event) {
this.isFocused = true;
this.onFocus && this.onFocus(e);
},
handleInputBlur(e: Event) {
this.isFocused = false;
this.onBlur && this.onBlur(e);
this.formItemContext.onFieldBlur();
},
const onFocus: FocusEventHandler = e => {
const { onFocus } = props;
focused.value = true;
onFocus?.(e);
nextTick(() => {
clearPasswordValueAttribute();
});
};
focus() {
this.input.focus();
},
const onBlur: FocusEventHandler = e => {
const { onBlur } = props;
focused.value = false;
onBlur?.(e);
formItemContext.onFieldBlur();
nextTick(() => {
clearPasswordValueAttribute();
});
};
blur() {
this.input.blur();
},
select() {
this.input.select();
},
saveClearableInput(input: HTMLInputElement) {
this.clearableInput = input;
},
saveInput(input: HTMLInputElement) {
this.input = input;
},
setValue(value: string | number, callback?: Function) {
if (this.stateValue === value) {
const triggerChange = (e: Event) => {
emit('update:value', (e.target as HTMLInputElement).value);
emit('change', e);
emit('input', e);
formItemContext.onFieldChange();
};
const instance = getCurrentInstance();
const setValue = (value: string | number, callback?: Function) => {
if (stateValue.value === value) {
return;
}
if (!hasProp(this, 'value')) {
this.stateValue = value;
if (props.value === controlDefaultValue) {
stateValue.value = value;
} else {
(this as any).$forceUpdate();
instance.update();
}
nextTick(() => {
callback && callback();
});
},
triggerChange(e: Event) {
this.$emit('update:value', (e.target as HTMLInputElement).value);
this.$emit('change', e);
this.$emit('input', e);
this.formItemContext.onFieldChange();
},
handleReset(e: Event) {
this.setValue('', () => {
this.focus();
};
const handleReset = (e: MouseEvent) => {
resolveOnChange(inputRef.value, e, triggerChange);
setValue('', () => {
focus();
});
resolveOnChange(this.input, e, this.triggerChange);
},
renderInput(prefixCls: string, { addonBefore, addonAfter }) {
const otherProps = omit(this.$props, [
};
const handleChange = (e: ChangeEvent) => {
const { value, composing, isComposing } = e.target as any;
// https://github.com/vueComponent/ant-design-vue/issues/2203
if (((isComposing || composing) && props.lazy) || stateValue.value === value) return;
const newVal = e.target.value;
resolveOnChange(inputRef.value, e, triggerChange);
setValue(newVal, () => {
clearPasswordValueAttribute();
});
};
const handleKeyDown = (e: KeyboardEvent) => {
if (e.keyCode === 13) {
emit('pressEnter', e);
}
emit('keydown', e);
};
onMounted(() => {
if (process.env.NODE_ENV === 'test') {
if (props.autofocus) {
focus();
}
}
clearPasswordValueAttribute();
});
onBeforeUnmount(() => {
clearTimeout(removePasswordTimeout);
});
const renderInput = () => {
const {
addonBefore = slots.addonBefore,
addonAfter = slots.addonAfter,
disabled,
bordered = true,
valueModifiers = {},
} = props;
const otherProps = omit(props as InputProps & { inputType: any; placeholder: string }, [
'prefixCls',
'onPressEnter',
'addonBefore',
@ -159,36 +279,29 @@ export default defineComponent({
'prefix',
'suffix',
'allowClear',
// Input elements must be either controlled or uncontrolled,
// specify either the value prop, or the defaultValue prop, but not both.
'defaultValue',
'lazy',
'size',
'inputPrefixCls',
'loading',
'inputType',
'bordered',
]);
const {
handleKeyDown,
handleChange,
handleInputFocus,
handleInputBlur,
size,
disabled,
valueModifiers = {},
$attrs,
} = this;
const inputProps: any = {
const inputProps = {
...otherProps,
...$attrs,
id: otherProps.id ?? this.formItemContext.id.value,
onKeydown: handleKeyDown,
class: classNames(getInputClassName(prefixCls, size, disabled), {
[$attrs.class as string]: $attrs.class && !addonBefore && !addonAfter,
}),
ref: this.saveInput,
key: 'ant-input',
onInput: handleChange,
autocomplete: autocomplete.value,
onChange: handleChange,
onFocus: handleInputFocus,
onBlur: handleInputBlur,
onInput: handleChange,
onFocus,
onBlur,
onKeydown: handleKeyDown,
class: classNames(
getInputClassName(prefixCls.value, bordered, size.value, disabled, direction.value),
{
[attrs.class as string]: attrs.class && !addonBefore && !addonAfter,
},
),
ref: inputRef,
key: 'ant-input',
};
if (valueModifiers.lazy) {
delete inputProps.onInput;
@ -198,58 +311,185 @@ export default defineComponent({
}
const inputNode = <input {...inputProps} />;
return withDirectives(inputNode as VNode, [[antInputDirective]]);
},
clearPasswordValueAttribute() {
// https://github.com/ant-design/ant-design/issues/20541
this.removePasswordTimeout = setTimeout(() => {
if (
this.input &&
this.input.getAttribute &&
this.input.getAttribute('type') === 'password' &&
this.input.hasAttribute('value')
) {
this.input.removeAttribute('value');
}
});
},
handleChange(e: Event) {
const { value, composing, isComposing } = e.target as any;
// https://github.com/vueComponent/ant-design-vue/issues/2203
if (((isComposing || composing) && this.lazy) || this.stateValue === value) return;
this.setValue(value, this.clearPasswordValueAttribute);
resolveOnChange(this.input, e, this.triggerChange);
},
handleKeyDown(e: KeyboardEvent) {
if (e.keyCode === 13) {
this.$emit('pressEnter', e);
}
this.$emit('keydown', e);
},
},
render() {
const { prefixCls: customizePrefixCls } = this.$props;
const { stateValue, isFocused } = this.$data;
const getPrefixCls = this.configProvider.getPrefixCls;
const prefixCls = getPrefixCls('input', customizePrefixCls);
const addonAfter = getComponent(this, 'addonAfter');
const addonBefore = getComponent(this, 'addonBefore');
const suffix = getComponent(this, 'suffix');
const prefix = getComponent(this, 'prefix');
const props: any = {
...this.$attrs,
...getOptionProps(this),
prefixCls,
inputType: 'input',
value: fixControlledValue(stateValue),
element: this.renderInput(prefixCls, { addonAfter, addonBefore }),
handleReset: this.handleReset,
addonAfter,
addonBefore,
suffix,
prefix,
isFocused,
};
return <ClearableLabeledInput {...props} ref={this.saveClearableInput} />;
return () => {
const inputProps: any = {
...attrs,
...props,
prefixCls: prefixCls.value,
inputType: 'input',
value: fixControlledValue(stateValue.value),
handleReset,
focused: focused.value,
};
return (
<ClearableLabeledInput
{...omit(inputProps, ['element', 'valueModifiers'])}
ref={clearableInputRef}
v-slots={{ ...slots, element: renderInput }}
/>
);
};
},
// methods: {
// handleInputFocus(e: Event) {
// this.isFocused = true;
// this.onFocus && this.onFocus(e);
// },
// handleInputBlur(e: Event) {
// this.isFocused = false;
// this.onBlur && this.onBlur(e);
// this.formItemContext.onFieldBlur();
// },
// focus() {
// this.input.focus();
// },
// blur() {
// this.input.blur();
// },
// select() {
// this.input.select();
// },
// saveClearableInput(input: HTMLInputElement) {
// this.clearableInput = input;
// },
// saveInput(input: HTMLInputElement) {
// this.input = input;
// },
// setValue(value: string | number, callback?: Function) {
// if (this.stateValue === value) {
// return;
// }
// if (!hasProp(this, 'value')) {
// this.stateValue = value;
// } else {
// (this as any).$forceUpdate();
// }
// nextTick(() => {
// callback && callback();
// });
// },
// triggerChange(e: Event) {
// this.$emit('update:value', (e.target as HTMLInputElement).value);
// this.$emit('change', e);
// this.$emit('input', e);
// this.formItemContext.onFieldChange();
// },
// handleReset(e: Event) {
// this.setValue('', () => {
// this.focus();
// });
// resolveOnChange(this.input, e, this.triggerChange);
// },
// renderInput(prefixCls: string, { addonBefore, addonAfter }) {
// const otherProps = omit(this.$props, [
// 'prefixCls',
// 'onPressEnter',
// 'addonBefore',
// 'addonAfter',
// 'prefix',
// 'suffix',
// 'allowClear',
// 'defaultValue',
// 'lazy',
// 'size',
// 'inputPrefixCls',
// 'loading',
// ]);
// const {
// handleKeyDown,
// handleChange,
// handleInputFocus,
// handleInputBlur,
// size,
// disabled,
// valueModifiers = {},
// $attrs,
// } = this;
// const inputProps: any = {
// ...otherProps,
// ...$attrs,
// id: otherProps.id ?? this.formItemContext.id.value,
// onKeydown: handleKeyDown,
// class: classNames(getInputClassName(prefixCls, size, disabled), {
// [$attrs.class as string]: $attrs.class && !addonBefore && !addonAfter,
// }),
// ref: this.saveInput,
// key: 'ant-input',
// onInput: handleChange,
// onChange: handleChange,
// onFocus: handleInputFocus,
// onBlur: handleInputBlur,
// };
// if (valueModifiers.lazy) {
// delete inputProps.onInput;
// }
// if (!inputProps.autofocus) {
// delete inputProps.autofocus;
// }
// const inputNode = <input {...inputProps} />;
// return withDirectives(inputNode as VNode, [[antInputDirective]]);
// },
// clearPasswordValueAttribute() {
// // https://github.com/ant-design/ant-design/issues/20541
// this.removePasswordTimeout = setTimeout(() => {
// if (
// this.input &&
// this.input.getAttribute &&
// this.input.getAttribute('type') === 'password' &&
// this.input.hasAttribute('value')
// ) {
// this.input.removeAttribute('value');
// }
// });
// },
// handleChange(e: Event) {
// const { value, composing, isComposing } = e.target as any;
// // https://github.com/vueComponent/ant-design-vue/issues/2203
// if (((isComposing || composing) && this.lazy) || this.stateValue === value) return;
// this.setValue(value, this.clearPasswordValueAttribute);
// resolveOnChange(this.input, e, this.triggerChange);
// },
// handleKeyDown(e: KeyboardEvent) {
// if (e.keyCode === 13) {
// this.$emit('pressEnter', e);
// }
// this.$emit('keydown', e);
// },
// },
// render() {
// const { prefixCls: customizePrefixCls } = this.$props;
// const { stateValue, isFocused } = this.$data;
// const getPrefixCls = this.configProvider.getPrefixCls;
// const prefixCls = getPrefixCls('input', customizePrefixCls);
// const addonAfter = getComponent(this, 'addonAfter');
// const addonBefore = getComponent(this, 'addonBefore');
// const suffix = getComponent(this, 'suffix');
// const prefix = getComponent(this, 'prefix');
// const props: any = {
// ...this.$attrs,
// ...getOptionProps(this),
// prefixCls,
// inputType: 'input',
// value: fixControlledValue(stateValue),
// element: this.renderInput(prefixCls, { addonAfter, addonBefore }),
// handleReset: this.handleReset,
// addonAfter,
// addonBefore,
// suffix,
// prefix,
// isFocused,
// };
// return <ClearableLabeledInput {...props} ref={this.saveClearableInput} />;
// },
});

View File

@ -1,20 +1,23 @@
import classNames from '../_util/classNames';
import { getComponent, getOptionProps } from '../_util/props-util';
import { isValidElement } from '../_util/props-util';
import { cloneElement } from '../_util/vnode';
import Input from './Input';
import EyeOutlined from '@ant-design/icons-vue/EyeOutlined';
import EyeInvisibleOutlined from '@ant-design/icons-vue/EyeInvisibleOutlined';
import type { InputProps } from './inputProps';
import inputProps from './inputProps';
import PropTypes from '../_util/vue-types';
import BaseMixin from '../_util/BaseMixin';
import { defineComponent, inject } from 'vue';
import { defaultConfigProvider } from '../config-provider';
import { computed, defineComponent, ref } from 'vue';
import useConfigInject from '../_util/hooks/useConfigInject';
import omit from '../_util/omit';
const ActionMap = {
click: 'onClick',
hover: 'onMouseover',
};
const defaultIconRender = (visible: boolean) =>
visible ? <EyeOutlined /> : <EyeInvisibleOutlined />;
export default defineComponent({
name: 'AInputPassword',
mixins: [BaseMixin],
@ -25,96 +28,76 @@ export default defineComponent({
inputPrefixCls: PropTypes.string,
action: PropTypes.string.def('click'),
visibilityToggle: PropTypes.looseBool.def(true),
iconRender: PropTypes.func.def((visible: boolean) =>
visible ? <EyeOutlined /> : <EyeInvisibleOutlined />,
),
iconRender: PropTypes.func,
},
setup() {
return {
input: null,
configProvider: inject('configProvider', defaultConfigProvider),
};
},
data() {
return {
visible: false,
};
},
methods: {
saveInput(node: any) {
this.input = node;
},
focus() {
this.input.focus();
},
blur() {
this.input.blur();
},
onVisibleChange() {
if (this.disabled) {
setup(props, { slots, attrs, expose }) {
const visible = ref(false);
const onVisibleChange = () => {
const { disabled } = props;
if (disabled) {
return;
}
this.setState({
visible: !this.visible,
});
},
getIcon(prefixCls) {
const { action } = this.$props;
const iconTrigger = ActionMap[action] || '';
const iconRender = this.$slots.iconRender || this.$props.iconRender;
const icon = iconRender(this.visible);
visible.value = !visible.value;
};
const inputRef = ref();
const focus = () => {
inputRef.value?.focus();
};
const blur = () => {
inputRef.value?.blur();
};
expose({
focus,
blur,
});
const getIcon = (prefixCls: string) => {
const { action, iconRender = slots.iconRender || defaultIconRender } = props;
const iconTrigger = ActionMap[action!] || '';
const icon = iconRender(visible.value);
const iconProps = {
[iconTrigger]: this.onVisibleChange,
onMousedown: (e: Event) => {
[iconTrigger]: onVisibleChange,
class: `${prefixCls}-icon`,
key: 'passwordIcon',
onMousedown: (e: MouseEvent) => {
// Prevent focused state lost
// https://github.com/ant-design/ant-design/issues/15173
e.preventDefault();
},
onMouseup: (e: Event) => {
// Prevent focused state lost
// https://github.com/ant-design/ant-design/pull/23633/files
onMouseup: (e: MouseEvent) => {
// Prevent caret position change
// https://github.com/ant-design/ant-design/issues/23524
e.preventDefault();
},
class: `${prefixCls}-icon`,
key: 'passwordIcon',
};
return cloneElement(icon, iconProps);
},
},
render() {
const {
prefixCls: customizePrefixCls,
inputPrefixCls: customizeInputPrefixCls,
size,
suffix,
action,
visibilityToggle,
iconRender,
...restProps
} = getOptionProps(this);
const { class: className } = this.$attrs;
const getPrefixCls = this.configProvider.getPrefixCls;
const inputPrefixCls = getPrefixCls('input', customizeInputPrefixCls);
const prefixCls = getPrefixCls('input-password', customizePrefixCls);
const suffixIcon = visibilityToggle && this.getIcon(prefixCls);
const inputClassName = classNames(prefixCls, className, {
[`${prefixCls}-${size}`]: !!size,
});
const inputProps = {
...restProps,
prefixCls: inputPrefixCls,
size,
suffix: suffixIcon,
prefix: getComponent(this, 'prefix'),
addonAfter: getComponent(this, 'addonAfter'),
addonBefore: getComponent(this, 'addonBefore'),
...this.$attrs,
type: this.visible ? 'text' : 'password',
class: inputClassName,
ref: 'input',
return cloneElement(isValidElement(icon) ? icon : <span>{icon}</span>, iconProps);
};
const { prefixCls, getPrefixCls } = useConfigInject('input-password', props);
const inputPrefixCls = computed(() => getPrefixCls('input', props.inputPrefixCls));
const renderPassword = () => {
const { size, visibilityToggle, ...restProps } = props;
const suffixIcon = visibilityToggle && getIcon(prefixCls.value);
const inputClassName = classNames(prefixCls.value, attrs.class, {
[`${prefixCls.value}-${size}`]: !!size,
});
const omittedProps = {
...omit(restProps, ['suffix', 'iconRender']),
...attrs,
type: visible.value ? 'text' : 'password',
class: inputClassName,
prefixCls: inputPrefixCls.value,
suffix: suffixIcon,
} as InputProps;
if (size) {
omittedProps.size = size;
}
return <Input ref={inputRef} {...omittedProps} v-slots={slots} />;
};
return () => {
return renderPassword();
};
return <Input {...inputProps} ref={this.saveInput} />;
},
});

View File

@ -1,187 +1,145 @@
import { defineComponent, inject } from 'vue';
import type { PropType } from 'vue';
import { computed, ref, defineComponent } from 'vue';
import classNames from '../_util/classNames';
import isMobile from '../_util/isMobile';
import Input from './Input';
import LoadingOutlined from '@ant-design/icons-vue/LoadingOutlined';
import SearchOutlined from '@ant-design/icons-vue/SearchOutlined';
import inputProps from './inputProps';
import Button from '../button';
import { cloneElement } from '../_util/vnode';
import PropTypes from '../_util/vue-types';
import { getOptionProps, getComponent } from '../_util/props-util';
import { defaultConfigProvider } from '../config-provider';
import isPlainObject from 'lodash-es/isPlainObject';
import type { ChangeEvent, MouseEventHandler } from '../_util/EventInterface';
import useConfigInject from '../_util/hooks/useConfigInject';
import omit from '../_util/omit';
import isMobile from '../_util/isMobile';
export default defineComponent({
name: 'AInputSearch',
inheritAttrs: false,
props: {
...inputProps,
inputPrefixCls: PropTypes.string,
// https://github.com/vueComponent/ant-design-vue/issues/1916
enterButton: PropTypes.VNodeChild,
onSearch: PropTypes.func,
enterButton: PropTypes.any,
onSearch: {
type: Function as PropType<
(value: string, event?: ChangeEvent | MouseEvent | KeyboardEvent) => void
>,
},
},
setup() {
return {
configProvider: inject('configProvider', defaultConfigProvider),
input: null,
setup(props, { slots, attrs, expose, emit }) {
const inputRef = ref();
const focus = () => {
inputRef.value?.focus();
};
},
methods: {
saveInput(node: HTMLInputElement) {
this.input = node;
},
handleChange(e: Event) {
this.$emit('update:value', (e.target as HTMLInputElement).value);
const blur = () => {
inputRef.value?.blur();
};
expose({
focus,
blur,
});
const onChange = (e: ChangeEvent) => {
emit('update:value', (e.target as HTMLInputElement).value);
if (e && e.target && e.type === 'click') {
this.$emit('search', (e.target as HTMLInputElement).value, e);
emit('search', e.target.value, e);
}
this.$emit('change', e);
},
handleSearch(e: Event) {
if (this.loading || this.disabled) {
return;
emit('change', e);
};
const onMousedown: MouseEventHandler = e => {
if (document.activeElement === inputRef.value?.inputRef.value) {
e.preventDefault();
}
this.$emit('search', this.input.stateValue, e);
};
const onSearch = (e: MouseEvent | KeyboardEvent) => {
emit('search', inputRef.value?.stateValue, e);
if (!isMobile.tablet) {
this.input.focus();
inputRef.value.focus();
}
},
focus() {
this.input.focus();
},
};
blur() {
this.input.blur();
},
renderLoading(prefixCls: string) {
const { size } = this.$props;
let enterButton = getComponent(this, 'enterButton');
// <a-input-search enterButton /> enterButton any enterButton
const { prefixCls, getPrefixCls, direction, size } = useConfigInject('input-search', props);
const inputPrefixCls = computed(() => getPrefixCls('input', props.inputPrefixCls));
return () => {
const {
disabled,
loading,
addonAfter = slots.addonAfter?.(),
suffix = slots.suffix?.(),
...restProps
} = props;
let { enterButton = slots.enterButton?.() } = props;
enterButton = enterButton || enterButton === '';
if (enterButton) {
return (
<Button class={`${prefixCls}-button`} type="primary" size={size} key="enterButton">
<LoadingOutlined />
</Button>
);
}
return <LoadingOutlined class={`${prefixCls}-icon`} key="loadingIcon" />;
},
renderSuffix(prefixCls: string) {
const { loading } = this;
const suffix = getComponent(this, 'suffix');
let enterButton = getComponent(this, 'enterButton');
// <a-input-search enterButton /> enterButton any enterButton
enterButton = enterButton || enterButton === '';
if (loading && !enterButton) {
return [suffix, this.renderLoading(prefixCls)];
}
const searchIcon = typeof enterButton === 'boolean' ? <SearchOutlined /> : null;
const btnClassName = `${prefixCls.value}-button`;
if (enterButton) return suffix;
const icon = (
<SearchOutlined class={`${prefixCls}-icon`} key="searchIcon" onClick={this.handleSearch} />
);
if (suffix) {
// let cloneSuffix = suffix;
// if (isValidElement(cloneSuffix) && !cloneSuffix.key) {
// cloneSuffix = cloneElement(cloneSuffix, {
// key: 'originSuffix',
// });
// }
return [suffix, icon];
}
return icon;
},
renderAddonAfter(prefixCls: string) {
const { size, disabled, loading } = this;
const btnClassName = `${prefixCls}-button`;
let enterButton = getComponent(this, 'enterButton');
enterButton = enterButton || enterButton === '';
const addonAfter = getComponent(this, 'addonAfter');
if (loading && enterButton) {
return [this.renderLoading(prefixCls), addonAfter];
}
if (!enterButton) return addonAfter;
const enterButtonAsElement = Array.isArray(enterButton) ? enterButton[0] : enterButton;
let button: any;
const isAntdButton =
enterButtonAsElement.type &&
isPlainObject(enterButtonAsElement.type) &&
enterButtonAsElement.type.__ANT_BUTTON;
if (enterButtonAsElement.tagName === 'button' || isAntdButton) {
if (isAntdButton || enterButtonAsElement.tagName === 'button') {
button = cloneElement(enterButtonAsElement, {
onMousedown,
onClick: onSearch,
key: 'enterButton',
class: isAntdButton ? btnClassName : '',
...(isAntdButton ? { size } : {}),
onClick: this.handleSearch,
...(isAntdButton
? {
class: btnClassName,
size: size.value,
}
: {}),
});
} else {
button = (
<Button
class={btnClassName}
type="primary"
size={size}
type={enterButton ? 'primary' : undefined}
size={size.value}
disabled={disabled}
key="enterButton"
onClick={this.handleSearch}
onMousedown={onMousedown}
onClick={onSearch}
loading={loading}
icon={searchIcon}
>
{enterButton === true || enterButton === '' ? <SearchOutlined /> : enterButton}
{enterButton}
</Button>
);
}
if (addonAfter) {
return [button, addonAfter];
button = [button, addonAfter];
}
return button;
},
},
render() {
const {
prefixCls: customizePrefixCls,
inputPrefixCls: customizeInputPrefixCls,
size,
class: className,
...restProps
} = { ...getOptionProps(this), ...this.$attrs } as any;
delete restProps.onSearch;
delete restProps.loading;
delete restProps.enterButton;
delete restProps.addonBefore;
delete restProps['onUpdate:value'];
const getPrefixCls = this.configProvider.getPrefixCls;
const prefixCls = getPrefixCls('input-search', customizePrefixCls);
const inputPrefixCls = getPrefixCls('input', customizeInputPrefixCls);
let enterButton = getComponent(this, 'enterButton');
const addonBefore = getComponent(this, 'addonBefore');
enterButton = enterButton || enterButton === '';
let inputClassName: string;
if (enterButton) {
inputClassName = classNames(prefixCls, className, {
[`${prefixCls}-enter-button`]: !!enterButton,
[`${prefixCls}-${size}`]: !!size,
});
} else {
inputClassName = classNames(prefixCls, className);
}
const inputProps = {
...restProps,
prefixCls: inputPrefixCls,
size,
suffix: this.renderSuffix(prefixCls),
prefix: getComponent(this, 'prefix'),
addonAfter: this.renderAddonAfter(prefixCls),
addonBefore,
class: inputClassName,
onPressEnter: this.handleSearch,
onChange: this.handleChange,
const cls = classNames(
prefixCls.value,
{
[`${prefixCls.value}-rtl`]: direction.value === 'rtl',
[`${prefixCls.value}-${size.value}`]: !!size.value,
[`${prefixCls.value}-with-button`]: !!enterButton,
},
attrs.class,
);
return (
<Input
ref={inputRef}
{...omit(restProps, ['onUpdate:value', 'onSearch'])}
{...attrs}
onPressEnter={onSearch}
size={size.value}
prefixCls={inputPrefixCls.value}
addonAfter={button}
suffix={suffix}
onChange={onChange}
class={cls}
disabled={disabled}
v-slots={slots}
/>
);
};
return <Input {...inputProps} ref={this.saveInput} />;
},
});

View File

@ -1,29 +1,62 @@
import type { PropType } from 'vue';
import type { ExtractPropTypes, PropType } from 'vue';
import PropTypes from '../_util/vue-types';
import type { SizeType } from '../config-provider';
export default {
import { controlDefaultValue } from '../_util/util';
export const inputDefaultValue = Symbol() as unknown as string;
const inputProps = {
id: PropTypes.string,
prefixCls: PropTypes.string,
inputPrefixCls: PropTypes.string,
defaultValue: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
value: {
type: [String, Number, Symbol] as PropType<string | number>,
default: controlDefaultValue,
},
placeholder: {
type: [String, Number] as PropType<string | number>,
},
type: PropTypes.string.def('text'),
autocomplete: String,
type: {
type: String as PropType<
| 'button'
| 'checkbox'
| 'color'
| 'date'
| 'datetime-local'
| 'email'
| 'file'
| 'hidden'
| 'image'
| 'month'
| 'number'
| 'password'
| 'radio'
| 'range'
| 'reset'
| 'search'
| 'submit'
| 'tel'
| 'text'
| 'time'
| 'url'
| 'week'
>,
default: 'text',
},
name: PropTypes.string,
size: { type: String as PropType<SizeType> },
disabled: PropTypes.looseBool,
readonly: PropTypes.looseBool,
addonBefore: PropTypes.VNodeChild,
addonAfter: PropTypes.VNodeChild,
prefix: PropTypes.VNodeChild,
suffix: PropTypes.VNodeChild,
addonBefore: PropTypes.any,
addonAfter: PropTypes.any,
prefix: PropTypes.any,
suffix: PropTypes.any,
autofocus: PropTypes.looseBool,
allowClear: PropTypes.looseBool,
lazy: PropTypes.looseBool.def(true),
maxlength: PropTypes.number,
loading: PropTypes.looseBool,
bordered: PropTypes.looseBool,
onPressEnter: PropTypes.func,
onKeydown: PropTypes.func,
onKeyup: PropTypes.func,
@ -34,3 +67,5 @@ export default {
'onUpdate:value': PropTypes.func,
valueModifiers: Object,
};
export default inputProps;
export type InputProps = Partial<ExtractPropTypes<typeof inputProps>>;

View File

@ -6,29 +6,66 @@
@search-prefix: ~'@{ant-prefix}-input-search';
.@{search-prefix} {
&-icon {
color: @text-color-secondary;
cursor: pointer;
transition: all 0.3s;
&:hover {
color: @input-icon-hover-color;
.@{ant-prefix}-input {
&:hover,
&:focus {
border-color: @input-hover-border-color;
+ .@{ant-prefix}-input-group-addon .@{search-prefix}-button:not(.@{ant-prefix}-btn-primary) {
border-left-color: @input-hover-border-color;
}
}
}
&-enter-button {
input {
border-right: 0;
}
.@{ant-prefix}-input-affix-wrapper {
border-radius: 0;
}
& + .@{ant-prefix}-input-group-addon,
input + .@{ant-prefix}-input-group-addon {
// fix slight height diff in Firefox:
// https://ant.design/components/auto-complete-cn/#components-auto-complete-demo-certain-category
.@{ant-prefix}-input-lg {
line-height: @line-height-base - 0.0002;
}
> .@{ant-prefix}-input-group {
> .@{ant-prefix}-input-group-addon:last-child {
left: -1px;
padding: 0;
border: 0;
.@{search-prefix}-button {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
padding-top: 0;
padding-bottom: 0;
border-radius: 0 @border-radius-base @border-radius-base 0;
}
.@{search-prefix}-button:not(.@{ant-prefix}-btn-primary) {
color: @text-color-secondary;
&.@{ant-prefix}-btn-loading::before {
top: 0;
right: 0;
bottom: 0;
left: 0;
}
}
}
}
&-button {
height: @input-height-base;
&:hover,
&:focus {
z-index: 1;
}
}
&-large &-button {
height: @input-height-lg;
}
&-small &-button {
height: @input-height-sm;
}
}