diff --git a/components/tooltip/placements.ts b/components/_util/placements.ts similarity index 100% rename from components/tooltip/placements.ts rename to components/_util/placements.ts diff --git a/components/dropdown/dropdown.tsx b/components/dropdown/dropdown.tsx index 19ef4ae07..38306e194 100644 --- a/components/dropdown/dropdown.tsx +++ b/components/dropdown/dropdown.tsx @@ -10,7 +10,7 @@ import RightOutlined from '@ant-design/icons-vue/RightOutlined'; import useConfigInject from '../config-provider/hooks/useConfigInject'; import devWarning from '../vc-util/devWarning'; import omit from '../_util/omit'; -import getPlacements from '../tooltip/placements'; +import getPlacements from '../_util/placements'; export type DropdownProps = Partial>>; diff --git a/components/style.ts b/components/style.ts index a39c73fbb..ca73d113f 100644 --- a/components/style.ts +++ b/components/style.ts @@ -10,7 +10,7 @@ import './pagination/style'; // import './badge/style'; import './tabs/style'; import './input/style'; -import './tooltip/style'; +// import './tooltip/style'; import './popover/style'; import './popconfirm/style'; // import './menu/style'; diff --git a/components/theme/interface/components.ts b/components/theme/interface/components.ts index 9db269bd3..f285f04db 100644 --- a/components/theme/interface/components.ts +++ b/components/theme/interface/components.ts @@ -41,7 +41,7 @@ import type { ComponentToken as NotificationComponentToken } from '../../notific // import type { ComponentToken as TabsComponentToken } from '../../tabs/style'; // import type { ComponentToken as TagComponentToken } from '../../tag/style'; // import type { ComponentToken as TimelineComponentToken } from '../../timeline/style'; -// import type { ComponentToken as TooltipComponentToken } from '../../tooltip/style'; +import type { ComponentToken as TooltipComponentToken } from '../../tooltip/style'; // import type { ComponentToken as TransferComponentToken } from '../../transfer/style'; // import type { ComponentToken as TypographyComponentToken } from '../../typography/style'; // import type { ComponentToken as UploadComponentToken } from '../../upload/style'; @@ -106,7 +106,7 @@ export interface ComponentTokenMap { Modal?: ModalComponentToken; Message?: MessageComponentToken; // Upload?: UploadComponentToken; - // Tooltip?: TooltipComponentToken; + Tooltip?: TooltipComponentToken; // Table?: TableComponentToken; // Space?: SpaceComponentToken; // Progress?: ProgressComponentToken; diff --git a/components/tooltip/Tooltip.tsx b/components/tooltip/Tooltip.tsx index bd6a207a9..8f4d0d40d 100644 --- a/components/tooltip/Tooltip.tsx +++ b/components/tooltip/Tooltip.tsx @@ -1,19 +1,26 @@ -import type { ExtractPropTypes } from 'vue'; -import { computed, watch, defineComponent, onMounted, ref } from 'vue'; +import type { CSSProperties, ExtractPropTypes } from 'vue'; +import { computed, watch, defineComponent, ref } from 'vue'; import VcTooltip from '../vc-tooltip'; import classNames from '../_util/classNames'; import PropTypes from '../_util/vue-types'; import warning from '../_util/warning'; -import { getStyle, filterEmpty, isValidElement, initDefaultProps } from '../_util/props-util'; +import { + getStyle, + filterEmpty, + isValidElement, + initDefaultProps, + isFragment, +} from '../_util/props-util'; import { cloneElement } from '../_util/vnode'; export type { TriggerType, TooltipPlacement } from './abstractTooltipProps'; import abstractTooltipProps from './abstractTooltipProps'; import useConfigInject from '../config-provider/hooks/useConfigInject'; -import getPlacements from './placements'; +import getPlacements from '../_util/placements'; import firstNotUndefined from '../_util/firstNotUndefined'; import raf from '../_util/raf'; import { parseColor } from './util'; -export type { AdjustOverflow, PlacementsConfig } from './placements'; +export type { AdjustOverflow, PlacementsConfig } from '../_util/placements'; +import useStyle from './style'; // https://github.com/react-component/tooltip // https://github.com/yiminghe/dom-align @@ -26,10 +33,12 @@ export interface TooltipAlignConfig { useCssBottom?: boolean; useCssTransform?: boolean; } - -const splitObject = (obj: any, keys: string[]) => { - const picked = {}; - const omitted = { ...obj }; +const splitObject = ( + obj: T, + keys: (keyof T)[], +): Record<'picked' | 'omitted', T> => { + const picked: T = {} as T; + const omitted: T = { ...obj }; keys.forEach(key => { if (obj && key in obj) { picked[key] = obj[key]; @@ -74,29 +83,32 @@ export default defineComponent({ slots: ['title'], // emits: ['update:visible', 'visibleChange'], setup(props, { slots, emit, attrs, expose }) { - const { prefixCls, getPopupContainer, direction } = useConfigInject('tooltip', props); + if (process.env.NODE_ENV !== 'production') { + [ + ['visible', 'open'], + ['onVisibleChange', 'onOpenChange'], + ].forEach(([deprecatedName, newName]) => { + warning( + props[deprecatedName] === undefined, + 'Tooltip', + `\`${deprecatedName}\` is deprecated, please use \`${newName}\` instead.`, + ); + }); + } - const visible = ref(firstNotUndefined([props.visible, props.defaultVisible])); + const { prefixCls, getPopupContainer, direction } = useConfigInject('tooltip', props); + const mergedOpen = computed(() => props.open ?? props.visible); + const innerOpen = ref(firstNotUndefined([props.open, props.visible])); const tooltip = ref(); - onMounted(() => { - warning( - props.defaultVisible === undefined, - 'Tooltip', - `'defaultVisible' is deprecated, please use 'v-model:visible'`, - ); - }); let rafId: any; - watch( - () => props.visible, - val => { - raf.cancel(rafId); - rafId = raf(() => { - visible.value = !!val; - }); - }, - ); + watch(mergedOpen, val => { + raf.cancel(rafId); + rafId = raf(() => { + innerOpen.value = !!val; + }); + }); const isNoTitle = () => { const title = props.title ?? slots.title; return !title && title !== 0; @@ -104,12 +116,14 @@ export default defineComponent({ const handleVisibleChange = (val: boolean) => { const noTitle = isNoTitle(); - if (props.visible === undefined) { - visible.value = noTitle ? false : val; + if (mergedOpen.value === undefined) { + innerOpen.value = noTitle ? false : val; } if (!noTitle) { emit('update:visible', val); emit('visibleChange', val); + emit('update:open', val); + emit('openChange', val); } }; @@ -117,7 +131,7 @@ export default defineComponent({ return tooltip.value.getPopupDomNode(); }; - expose({ getPopupDomNode, visible, forcePopupAlign: () => tooltip.value?.forcePopupAlign() }); + expose({ getPopupDomNode, open, forcePopupAlign: () => tooltip.value?.forcePopupAlign() }); const tooltipPlacements = computed(() => { const { builtinPlacements, arrowPointAtCenter, autoAdjustOverflow } = props; @@ -139,7 +153,8 @@ export default defineComponent({ ((elementType.__ANT_BUTTON === true || elementType === 'button') && isTrueProps(ele.props.disabled)) || (elementType.__ANT_SWITCH === true && - (isTrueProps(ele.props.disabled) || isTrueProps(ele.props.loading))) + (isTrueProps(ele.props.disabled) || isTrueProps(ele.props.loading))) || + (elementType.__ANT_RADIO === true && isTrueProps(ele.props.disabled)) ) { // Pick some layout related style properties up to span // Prevent layout bugs like https://github.com/ant-design/ant-design/issues/5254 @@ -153,14 +168,14 @@ export default defineComponent({ 'display', 'zIndex', ]); - const spanStyle = { + const spanStyle: CSSProperties = { display: 'inline-block', // default inline-block is important ...picked, cursor: 'not-allowed', lineHeight: 1, // use the true height of child nodes - width: ele.props && ele.props.block ? '100%' : null, + width: ele.props && ele.props.block ? '100%' : undefined, }; - const buttonStyle = { + const buttonStyle: CSSProperties = { ...omitted, pointerEvents: 'none', }; @@ -190,46 +205,50 @@ export default defineComponent({ // 当前返回的位置 const placement = Object.keys(placements).find( key => - placements[key].points[0] === align.points[0] && - placements[key].points[1] === align.points[1], + placements[key].points[0] === align.points?.[0] && + placements[key].points[1] === align.points?.[1], ); - if (!placement) { - return; + if (placement) { + // 根据当前坐标设置动画点 + const rect = domNode.getBoundingClientRect(); + const transformOrigin = { + top: '50%', + left: '50%', + }; + if (placement.indexOf('top') >= 0 || placement.indexOf('Bottom') >= 0) { + transformOrigin.top = `${rect.height - align.offset[1]}px`; + } else if (placement.indexOf('Top') >= 0 || placement.indexOf('bottom') >= 0) { + transformOrigin.top = `${-align.offset[1]}px`; + } + if (placement.indexOf('left') >= 0 || placement.indexOf('Right') >= 0) { + transformOrigin.left = `${rect.width - align.offset[0]}px`; + } else if (placement.indexOf('right') >= 0 || placement.indexOf('Left') >= 0) { + transformOrigin.left = `${-align.offset[0]}px`; + } + domNode.style.transformOrigin = `${transformOrigin.left} ${transformOrigin.top}`; } - // 根据当前坐标设置动画点 - const rect = domNode.getBoundingClientRect(); - const transformOrigin = { - top: '50%', - left: '50%', - }; - if (placement.indexOf('top') >= 0 || placement.indexOf('Bottom') >= 0) { - transformOrigin.top = `${rect.height - align.offset[1]}px`; - } else if (placement.indexOf('Top') >= 0 || placement.indexOf('bottom') >= 0) { - transformOrigin.top = `${-align.offset[1]}px`; - } - if (placement.indexOf('left') >= 0 || placement.indexOf('Right') >= 0) { - transformOrigin.left = `${rect.width - align.offset[0]}px`; - } else if (placement.indexOf('right') >= 0 || placement.indexOf('Left') >= 0) { - transformOrigin.left = `${-align.offset[0]}px`; - } - domNode.style.transformOrigin = `${transformOrigin.left} ${transformOrigin.top}`; }; const colorInfo = computed(() => parseColor(prefixCls.value, props.color)); + const injectFromPopover = computed(() => (attrs as any)['data-popover-inject']); + const [wrapSSR, hashId] = useStyle( + prefixCls, + computed(() => !injectFromPopover.value), + ); return () => { const { openClassName, overlayClassName } = props; let children = filterEmpty(slots.default?.()) ?? null; children = children.length === 1 ? children[0] : children; - let tempVisible = visible.value; + let tempVisible = innerOpen.value; // Hide tooltip when there is no title - if (props.visible === undefined && isNoTitle()) { + if (mergedOpen.value === undefined && isNoTitle()) { tempVisible = false; } if (!children) { return null; } const child = getDisabledCompatibleChildren( - isValidElement(children) ? children : {children}, + isValidElement(children) && !isFragment(children) ? children : {children}, ); const childCls = classNames({ [openClassName || `${prefixCls.value}-open`]: true, @@ -242,6 +261,7 @@ export default defineComponent({ }, colorInfo.value.className, + hashId.value, ); const formattedOverlayInnerStyle = { ...props.overlayInnerStyle, @@ -261,7 +281,7 @@ export default defineComponent({ onVisibleChange: handleVisibleChange, onPopupAlign, }; - return ( + return wrapSSR( - {visible.value ? cloneElement(child, { class: childCls }) : child} - + {innerOpen.value ? cloneElement(child, { class: childCls }) : child} + , ); }; }, diff --git a/components/tooltip/abstractTooltipProps.ts b/components/tooltip/abstractTooltipProps.ts index e3b096e1f..489829de4 100644 --- a/components/tooltip/abstractTooltipProps.ts +++ b/components/tooltip/abstractTooltipProps.ts @@ -1,9 +1,10 @@ import type { CSSProperties, PropType } from 'vue'; import type { AlignType, BuildInPlacements } from '../vc-trigger/interface'; -import type { AdjustOverflow } from './placements'; +import type { AdjustOverflow } from '../_util/placements'; export type TriggerType = 'hover' | 'focus' | 'click' | 'contextmenu'; import type { PresetColorType } from '../_util/colors'; import type { LiteralUnion } from '../_util/type'; +import { objectType } from '../_util/type'; export type TooltipPlacement = | 'top' | 'left' @@ -20,16 +21,14 @@ export type TooltipPlacement = export default () => ({ trigger: [String, Array] as PropType, + open: { type: Boolean, default: undefined }, + /** @deprecated Please use `open` instead. */ visible: { type: Boolean, default: undefined }, - defaultVisible: { type: Boolean, default: undefined }, placement: String as PropType, color: String as PropType>, transitionName: String, - overlayStyle: { type: Object as PropType, default: undefined as CSSProperties }, - overlayInnerStyle: { - type: Object as PropType, - default: undefined as CSSProperties, - }, + overlayStyle: objectType(), + overlayInnerStyle: objectType(), overlayClassName: String, openClassName: String, prefixCls: String, @@ -42,15 +41,13 @@ export default () => ({ default: undefined as boolean | AdjustOverflow, }, destroyTooltipOnHide: { type: Boolean, default: undefined }, - align: { - type: Object as PropType, - default: undefined as AlignType, - }, - builtinPlacements: { - type: Object as PropType, - default: undefined as BuildInPlacements, - }, + align: objectType(), + builtinPlacements: objectType(), children: Array, + /** @deprecated Please use `onOpenChange` instead. */ onVisibleChange: Function as PropType<(vis: boolean) => void>, + /** @deprecated Please use `onUpdate:open` instead. */ 'onUpdate:visible': Function as PropType<(vis: boolean) => void>, + onOpenChange: Function as PropType<(vis: boolean) => void>, + 'onUpdate:open': Function as PropType<(vis: boolean) => void>, }); diff --git a/components/tooltip/demo/color.vue b/components/tooltip/demo/color.vue index bf1e3b0e0..401f39277 100644 --- a/components/tooltip/demo/color.vue +++ b/components/tooltip/demo/color.vue @@ -62,7 +62,7 @@ export default defineComponent({