import classNames from '../_util/classNames'; import CloseCircleFilled from '@ant-design/icons-vue/CloseCircleFilled'; import { getInputClassName } from './Input'; import PropTypes from '../_util/vue-types'; import { cloneElement } from '../_util/vnode'; 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(propsAndSlots: any) { return !!(propsAndSlots.prefix || propsAndSlots.suffix || propsAndSlots.allowClear); } function hasAddon(propsAndSlots: any) { return !!(propsAndSlots.addonBefore || propsAndSlots.addonAfter); } const ClearableInputType = ['text', 'input']; export default defineComponent({ name: 'ClearableLabeledInput', inheritAttrs: false, props: { prefixCls: PropTypes.string, inputType: PropTypes.oneOf(tuple('text', 'input')), value: PropTypes.any, defaultValue: PropTypes.any, allowClear: PropTypes.looseBool, element: PropTypes.any, handleReset: PropTypes.func, disabled: PropTypes.looseBool, direction: { type: String as PropType }, size: { type: String as PropType }, suffix: PropTypes.any, prefix: PropTypes.any, addonBefore: PropTypes.any, addonAfter: PropTypes.any, readonly: PropTypes.looseBool, focused: PropTypes.looseBool, bordered: PropTypes.looseBool.def(true), triggerFocus: { type: Function as PropType<() => void> }, }, 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, suffix = slots.suffix } = props; if (!allowClear) { return null; } const needClear = !disabled && !readonly && value; const className = `${prefixCls}-clear-icon`; return ( e.preventDefault()} class={classNames( { [`${className}-hidden`]: !needClear, [`${className}-has-suffix`]: !!suffix, }, className, )} role="button" /> ); }; const renderSuffix = (prefixCls: string) => { const { suffix = slots.suffix?.(), allowClear } = props; if (suffix || allowClear) { return ( {renderClearIcon(prefixCls)} {suffix} ); } return null; }; 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, }); } const prefixNode = prefix ? {prefix} : null; 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 ( {prefixNode} {cloneElement(element, { style: null, value, class: getInputClassName(prefixCls, bordered, size, disabled), })} {suffixNode} ); }; const renderInputWithLabel = (prefixCls: string, labeledElement: VNode) => { const { addonBefore = slots.addonBefore?.(), addonAfter = slots.addonAfter?.(), size, direction, } = props; // Not wrap when there is not addons if (!hasAddon({ addonBefore, addonAfter })) { return labeledElement; } const wrapperClassName = `${prefixCls}-group`; const addonClassName = `${wrapperClassName}-addon`; const addonBeforeNode = addonBefore ? ( {addonBefore} ) : null; const addonAfterNode = addonAfter ? {addonAfter} : null; const mergedWrapperClassName = classNames(`${prefixCls}-wrapper`, wrapperClassName, { [`${wrapperClassName}-rtl`]: direction === 'rtl', }); 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 ( {addonBeforeNode} {cloneElement(labeledElement, { style: null })} {addonAfterNode} ); }; const renderTextAreaWithClearIcon = (prefixCls: string, element: VNode) => { const { value, allowClear, direction, bordered, addonAfter = slots.addonAfter, addonBefore = slots.addonBefore, } = props; if (!allowClear) { return cloneElement(element, { value, }); } const affixWrapperCls = classNames( `${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 ( {cloneElement(element, { style: null, value, })} {renderClearIcon(prefixCls)} ); }; return () => { const { prefixCls, inputType, element = slots.element?.() } = props; if (inputType === ClearableInputType[0]) { return renderTextAreaWithClearIcon(prefixCls, element); } return renderInputWithLabel(prefixCls, renderLabeledIcon(prefixCls, element)); }; }, });