2023-03-27 07:51:19 +08:00
import { computed , defineComponent , onMounted , shallowRef } from 'vue'
import { tryOnBeforeUnmount } from '@vueuse/core'
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-03-27 07:51:19 +08:00
import { isTwoCNChar , isUnBorderedButtonType } from './button-helper'
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 {
loading : ! ! loading ,
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-03-27 07:51:19 +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 )
const { compactSize , compactItemClassnames } = useCompactItemContext ( prefixCls , direction )
const sizeCls = computed ( ( ) = > {
const sizeClassNameMap = { large : 'lg' , small : 'sm' , middle : undefined }
const sizeFullname = compactSize ? . value || size . value
return sizeClassNameMap [ sizeFullname ! ]
} )
const disabled = useDisabled ( props )
const buttonRef = shallowRef < HTMLButtonElement | null > ( null )
const loadingOrDelay = computed ( ( ) = > {
return getLoadingConfig ( props . loading )
} )
const [ innerLoading , setLoading ] = useState < Loading > ( loadingOrDelay . value . loading )
const [ hasTwoCNChar , setHasTwoCNChar ] = useState ( false )
2023-03-17 22:38:25 +08:00
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 )
}
// fixTwoCNChar()
} )
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' )
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 ` )
warning ( ! ( ghost && isUnBorderedButtonType ( type ) ) , 'Button' , "`link` or `text` button can't be a `ghost` button." )
}
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 ? . ( ) )
const isNeedInserted = ( ) = > {
return children . length === 1 && ! slots . icon && isUnBorderedButtonType ( props . type )
}
const fixTwoCNChar = ( ) = > {
// FIXME: for HOC usage like <FormatMessage />
if ( ! buttonRef . value || autoInsertSpaceInButton . value === false ) {
return
}
const buttonText = buttonRef . value . textContent
if ( isNeedInserted ( ) && isTwoCNChar ( buttonText as string ) ) {
if ( ! hasTwoCNChar ) {
setHasTwoCNChar ( true )
}
} else if ( hasTwoCNChar ) {
setHasTwoCNChar ( false )
}
}
fixTwoCNChar ( )
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 ,
[ ` ${ prefixCls . value } -icon-only ` ] : ! children && children !== 0 && ! ! iconType ,
[ ` ${ prefixCls . value } -background-ghost ` ] : ghost && ! isUnBorderedButtonType ( type ) ,
[ ` ${ prefixCls . value } -loading ` ] : innerLoading . value ,
[ ` ${ prefixCls . value } -two-chinese-chars ` ] : hasTwoCNChar . value && autoInsertSpace && ! innerLoading . value ,
[ ` ${ prefixCls . value } -block ` ] : block ,
[ ` ${ prefixCls . value } -dangerous ` ] : ! ! danger ,
[ ` ${ prefixCls . value } -rtl ` ] : direction . value === 'rtl' ,
[ ` ${ prefixCls . value } -disabled ` ] : hrefAndDisabled
} ,
attrs . class ,
compactItemClassnames . value ,
rootClassName
)
const iconNode = icon && ! innerLoading . value ? icon ? . ( ) : < > L < / >
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 }
{ children }
< / a >
)
}
let buttonNode = (
< button
{ . . . attrs }
onClick = { handleClick }
class = { classes }
>
{ children }
< / 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