diff --git a/components/button/button-group.tsx b/components/button/button-group.tsx index f6a897d6d..66494438d 100644 --- a/components/button/button-group.tsx +++ b/components/button/button-group.tsx @@ -1,4 +1,4 @@ -import { defineComponent } from 'vue'; +import { computed, defineComponent } from 'vue'; import { flattenChildren } from '../_util/props-util'; import PropTypes from '../_util/vue-types'; import useConfigInject from '../_util/hooks/useConfigInject'; @@ -21,10 +21,8 @@ export default defineComponent({ props: buttonGroupProps, setup(props, { slots }) { const { prefixCls, direction } = useConfigInject('btn-group', props); - - return () => { + const classes = computed(() => { const { size } = props; - // large => lg // small => sm let sizeCls = ''; @@ -38,12 +36,14 @@ export default defineComponent({ default: break; } - const classes = { + return { [`${prefixCls.value}`]: true, [`${prefixCls.value}-${sizeCls}`]: sizeCls, [`${prefixCls.value}-rtl`]: direction.value === 'rtl', }; - return
{flattenChildren(slots.default?.())}
; + }); + return () => { + return
{flattenChildren(slots.default?.())}
; }; }, }); diff --git a/components/button/button.tsx b/components/button/button.tsx index 7c39538eb..8929d7ace 100644 --- a/components/button/button.tsx +++ b/components/button/button.tsx @@ -4,6 +4,7 @@ import { onBeforeUnmount, onMounted, onUpdated, + Ref, ref, Text, watch, @@ -19,6 +20,8 @@ import devWarning from '../vc-util/devWarning'; import type { ButtonType } from './buttonTypes'; import type { VNode } from 'vue'; +type Loading = boolean | number; + const rxTwoCNChar = /^[\u4e00-\u9fa5]{2}$/; const isTwoCNChar = rxTwoCNChar.test.bind(rxTwoCNChar); const props = buttonTypes(); @@ -38,18 +41,41 @@ export default defineComponent({ const { prefixCls, autoInsertSpaceInButton, direction } = useConfigInject('btn', props); const buttonNodeRef = ref(null); - const delayTimeout = ref(undefined); - const iconCom = ref(null); - const children = ref([]); + const delayTimeoutRef = ref(undefined); + let isNeedInserted = false; - const sLoading = ref(props.loading); + const innerLoading: Ref = ref(false); const hasTwoCNChar = ref(false); const autoInsertSpace = computed(() => autoInsertSpaceInButton.value !== false); - const getClasses = () => { - const { type, shape, size, ghost, block, danger } = props; + // =============== 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 = window.setTimeout(() => { + innerLoading.value = val; + }, loadingOrDelay.value); + } else { + innerLoading.value = val; + } + }, + { + immediate: true, + }, + ); + + const classes = computed(() => { + const { type, shape, size, ghost, block, danger } = props; + const pre = prefixCls.value; // large => lg // small => sm let sizeCls = ''; @@ -63,22 +89,20 @@ export default defineComponent({ default: break; } - const iconType = sLoading.value ? 'loading' : iconCom.value; return { [attrs.class as string]: attrs.class, - [`${prefixCls.value}`]: true, - [`${prefixCls.value}-${type}`]: type, - [`${prefixCls.value}-${shape}`]: shape, - [`${prefixCls.value}-${sizeCls}`]: sizeCls, - [`${prefixCls.value}-icon-only`]: children.value.length === 0 && !!iconType, - [`${prefixCls.value}-loading`]: sLoading.value, - [`${prefixCls.value}-background-ghost`]: ghost && !isUnborderedButtonType(type), - [`${prefixCls.value}-two-chinese-chars`]: hasTwoCNChar.value && autoInsertSpace.value, - [`${prefixCls.value}-block`]: block, - [`${prefixCls.value}-dangerous`]: !!danger, - [`${prefixCls.value}-rtl`]: direction.value === 'rtl', + [`${pre}`]: true, + [`${pre}-${type}`]: type, + [`${pre}-${shape}`]: shape, + [`${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 @@ -88,7 +112,7 @@ export default defineComponent({ } const buttonText = node.textContent; - if (isNeedInserted() && isTwoCNChar(buttonText)) { + if (isNeedInserted && isTwoCNChar(buttonText)) { if (!hasTwoCNChar.value) { hasTwoCNChar.value = true; } @@ -98,7 +122,7 @@ export default defineComponent({ }; const handleClick = (event: Event) => { // https://github.com/ant-design/ant-design/issues/30207 - if (sLoading.value || props.disabled) { + if (innerLoading.value || props.disabled) { event.preventDefault(); return; } @@ -117,9 +141,6 @@ export default defineComponent({ return child; }; - const isNeedInserted = () => - children.value.length === 1 && !iconCom.value && !isUnborderedButtonType(props.type); - watchEffect(() => { devWarning( !(props.ghost && isUnborderedButtonType(props.type)), @@ -128,50 +149,45 @@ export default defineComponent({ ); }); - watch( - () => props.loading, - (val, preVal) => { - if (preVal && typeof preVal !== 'boolean') { - clearTimeout(delayTimeout.value); - } - if (val && typeof val !== 'boolean' && val.delay) { - delayTimeout.value = setTimeout(() => { - sLoading.value = !!val; - }, val.delay); - } else { - sLoading.value = !!val; - } - }, - { - immediate: true, - }, - ); - onMounted(fixTwoCNChar); onUpdated(fixTwoCNChar); onBeforeUnmount(() => { - delayTimeout.value && clearTimeout(delayTimeout.value); + delayTimeoutRef.value && clearTimeout(delayTimeoutRef.value); }); return () => { - iconCom.value = getPropsSlot(slots, props, 'icon'); - children.value = flattenChildren(getPropsSlot(slots, props)); + const children = flattenChildren(getPropsSlot(slots, props)); + + const icon = getPropsSlot(slots, props, 'icon'); + + isNeedInserted = children.length === 1 && !icon && !isUnborderedButtonType(props.type); const { type, htmlType, disabled, href, title, target } = props; - const classes = getClasses(); + const iconType = innerLoading.value ? 'loading' : icon; const buttonProps = { ...attrs, title, disabled, - class: classes, + class: [ + classes.value, + attrs.class, + { [`${prefixCls.value}-icon-only`]: children.length === 0 && !!iconType }, + ], onClick: handleClick, }; - const iconNode = sLoading.value ? : iconCom.value; - const kids = children.value.map((child) => - insertSpace(child, isNeedInserted() && autoInsertSpace.value), + const iconNode = innerLoading.value ? ( + + + + ) : ( + icon + ); + + const kids = children.map((child) => + insertSpace(child, isNeedInserted && autoInsertSpace.value), ); if (href !== undefined) { diff --git a/components/button/buttonTypes.ts b/components/button/buttonTypes.ts index e71a0b696..87b1f498d 100644 --- a/components/button/buttonTypes.ts +++ b/components/button/buttonTypes.ts @@ -6,7 +6,7 @@ import type { SizeType } from '../config-provider'; const ButtonTypes = tuple('default', 'primary', 'ghost', 'dashed', 'link', 'text'); export type ButtonType = typeof ButtonTypes[number]; -const ButtonShapes = tuple('circle', 'circle-outline', 'round'); +const ButtonShapes = tuple('circle', 'round'); export type ButtonShape = typeof ButtonShapes[number]; const ButtonHTMLTypes = tuple('submit', 'button', 'reset'); diff --git a/components/button/index.ts b/components/button/index.ts index 4b9364911..b5136a9e2 100644 --- a/components/button/index.ts +++ b/components/button/index.ts @@ -17,6 +17,8 @@ Button.install = function (app: App) { return app; }; +export { ButtonGroup }; + export default Button as typeof Button & Plugin & { readonly Group: typeof ButtonGroup; diff --git a/components/button/style/index.less b/components/button/style/index.less index 97fbeb2ed..2ea7e01a3 100644 --- a/components/button/style/index.less +++ b/components/button/style/index.less @@ -155,38 +155,24 @@ } } - &&-loading:not(&-circle):not(&-circle-outline):not(&-icon-only) { - padding-left: 29px; - .@{iconfont-css-prefix}:not(:last-child) { - margin-left: -14px; - } - } + & > &-loading-icon { + transition: all 0.3s @ease-in-out; - &-sm&-loading:not(&-circle):not(&-circle-outline):not(&-icon-only) { - padding-left: 24px; .@{iconfont-css-prefix} { - margin-left: -17px; + padding-right: @padding-xs; + animation: none; + // for smooth button padding transition + svg { + animation: loadingCircle 1s infinite linear; + } } - } - // & > &-loading-icon { - // transition: all 0.3s @ease-in-out; - - // .@{iconfont-css-prefix} { - // padding-right: @padding-xs; - // animation: none; - // // for smooth button padding transition - // svg { - // animation: loadingCircle 1s infinite linear; - // } - // } - - // &:only-child { - // .@{iconfont-css-prefix} { - // padding-right: 0; - // } - // } - // } + &:only-child { + .@{iconfont-css-prefix} { + padding-right: 0; + } + } + } &-group { .btn-group(@btn-prefix-cls); diff --git a/components/modal/Modal.tsx b/components/modal/Modal.tsx index c0995dac0..66d06a74a 100644 --- a/components/modal/Modal.tsx +++ b/components/modal/Modal.tsx @@ -136,7 +136,9 @@ export interface ModalFuncProps { type getContainerFunc = () => HTMLElement; -export type ModalFunc = (props: ModalFuncProps) => { +export type ModalFunc = ( + props: ModalFuncProps, +) => { destroy: () => void; update: (newConfig: ModalFuncProps) => void; }; diff --git a/components/popconfirm/index.tsx b/components/popconfirm/index.tsx index 5521f9038..f4d739f3c 100644 --- a/components/popconfirm/index.tsx +++ b/components/popconfirm/index.tsx @@ -139,7 +139,7 @@ const Popconfirm = defineComponent({ this.renderOverlay(prefixCls, popconfirmLocale)} + children={popconfirmLocale => this.renderOverlay(prefixCls, popconfirmLocale)} /> ); const tooltipProps = { diff --git a/v2-doc b/v2-doc index 4c2982755..b6ab0fec2 160000 --- a/v2-doc +++ b/v2-doc @@ -1 +1 @@ -Subproject commit 4c298275518d5790a58d26f2ed9b83ee5ba1dba4 +Subproject commit b6ab0fec2cfa378bab8dfe6c8ef6b6a8664b970e