🌈 An enterprise-class UI components based on Ant Design and Vue. 🐜
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

245 lines
7.2 KiB

import {
computed,
defineComponent,
onBeforeUnmount,
onMounted,
onUpdated,
shallowRef,
Text,
watch,
watchEffect,
} from 'vue';
import Wave from '../_util/wave';
import buttonProps from './buttonTypes';
import { flattenChildren, initDefaultProps } from '../_util/props-util';
import useConfigInject from '../config-provider/hooks/useConfigInject';
import { useInjectDisabled } from '../config-provider/DisabledContext';
import devWarning from '../vc-util/devWarning';
import LoadingIcon from './LoadingIcon';
import useStyle from './style';
import type { ButtonType } from './buttonTypes';
import type { VNode } from 'vue';
import { GroupSizeContext } from './button-group';
import { useCompactItemContext } from '../space/Compact';
import type { CustomSlotsType } from '../_util/type';
type Loading = boolean | number;
const rxTwoCNChar = /^[\u4e00-\u9fa5]{2}$/;
const isTwoCNChar = rxTwoCNChar.test.bind(rxTwoCNChar);
function isUnBorderedButtonType(type: ButtonType | undefined) {
return type === 'text' || type === 'link';
}
export { buttonProps };
export default defineComponent({
compatConfig: { MODE: 3 },
name: 'AButton',
inheritAttrs: false,
__ANT_BUTTON: true,
props: initDefaultProps(buttonProps(), { type: 'default' }),
slots: Object as CustomSlotsType<{
icon: any;
default: any;
}>,
// emits: ['click', 'mousedown'],
setup(props, { slots, attrs, emit, expose }) {
const { prefixCls, autoInsertSpaceInButton, direction, size } = useConfigInject('btn', props);
const [wrapSSR, hashId] = useStyle(prefixCls);
const groupSizeContext = GroupSizeContext.useInject();
const disabledContext = useInjectDisabled();
const mergedDisabled = computed(() => props.disabled ?? disabledContext.value);
const buttonNodeRef = shallowRef<HTMLElement>(null);
const delayTimeoutRef = shallowRef(undefined);
let isNeedInserted = false;
const innerLoading = shallowRef<Loading>(false);
const hasTwoCNChar = shallowRef(false);
const autoInsertSpace = computed(() => autoInsertSpaceInButton.value !== false);
const { compactSize, compactItemClassnames } = useCompactItemContext(prefixCls, direction);
// =============== Update Loading ===============
const loadingOrDelay = computed(() =>
typeof props.loading === 'object' && props.loading.delay
? props.loading.delay || true
: !!props.loading,
);
watch(
loadingOrDelay,
val => {
clearTimeout(delayTimeoutRef.value);
if (typeof loadingOrDelay.value === 'number') {
delayTimeoutRef.value = setTimeout(() => {
innerLoading.value = val;
}, loadingOrDelay.value);
} else {
innerLoading.value = val;
}
},
{
immediate: true,
},
);
const classes = computed(() => {
const { type, shape = 'default', ghost, block, danger } = props;
const pre = prefixCls.value;
const sizeClassNameMap = { large: 'lg', small: 'sm', middle: undefined };
const sizeFullname = compactSize.value || groupSizeContext?.size || size.value;
const sizeCls = sizeFullname ? sizeClassNameMap[sizeFullname] || '' : '';
return [
compactItemClassnames.value,
{
[hashId.value]: true,
[`${pre}`]: true,
[`${pre}-${shape}`]: shape !== 'default' && shape,
[`${pre}-${type}`]: type,
[`${pre}-${sizeCls}`]: sizeCls,
[`${pre}-loading`]: innerLoading.value,
[`${pre}-background-ghost`]: ghost && !isUnBorderedButtonType(type),
[`${pre}-two-chinese-chars`]: hasTwoCNChar.value && autoInsertSpace.value,
[`${pre}-block`]: block,
[`${pre}-dangerous`]: !!danger,
[`${pre}-rtl`]: direction.value === 'rtl',
},
];
});
const fixTwoCNChar = () => {
// Fix for HOC usage like <FormatMessage />
const node = buttonNodeRef.value!;
if (!node || autoInsertSpaceInButton.value === false) {
return;
}
const buttonText = node.textContent;
if (isNeedInserted && isTwoCNChar(buttonText)) {
if (!hasTwoCNChar.value) {
hasTwoCNChar.value = true;
}
} else if (hasTwoCNChar.value) {
hasTwoCNChar.value = false;
}
};
const handleClick = (event: Event) => {
// https://github.com/ant-design/ant-design/issues/30207
if (innerLoading.value || mergedDisabled.value) {
event.preventDefault();
return;
}
emit('click', event);
};
const handleMousedown = (event: Event) => {
emit('mousedown', event);
};
const insertSpace = (child: VNode, needInserted: boolean) => {
const SPACE = needInserted ? ' ' : '';
if (child.type === Text) {
let text = (child.children as string).trim();
if (isTwoCNChar(text)) {
text = text.split('').join(SPACE);
}
return <span>{text}</span>;
}
return child;
};
watchEffect(() => {
devWarning(
!(props.ghost && isUnBorderedButtonType(props.type)),
'Button',
"`link` or `text` button can't be a `ghost` button.",
);
});
onMounted(fixTwoCNChar);
onUpdated(fixTwoCNChar);
onBeforeUnmount(() => {
delayTimeoutRef.value && clearTimeout(delayTimeoutRef.value);
});
const focus = () => {
buttonNodeRef.value?.focus();
};
const blur = () => {
buttonNodeRef.value?.blur();
};
expose({
focus,
blur,
});
return () => {
const { icon = slots.icon?.() } = props;
const children = flattenChildren(slots.default?.());
isNeedInserted = children.length === 1 && !icon && !isUnBorderedButtonType(props.type);
const { type, htmlType, href, title, target } = props;
const iconType = innerLoading.value ? 'loading' : icon;
const buttonProps = {
...attrs,
title,
disabled: mergedDisabled.value,
class: [
classes.value,
attrs.class,
{ [`${prefixCls.value}-icon-only`]: children.length === 0 && !!iconType },
],
onClick: handleClick,
onMousedown: handleMousedown,
};
// https://github.com/vueComponent/ant-design-vue/issues/4930
if (!mergedDisabled.value) {
delete buttonProps.disabled;
}
const iconNode =
icon && !innerLoading.value ? (
icon
) : (
<LoadingIcon
existIcon={!!icon}
prefixCls={prefixCls.value}
loading={!!innerLoading.value}
/>
);
const kids = children.map(child =>
insertSpace(child, isNeedInserted && autoInsertSpace.value),
);
if (href !== undefined) {
return wrapSSR(
<a {...buttonProps} href={href} target={target} ref={buttonNodeRef}>
{iconNode}
{kids}
</a>,
);
}
let buttonNode = (
<button {...buttonProps} ref={buttonNodeRef} type={htmlType}>
{iconNode}
{kids}
</button>
);
if (!isUnBorderedButtonType(type)) {
buttonNode = (
<Wave ref="wave" disabled={!!innerLoading.value}>
{buttonNode}
</Wave>
);
}
return wrapSSR(buttonNode);
};
},
});