Skip to content

Commit 1d01df4

Browse files
committed
refactor: tooltip
1 parent 337d958 commit 1d01df4

File tree

14 files changed

+291
-360
lines changed

14 files changed

+291
-360
lines changed
File renamed without changes.

components/dropdown/dropdown.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import RightOutlined from '@ant-design/icons-vue/RightOutlined';
1010
import useConfigInject from '../config-provider/hooks/useConfigInject';
1111
import devWarning from '../vc-util/devWarning';
1212
import omit from '../_util/omit';
13-
import getPlacements from '../tooltip/placements';
13+
import getPlacements from '../_util/placements';
1414

1515
export type DropdownProps = Partial<ExtractPropTypes<ReturnType<typeof dropdownProps>>>;
1616

components/style.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import './pagination/style';
1010
// import './badge/style';
1111
import './tabs/style';
1212
import './input/style';
13-
import './tooltip/style';
13+
// import './tooltip/style';
1414
import './popover/style';
1515
import './popconfirm/style';
1616
// import './menu/style';

components/theme/interface/components.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ import type { ComponentToken as NotificationComponentToken } from '../../notific
4141
// import type { ComponentToken as TabsComponentToken } from '../../tabs/style';
4242
// import type { ComponentToken as TagComponentToken } from '../../tag/style';
4343
// import type { ComponentToken as TimelineComponentToken } from '../../timeline/style';
44-
// import type { ComponentToken as TooltipComponentToken } from '../../tooltip/style';
44+
import type { ComponentToken as TooltipComponentToken } from '../../tooltip/style';
4545
// import type { ComponentToken as TransferComponentToken } from '../../transfer/style';
4646
// import type { ComponentToken as TypographyComponentToken } from '../../typography/style';
4747
// import type { ComponentToken as UploadComponentToken } from '../../upload/style';
@@ -106,7 +106,7 @@ export interface ComponentTokenMap {
106106
Modal?: ModalComponentToken;
107107
Message?: MessageComponentToken;
108108
// Upload?: UploadComponentToken;
109-
// Tooltip?: TooltipComponentToken;
109+
Tooltip?: TooltipComponentToken;
110110
// Table?: TableComponentToken;
111111
// Space?: SpaceComponentToken;
112112
// Progress?: ProgressComponentToken;

components/tooltip/Tooltip.tsx

+81-61
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,26 @@
1-
import type { ExtractPropTypes } from 'vue';
2-
import { computed, watch, defineComponent, onMounted, ref } from 'vue';
1+
import type { CSSProperties, ExtractPropTypes } from 'vue';
2+
import { computed, watch, defineComponent, ref } from 'vue';
33
import VcTooltip from '../vc-tooltip';
44
import classNames from '../_util/classNames';
55
import PropTypes from '../_util/vue-types';
66
import warning from '../_util/warning';
7-
import { getStyle, filterEmpty, isValidElement, initDefaultProps } from '../_util/props-util';
7+
import {
8+
getStyle,
9+
filterEmpty,
10+
isValidElement,
11+
initDefaultProps,
12+
isFragment,
13+
} from '../_util/props-util';
814
import { cloneElement } from '../_util/vnode';
915
export type { TriggerType, TooltipPlacement } from './abstractTooltipProps';
1016
import abstractTooltipProps from './abstractTooltipProps';
1117
import useConfigInject from '../config-provider/hooks/useConfigInject';
12-
import getPlacements from './placements';
18+
import getPlacements from '../_util/placements';
1319
import firstNotUndefined from '../_util/firstNotUndefined';
1420
import raf from '../_util/raf';
1521
import { parseColor } from './util';
16-
export type { AdjustOverflow, PlacementsConfig } from './placements';
22+
export type { AdjustOverflow, PlacementsConfig } from '../_util/placements';
23+
import useStyle from './style';
1724

1825
// https://github.com/react-component/tooltip
1926
// https://github.com/yiminghe/dom-align
@@ -26,10 +33,12 @@ export interface TooltipAlignConfig {
2633
useCssBottom?: boolean;
2734
useCssTransform?: boolean;
2835
}
29-
30-
const splitObject = (obj: any, keys: string[]) => {
31-
const picked = {};
32-
const omitted = { ...obj };
36+
const splitObject = <T extends CSSProperties>(
37+
obj: T,
38+
keys: (keyof T)[],
39+
): Record<'picked' | 'omitted', T> => {
40+
const picked: T = {} as T;
41+
const omitted: T = { ...obj };
3342
keys.forEach(key => {
3443
if (obj && key in obj) {
3544
picked[key] = obj[key];
@@ -74,50 +83,55 @@ export default defineComponent({
7483
slots: ['title'],
7584
// emits: ['update:visible', 'visibleChange'],
7685
setup(props, { slots, emit, attrs, expose }) {
77-
const { prefixCls, getPopupContainer, direction } = useConfigInject('tooltip', props);
86+
if (process.env.NODE_ENV !== 'production') {
87+
[
88+
['visible', 'open'],
89+
['onVisibleChange', 'onOpenChange'],
90+
].forEach(([deprecatedName, newName]) => {
91+
warning(
92+
props[deprecatedName] === undefined,
93+
'Tooltip',
94+
`\`${deprecatedName}\` is deprecated, please use \`${newName}\` instead.`,
95+
);
96+
});
97+
}
7898

79-
const visible = ref(firstNotUndefined([props.visible, props.defaultVisible]));
99+
const { prefixCls, getPopupContainer, direction } = useConfigInject('tooltip', props);
100+
const mergedOpen = computed(() => props.open ?? props.visible);
101+
const innerOpen = ref(firstNotUndefined([props.open, props.visible]));
80102

81103
const tooltip = ref();
82104

83-
onMounted(() => {
84-
warning(
85-
props.defaultVisible === undefined,
86-
'Tooltip',
87-
`'defaultVisible' is deprecated, please use 'v-model:visible'`,
88-
);
89-
});
90105
let rafId: any;
91-
watch(
92-
() => props.visible,
93-
val => {
94-
raf.cancel(rafId);
95-
rafId = raf(() => {
96-
visible.value = !!val;
97-
});
98-
},
99-
);
106+
watch(mergedOpen, val => {
107+
raf.cancel(rafId);
108+
rafId = raf(() => {
109+
innerOpen.value = !!val;
110+
});
111+
});
100112
const isNoTitle = () => {
101113
const title = props.title ?? slots.title;
102114
return !title && title !== 0;
103115
};
104116

105117
const handleVisibleChange = (val: boolean) => {
106118
const noTitle = isNoTitle();
107-
if (props.visible === undefined) {
108-
visible.value = noTitle ? false : val;
119+
if (mergedOpen.value === undefined) {
120+
innerOpen.value = noTitle ? false : val;
109121
}
110122
if (!noTitle) {
111123
emit('update:visible', val);
112124
emit('visibleChange', val);
125+
emit('update:open', val);
126+
emit('openChange', val);
113127
}
114128
};
115129

116130
const getPopupDomNode = () => {
117131
return tooltip.value.getPopupDomNode();
118132
};
119133

120-
expose({ getPopupDomNode, visible, forcePopupAlign: () => tooltip.value?.forcePopupAlign() });
134+
expose({ getPopupDomNode, open, forcePopupAlign: () => tooltip.value?.forcePopupAlign() });
121135

122136
const tooltipPlacements = computed(() => {
123137
const { builtinPlacements, arrowPointAtCenter, autoAdjustOverflow } = props;
@@ -139,7 +153,8 @@ export default defineComponent({
139153
((elementType.__ANT_BUTTON === true || elementType === 'button') &&
140154
isTrueProps(ele.props.disabled)) ||
141155
(elementType.__ANT_SWITCH === true &&
142-
(isTrueProps(ele.props.disabled) || isTrueProps(ele.props.loading)))
156+
(isTrueProps(ele.props.disabled) || isTrueProps(ele.props.loading))) ||
157+
(elementType.__ANT_RADIO === true && isTrueProps(ele.props.disabled))
143158
) {
144159
// Pick some layout related style properties up to span
145160
// Prevent layout bugs like https://github.com/ant-design/ant-design/issues/5254
@@ -153,14 +168,14 @@ export default defineComponent({
153168
'display',
154169
'zIndex',
155170
]);
156-
const spanStyle = {
171+
const spanStyle: CSSProperties = {
157172
display: 'inline-block', // default inline-block is important
158173
...picked,
159174
cursor: 'not-allowed',
160175
lineHeight: 1, // use the true height of child nodes
161-
width: ele.props && ele.props.block ? '100%' : null,
176+
width: ele.props && ele.props.block ? '100%' : undefined,
162177
};
163-
const buttonStyle = {
178+
const buttonStyle: CSSProperties = {
164179
...omitted,
165180
pointerEvents: 'none',
166181
};
@@ -190,46 +205,50 @@ export default defineComponent({
190205
// 当前返回的位置
191206
const placement = Object.keys(placements).find(
192207
key =>
193-
placements[key].points[0] === align.points[0] &&
194-
placements[key].points[1] === align.points[1],
208+
placements[key].points[0] === align.points?.[0] &&
209+
placements[key].points[1] === align.points?.[1],
195210
);
196-
if (!placement) {
197-
return;
198-
}
199-
// 根据当前坐标设置动画点
200-
const rect = domNode.getBoundingClientRect();
201-
const transformOrigin = {
202-
top: '50%',
203-
left: '50%',
204-
};
205-
if (placement.indexOf('top') >= 0 || placement.indexOf('Bottom') >= 0) {
206-
transformOrigin.top = `${rect.height - align.offset[1]}px`;
207-
} else if (placement.indexOf('Top') >= 0 || placement.indexOf('bottom') >= 0) {
208-
transformOrigin.top = `${-align.offset[1]}px`;
209-
}
210-
if (placement.indexOf('left') >= 0 || placement.indexOf('Right') >= 0) {
211-
transformOrigin.left = `${rect.width - align.offset[0]}px`;
212-
} else if (placement.indexOf('right') >= 0 || placement.indexOf('Left') >= 0) {
213-
transformOrigin.left = `${-align.offset[0]}px`;
211+
if (placement) {
212+
// 根据当前坐标设置动画点
213+
const rect = domNode.getBoundingClientRect();
214+
const transformOrigin = {
215+
top: '50%',
216+
left: '50%',
217+
};
218+
if (placement.indexOf('top') >= 0 || placement.indexOf('Bottom') >= 0) {
219+
transformOrigin.top = `${rect.height - align.offset[1]}px`;
220+
} else if (placement.indexOf('Top') >= 0 || placement.indexOf('bottom') >= 0) {
221+
transformOrigin.top = `${-align.offset[1]}px`;
222+
}
223+
if (placement.indexOf('left') >= 0 || placement.indexOf('Right') >= 0) {
224+
transformOrigin.left = `${rect.width - align.offset[0]}px`;
225+
} else if (placement.indexOf('right') >= 0 || placement.indexOf('Left') >= 0) {
226+
transformOrigin.left = `${-align.offset[0]}px`;
227+
}
228+
domNode.style.transformOrigin = `${transformOrigin.left} ${transformOrigin.top}`;
214229
}
215-
domNode.style.transformOrigin = `${transformOrigin.left} ${transformOrigin.top}`;
216230
};
217231
const colorInfo = computed(() => parseColor(prefixCls.value, props.color));
232+
const injectFromPopover = computed(() => (attrs as any)['data-popover-inject']);
233+
const [wrapSSR, hashId] = useStyle(
234+
prefixCls,
235+
computed(() => !injectFromPopover.value),
236+
);
218237
return () => {
219238
const { openClassName, overlayClassName } = props;
220239
let children = filterEmpty(slots.default?.()) ?? null;
221240
children = children.length === 1 ? children[0] : children;
222241

223-
let tempVisible = visible.value;
242+
let tempVisible = innerOpen.value;
224243
// Hide tooltip when there is no title
225-
if (props.visible === undefined && isNoTitle()) {
244+
if (mergedOpen.value === undefined && isNoTitle()) {
226245
tempVisible = false;
227246
}
228247
if (!children) {
229248
return null;
230249
}
231250
const child = getDisabledCompatibleChildren(
232-
isValidElement(children) ? children : <span>{children}</span>,
251+
isValidElement(children) && !isFragment(children) ? children : <span>{children}</span>,
233252
);
234253
const childCls = classNames({
235254
[openClassName || `${prefixCls.value}-open`]: true,
@@ -242,6 +261,7 @@ export default defineComponent({
242261
},
243262

244263
colorInfo.value.className,
264+
hashId.value,
245265
);
246266
const formattedOverlayInnerStyle = {
247267
...props.overlayInnerStyle,
@@ -261,7 +281,7 @@ export default defineComponent({
261281
onVisibleChange: handleVisibleChange,
262282
onPopupAlign,
263283
};
264-
return (
284+
return wrapSSR(
265285
<VcTooltip
266286
{...vcTooltipProps}
267287
v-slots={{
@@ -271,8 +291,8 @@ export default defineComponent({
271291
overlay: getOverlay,
272292
}}
273293
>
274-
{visible.value ? cloneElement(child, { class: childCls }) : child}
275-
</VcTooltip>
294+
{innerOpen.value ? cloneElement(child, { class: childCls }) : child}
295+
</VcTooltip>,
276296
);
277297
};
278298
},
+12-15
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import type { CSSProperties, PropType } from 'vue';
22
import type { AlignType, BuildInPlacements } from '../vc-trigger/interface';
3-
import type { AdjustOverflow } from './placements';
3+
import type { AdjustOverflow } from '../_util/placements';
44
export type TriggerType = 'hover' | 'focus' | 'click' | 'contextmenu';
55
import type { PresetColorType } from '../_util/colors';
66
import type { LiteralUnion } from '../_util/type';
7+
import { objectType } from '../_util/type';
78
export type TooltipPlacement =
89
| 'top'
910
| 'left'
@@ -20,16 +21,14 @@ export type TooltipPlacement =
2021

2122
export default () => ({
2223
trigger: [String, Array] as PropType<TriggerType | TriggerType[]>,
24+
open: { type: Boolean, default: undefined },
25+
/** @deprecated Please use `open` instead. */
2326
visible: { type: Boolean, default: undefined },
24-
defaultVisible: { type: Boolean, default: undefined },
2527
placement: String as PropType<TooltipPlacement>,
2628
color: String as PropType<LiteralUnion<PresetColorType>>,
2729
transitionName: String,
28-
overlayStyle: { type: Object as PropType<CSSProperties>, default: undefined as CSSProperties },
29-
overlayInnerStyle: {
30-
type: Object as PropType<CSSProperties>,
31-
default: undefined as CSSProperties,
32-
},
30+
overlayStyle: objectType<CSSProperties>(),
31+
overlayInnerStyle: objectType<CSSProperties>(),
3332
overlayClassName: String,
3433
openClassName: String,
3534
prefixCls: String,
@@ -42,15 +41,13 @@ export default () => ({
4241
default: undefined as boolean | AdjustOverflow,
4342
},
4443
destroyTooltipOnHide: { type: Boolean, default: undefined },
45-
align: {
46-
type: Object as PropType<AlignType>,
47-
default: undefined as AlignType,
48-
},
49-
builtinPlacements: {
50-
type: Object as PropType<BuildInPlacements>,
51-
default: undefined as BuildInPlacements,
52-
},
44+
align: objectType<AlignType>(),
45+
builtinPlacements: objectType<BuildInPlacements>(),
5346
children: Array,
47+
/** @deprecated Please use `onOpenChange` instead. */
5448
onVisibleChange: Function as PropType<(vis: boolean) => void>,
49+
/** @deprecated Please use `onUpdate:open` instead. */
5550
'onUpdate:visible': Function as PropType<(vis: boolean) => void>,
51+
onOpenChange: Function as PropType<(vis: boolean) => void>,
52+
'onUpdate:open': Function as PropType<(vis: boolean) => void>,
5653
});

components/tooltip/demo/color.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ export default defineComponent({
6262
</script>
6363

6464
<style scoped>
65-
#components-a-tooltip-demo-color .ant-btn {
65+
:deep(#components-a-tooltip-demo-color) .ant-btn {
6666
margin-right: 8px;
6767
margin-bottom: 8px;
6868
}

components/tooltip/demo/placement.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ export default defineComponent({
111111
});
112112
</script>
113113
<style scoped>
114-
#components-a-tooltip-demo-placement .ant-btn {
114+
:deep(#components-a-tooltip-demo-placement) .ant-btn {
115115
width: 70px;
116116
text-align: center;
117117
padding: 0;

0 commit comments

Comments
 (0)