antd-tiny-vue/components/button/button.tsx

228 lines
6.4 KiB
TypeScript
Raw Normal View History

2023-03-27 07:51:19 +08:00
import { computed, defineComponent, onMounted, shallowRef } from 'vue'
import { tryOnBeforeUnmount } from '@vueuse/core'
2023-04-16 18:09:13 +08:00
import {
classNames,
filterEmpty,
getSlotsProps,
runEvent,
useState
} from '@v-c/utils'
2023-03-17 22:38:25 +08:00
import { useProviderConfigState } from '../config-provider/context'
2023-03-27 07:51:19 +08:00
import warning from '../_util/warning'
2023-03-18 18:17:20 +08:00
import Wave from '../_util/wave'
2023-03-27 07:51:19 +08:00
import { useSize } from '../_util/hooks/size'
import { useDisabled } from '../_util/hooks/disabled'
import { useCompactItemContext } from '../space/compact'
2023-03-17 22:38:25 +08:00
import useStyle from './style'
2023-03-27 07:51:19 +08:00
import type { ButtonProps, LoadingConfigType } from './interface'
2023-03-25 17:16:46 +08:00
import { buttonProps } from './interface'
2023-04-17 10:55:29 +08:00
import {
isTwoCNChar,
isUnBorderedButtonType,
spaceChildren
} from './button-helper'
2023-03-27 07:51:19 +08:00
type Loading = number | boolean
function getLoadingConfig(loading: ButtonProps['loading']): LoadingConfigType {
if (typeof loading === 'object' && loading) {
const delay = loading?.delay
const isDelay = !Number.isNaN(delay) && typeof delay === 'number'
return {
loading: false,
delay: isDelay ? delay : 0
}
}
return {
2023-04-16 18:09:13 +08:00
loading,
2023-03-27 07:51:19 +08:00
delay: 0
}
}
2023-03-10 22:11:33 +08:00
const Button = defineComponent({
name: 'AButton',
2023-03-17 22:38:25 +08:00
inheritAttrs: false,
2023-03-25 17:16:46 +08:00
__ANT_BUTTON: true,
2023-03-17 22:38:25 +08:00
props: {
2023-03-25 17:16:46 +08:00
...buttonProps
2023-03-17 22:38:25 +08:00
},
setup(props, { slots, attrs }) {
2023-04-16 18:09:13 +08:00
const { getPrefixCls, autoInsertSpaceInButton, direction } =
useProviderConfigState()
2023-03-17 22:38:25 +08:00
const prefixCls = computed(() => getPrefixCls('btn', props.prefixCls))
const [wrapSSR, hashId] = useStyle(prefixCls)
2023-03-27 07:51:19 +08:00
const size = useSize(props)
2023-04-16 18:09:13 +08:00
const { compactSize, compactItemClassnames } = useCompactItemContext(
prefixCls,
direction
)
2023-03-27 07:51:19 +08:00
const sizeCls = computed(() => {
2023-04-17 08:08:22 +08:00
const sizeClassNameMap: Record<string, any> = {
large: 'lg',
small: 'sm',
middle: undefined
}
2023-04-17 10:09:13 +08:00
const sizeFullName = compactSize?.value || size.value
return sizeClassNameMap[sizeFullName]
2023-03-27 07:51:19 +08:00
})
const disabled = useDisabled(props)
const buttonRef = shallowRef<HTMLButtonElement | null>(null)
const loadingOrDelay = computed(() => {
return getLoadingConfig(props.loading)
})
2023-04-16 18:09:13 +08:00
const [innerLoading, setLoading] = useState<Loading>(
loadingOrDelay.value.loading
)
2023-03-27 07:51:19 +08:00
const [hasTwoCNChar, setHasTwoCNChar] = useState(false)
2023-03-17 22:38:25 +08:00
2023-04-17 10:51:11 +08:00
let isNeedInserted = false
2023-04-17 10:09:13 +08:00
2023-04-17 10:51:11 +08:00
const fixTwoCNChar = () => {
2023-04-17 10:09:13 +08:00
// FIXME: for HOC usage like <FormatMessage />
if (!buttonRef.value || autoInsertSpaceInButton.value === false) {
return
}
const buttonText = buttonRef.value.textContent
2023-04-17 10:51:11 +08:00
if (isNeedInserted && isTwoCNChar(buttonText as string)) {
if (!hasTwoCNChar.value) {
2023-04-17 10:09:13 +08:00
setHasTwoCNChar(true)
}
2023-04-17 10:51:11 +08:00
} else if (hasTwoCNChar.value) {
2023-04-17 10:09:13 +08:00
setHasTwoCNChar(false)
}
}
2023-03-27 07:51:19 +08:00
let delayTimer: number | null = null
onMounted(() => {
if (loadingOrDelay.value.delay > 0) {
delayTimer = window.setTimeout(() => {
delayTimer = null
setLoading(true)
}, loadingOrDelay.value.delay)
} else {
setLoading(loadingOrDelay.value.loading)
}
})
function cleanupTimer() {
if (delayTimer) {
window.clearTimeout(delayTimer)
delayTimer = null
2023-03-17 22:38:25 +08:00
}
2023-03-27 07:51:19 +08:00
}
tryOnBeforeUnmount(() => {
cleanupTimer()
2023-03-17 22:38:25 +08:00
})
2023-03-27 07:51:19 +08:00
const handleClick = (e: MouseEvent) => {
// FIXME: https://github.com/ant-design/ant-design/issues/30207
if (innerLoading.value || disabled.value) {
e.preventDefault()
return
}
runEvent(props, 'onClick', e)
}
const showError = () => {
const { ghost, type } = props
const icon = getSlotsProps(slots, props, 'icon')
2023-04-16 18:09:13 +08:00
warning(
!(typeof icon === 'string' && icon.length > 2),
'Button',
`\`icon\` is using ReactNode instead of string naming in v4. Please check \`${icon}\` at https://ant.design/components/icon`
)
2023-03-27 07:51:19 +08:00
2023-04-16 18:09:13 +08:00
warning(
!(ghost && isUnBorderedButtonType(type)),
'Button',
"`link` or `text` button can't be a `ghost` button."
)
2023-03-27 07:51:19 +08:00
}
2023-03-10 22:11:33 +08:00
return () => {
2023-03-27 07:51:19 +08:00
const { shape, rootClassName, ghost, type, block, danger } = props
const icon = getSlotsProps(slots, props, 'icon')
const children = filterEmpty(slots.default?.())
2023-04-17 10:51:11 +08:00
isNeedInserted =
children.length === 1 &&
!slots.icon &&
isUnBorderedButtonType(props.type)
fixTwoCNChar()
2023-03-27 07:51:19 +08:00
showError()
const iconType = innerLoading.value ? 'loading' : icon
const autoInsertSpace = autoInsertSpaceInButton.value !== false
const hrefAndDisabled = attrs.href !== undefined && disabled.value
const classes = classNames(
prefixCls.value,
hashId.value,
{
[`${prefixCls.value}-${shape}`]: shape !== 'default' && shape,
[`${prefixCls.value}-${type}`]: type,
[`${prefixCls.value}-${sizeCls.value}`]: sizeCls.value,
2023-04-16 18:09:13 +08:00
[`${prefixCls.value}-icon-only`]:
!children && children !== 0 && !!iconType,
[`${prefixCls.value}-background-ghost`]:
ghost && !isUnBorderedButtonType(type),
2023-03-27 07:51:19 +08:00
[`${prefixCls.value}-loading`]: innerLoading.value,
2023-04-16 18:09:13 +08:00
[`${prefixCls.value}-two-chinese-chars`]:
hasTwoCNChar.value && autoInsertSpace && !innerLoading.value,
2023-03-27 07:51:19 +08:00
[`${prefixCls.value}-block`]: block,
[`${prefixCls.value}-dangerous`]: !!danger,
[`${prefixCls.value}-rtl`]: direction.value === 'rtl',
[`${prefixCls.value}-disabled`]: hrefAndDisabled
},
attrs.class,
compactItemClassnames.value,
rootClassName
)
2023-04-17 10:51:11 +08:00
const iconNode = icon && (!innerLoading.value ? icon?.() : <>L</>)
2023-03-27 07:51:19 +08:00
2023-04-17 10:55:29 +08:00
const kids =
children || children === 0
? spaceChildren(children[0] as any, isNeedInserted && autoInsertSpace)
: undefined
2023-03-27 07:51:19 +08:00
if (attrs.href !== undefined) {
return wrapSSR(
<a
2023-03-18 18:17:20 +08:00
{...attrs}
2023-03-27 07:51:19 +08:00
{...props}
class={classes}
onClick={handleClick}
ref={buttonRef}
2023-03-18 18:17:20 +08:00
>
2023-03-27 07:51:19 +08:00
{iconNode}
2023-04-17 10:55:29 +08:00
{kids}
2023-03-27 07:51:19 +08:00
</a>
)
}
let buttonNode = (
2023-04-17 10:09:13 +08:00
<button
{...attrs}
onClick={handleClick}
class={classes}
ref={buttonRef}
>
2023-04-17 10:51:11 +08:00
{iconNode}
2023-04-17 10:55:29 +08:00
{kids}
2023-03-27 07:51:19 +08:00
</button>
2023-03-17 22:38:25 +08:00
)
2023-03-27 07:51:19 +08:00
if (!isUnBorderedButtonType(type)) {
buttonNode = <Wave>{buttonNode}</Wave>
}
return wrapSSR(buttonNode)
2023-03-10 22:11:33 +08:00
}
}
})
export default Button