Skip to content

Commit 9ae2517

Browse files
authored
refactor(tooltip): use composition api (#4059)
* refactor(tooltip): use composition api * chore: useConfigInject * fix: remove useless
1 parent d9e80c3 commit 9ae2517

File tree

12 files changed

+292
-285
lines changed

12 files changed

+292
-285
lines changed

components/slider/__tests__/index.test.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ describe('Slider', () => {
1919
await asyncExpect(() => {
2020
expect(document.body.innerHTML).toMatchSnapshot();
2121
wrapper.findAll('.ant-slider-handle')[0].trigger('mouseleave');
22-
}, 0);
22+
}, 100);
2323
await asyncExpect(() => {
2424
expect(document.body.innerHTML).toMatchSnapshot();
25-
}, 0);
25+
}, 100);
2626
});
2727
});

components/tooltip/Tooltip.tsx

+105-118
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,19 @@
1-
import { defineComponent, ExtractPropTypes, inject, CSSProperties } from 'vue';
1+
import { defineComponent, ExtractPropTypes, CSSProperties, onMounted, ref } from 'vue';
22
import VcTooltip from '../vc-tooltip';
33
import classNames from '../_util/classNames';
44
import getPlacements from './placements';
55
import PropTypes from '../_util/vue-types';
66
import { PresetColorTypes } from '../_util/colors';
7-
import {
8-
hasProp,
9-
getComponent,
10-
getStyle,
11-
filterEmpty,
12-
getSlot,
13-
isValidElement,
14-
} from '../_util/props-util';
7+
import warning from '../_util/warning';
8+
import { getPropsSlot, getStyle, filterEmpty, isValidElement } from '../_util/props-util';
159
import { cloneElement } from '../_util/vnode';
16-
import { defaultConfigProvider } from '../config-provider';
17-
import abstractTooltipProps from './abstractTooltipProps';
10+
import abstractTooltipProps, { triggerTypes, placementTypes } from './abstractTooltipProps';
11+
import useConfigInject from '../_util/hooks/useConfigInject';
1812

1913
const splitObject = (obj: any, keys: string[]) => {
2014
const picked = {};
2115
const omitted = { ...obj };
22-
keys.forEach(key => {
16+
keys.forEach((key) => {
2317
if (obj && key in obj) {
2418
picked[key] = obj[key];
2519
delete omitted[key];
@@ -36,59 +30,67 @@ const tooltipProps = {
3630
title: PropTypes.VNodeChild,
3731
};
3832

33+
export type TriggerTypes = typeof triggerTypes[number];
34+
35+
export type PlacementTypes = typeof placementTypes[number];
36+
3937
export type TooltipProps = Partial<ExtractPropTypes<typeof tooltipProps>>;
4038

4139
export default defineComponent({
4240
name: 'ATooltip',
4341
inheritAttrs: false,
4442
props: tooltipProps,
4543
emits: ['update:visible', 'visibleChange'],
46-
setup() {
47-
return {
48-
configProvider: inject('configProvider', defaultConfigProvider),
44+
setup(props, { slots, emit, attrs, expose }) {
45+
const { prefixCls, getTargetContainer } = useConfigInject('tooltip', props);
46+
47+
const visible = ref(props.visible);
48+
49+
const tooltip = ref();
50+
51+
onMounted(() => {
52+
warning(
53+
!('default-visible' in attrs) || !('defaultVisible' in attrs),
54+
'Tooltip',
55+
`'defaultVisible' is deprecated, please use 'v-model:visible'`,
56+
);
57+
});
58+
59+
const handleVisibleChange = (bool: boolean) => {
60+
visible.value = isNoTitle() ? false : bool;
61+
if (!isNoTitle()) {
62+
emit('update:visible', bool);
63+
emit('visibleChange', bool);
64+
}
4965
};
50-
},
51-
data() {
52-
return {
53-
sVisible: !!this.$props.visible || !!this.$props.defaultVisible,
66+
67+
const isNoTitle = () => {
68+
const title = getPropsSlot(slots, props, 'title');
69+
return !title && title !== 0;
70+
};
71+
72+
const getPopupDomNode = () => {
73+
return tooltip.value.getPopupDomNode();
74+
};
75+
76+
const getVisible = () => {
77+
return !!visible.value;
5478
};
55-
},
56-
watch: {
57-
visible(val) {
58-
this.sVisible = val;
59-
},
60-
},
61-
methods: {
62-
handleVisibleChange(visible: boolean) {
63-
if (!hasProp(this, 'visible')) {
64-
this.sVisible = this.isNoTitle() ? false : visible;
65-
}
66-
if (!this.isNoTitle()) {
67-
this.$emit('update:visible', visible);
68-
this.$emit('visibleChange', visible);
69-
}
70-
},
7179

72-
getPopupDomNode() {
73-
return (this.$refs.tooltip as any).getPopupDomNode();
74-
},
80+
expose({ getPopupDomNode, getVisible });
7581

76-
getPlacements() {
77-
const { builtinPlacements, arrowPointAtCenter, autoAdjustOverflow } = this.$props;
82+
const getTooltipPlacements = () => {
83+
const { builtinPlacements, arrowPointAtCenter, autoAdjustOverflow } = props;
7884
return (
7985
builtinPlacements ||
8086
getPlacements({
8187
arrowPointAtCenter,
82-
verticalArrowShift: 8,
8388
autoAdjustOverflow,
8489
})
8590
);
86-
},
91+
};
8792

88-
// Fix Tooltip won't hide at disabled button
89-
// mouse events don't trigger at disabled button in Chrome
90-
// https://github.com/react-component/tooltip/issues/18
91-
getDisabledCompatibleChildren(ele: any) {
93+
const getDisabledCompatibleChildren = (ele: any) => {
9294
if (
9395
((typeof ele.type === 'object' &&
9496
(ele.type.__ANT_BUTTON === true ||
@@ -130,27 +132,21 @@ export default defineComponent({
130132
return <span style={spanStyle}>{child}</span>;
131133
}
132134
return ele;
133-
},
134-
135-
isNoTitle() {
136-
const title = getComponent(this, 'title');
137-
return !title && title !== 0;
138-
},
135+
};
139136

140-
getOverlay() {
141-
const title = getComponent(this, 'title');
137+
const getOverlay = () => {
138+
const title = getPropsSlot(slots, props, 'title');
142139
if (title === 0) {
143140
return title;
144141
}
145142
return title || '';
146-
},
143+
};
147144

148-
// 动态设置动画点
149-
onPopupAlign(domNode: HTMLElement, align: any) {
150-
const placements = this.getPlacements();
145+
const onPopupAlign = (domNode: HTMLElement, align: any) => {
146+
const placements = getTooltipPlacements();
151147
// 当前返回的位置
152148
const placement = Object.keys(placements).filter(
153-
key =>
149+
(key) =>
154150
placements[key].points[0] === align.points[0] &&
155151
placements[key].points[1] === align.points[1],
156152
)[0];
@@ -174,67 +170,58 @@ export default defineComponent({
174170
transformOrigin.left = `${-align.offset[0]}px`;
175171
}
176172
domNode.style.transformOrigin = `${transformOrigin.left} ${transformOrigin.top}`;
177-
},
178-
},
173+
};
179174

180-
render() {
181-
const { $props, $data, $attrs } = this;
182-
const {
183-
prefixCls: customizePrefixCls,
184-
openClassName,
185-
getPopupContainer,
186-
color,
187-
overlayClassName,
188-
} = $props;
189-
const { getPopupContainer: getContextPopupContainer } = this.configProvider;
190-
const getPrefixCls = this.configProvider.getPrefixCls;
191-
const prefixCls = getPrefixCls('tooltip', customizePrefixCls);
192-
let children = this.children || filterEmpty(getSlot(this));
193-
children = children.length === 1 ? children[0] : children;
194-
let sVisible = $data.sVisible;
195-
// Hide tooltip when there is no title
196-
if (!hasProp(this, 'visible') && this.isNoTitle()) {
197-
sVisible = false;
198-
}
199-
if (!children) {
200-
return null;
201-
}
202-
const child = this.getDisabledCompatibleChildren(
203-
isValidElement(children) ? children : <span>{children}</span>,
204-
);
205-
const childCls = classNames({
206-
[openClassName || `${prefixCls}-open`]: sVisible,
207-
[child.props && child.props.class]: child.props && child.props.class,
208-
});
209-
const customOverlayClassName = classNames(overlayClassName, {
210-
[`${prefixCls}-${color}`]: color && PresetColorRegex.test(color),
211-
});
212-
let formattedOverlayInnerStyle: CSSProperties;
213-
let arrowContentStyle: CSSProperties;
214-
if (color && !PresetColorRegex.test(color)) {
215-
formattedOverlayInnerStyle = { backgroundColor: color };
216-
arrowContentStyle = { backgroundColor: color };
217-
}
175+
return () => {
176+
const { openClassName, getPopupContainer, color, overlayClassName } = props;
177+
let children = filterEmpty(slots.default?.()) ?? null;
178+
children = children.length === 1 ? children[0] : children;
179+
// Hide tooltip when there is no title
180+
if (!('visible' in props) && isNoTitle()) {
181+
visible.value = false;
182+
}
183+
if (!children) {
184+
return null;
185+
}
186+
const child = getDisabledCompatibleChildren(
187+
isValidElement(children) ? children : <span>{children}</span>,
188+
);
189+
const childCls = classNames({
190+
[openClassName || `${prefixCls.value}-open`]: visible.value,
191+
[child.props && child.props.class]: child.props && child.props.class,
192+
});
193+
const customOverlayClassName = classNames(overlayClassName, {
194+
[`${prefixCls.value}-${color}`]: color && PresetColorRegex.test(color),
195+
});
196+
let formattedOverlayInnerStyle: CSSProperties;
197+
let arrowContentStyle: CSSProperties;
198+
if (color && !PresetColorRegex.test(color)) {
199+
formattedOverlayInnerStyle = { backgroundColor: color };
200+
arrowContentStyle = { backgroundColor: color };
201+
}
218202

219-
const vcTooltipProps = {
220-
...$attrs,
221-
...$props,
222-
prefixCls,
223-
getTooltipContainer: getPopupContainer || getContextPopupContainer,
224-
builtinPlacements: this.getPlacements(),
225-
overlay: this.getOverlay(),
226-
visible: sVisible,
227-
ref: 'tooltip',
228-
overlayClassName: customOverlayClassName,
229-
overlayInnerStyle: formattedOverlayInnerStyle,
230-
arrowContent: <span class={`${prefixCls}-arrow-content`} style={arrowContentStyle}></span>,
231-
onVisibleChange: this.handleVisibleChange,
232-
onPopupAlign: this.onPopupAlign,
203+
const vcTooltipProps = {
204+
...attrs,
205+
...props,
206+
prefixCls: prefixCls.value,
207+
getTooltipContainer: getPopupContainer || getTargetContainer.value,
208+
builtinPlacements: getTooltipPlacements(),
209+
overlay: getOverlay(),
210+
visible: visible.value,
211+
ref: tooltip,
212+
overlayClassName: customOverlayClassName,
213+
overlayInnerStyle: formattedOverlayInnerStyle,
214+
arrowContent: (
215+
<span class={`${prefixCls.value}-arrow-content`} style={arrowContentStyle}></span>
216+
),
217+
onVisibleChange: handleVisibleChange,
218+
onPopupAlign,
219+
};
220+
return (
221+
<VcTooltip {...vcTooltipProps}>
222+
{visible.value ? cloneElement(child, { class: childCls }) : child}
223+
</VcTooltip>
224+
);
233225
};
234-
return (
235-
<VcTooltip {...vcTooltipProps}>
236-
{sVisible ? cloneElement(child, { class: childCls }) : child}
237-
</VcTooltip>
238-
);
239226
},
240227
});

components/tooltip/__tests__/tooltip.test.js

+7-7
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,14 @@ describe('Tooltip', () => {
4444
});
4545
await asyncExpect(() => {
4646
expect(onVisibleChange).not.toHaveBeenCalled();
47-
expect(wrapper.vm.$refs.tooltip.$refs.tooltip.visible).toBe(false);
47+
expect(wrapper.vm.$refs.tooltip.getVisible()).toBe(false);
4848
});
4949
await asyncExpect(() => {
5050
div.dispatchEvent(new MouseEvent('mouseleave'));
5151
});
5252
await asyncExpect(() => {
5353
expect(onVisibleChange).not.toHaveBeenCalled();
54-
expect(wrapper.vm.$refs.tooltip.$refs.tooltip.visible).toBe(false);
54+
expect(wrapper.vm.$refs.tooltip.getVisible()).toBe(false);
5555
});
5656
await asyncExpect(() => {
5757
// update `title` value.
@@ -62,14 +62,14 @@ describe('Tooltip', () => {
6262
});
6363
await asyncExpect(() => {
6464
expect(onVisibleChange).toHaveBeenLastCalledWith(true);
65-
expect(wrapper.vm.$refs.tooltip.$refs.tooltip.visible).toBe(true);
65+
expect(wrapper.vm.$refs.tooltip.getVisible()).toBe(true);
6666
}, 0);
6767
await asyncExpect(() => {
6868
wrapper.findAll('#hello')[0].element.dispatchEvent(new MouseEvent('mouseleave'));
6969
});
7070
await asyncExpect(() => {
7171
expect(onVisibleChange).toHaveBeenLastCalledWith(false);
72-
expect(wrapper.vm.$refs.tooltip.$refs.tooltip.visible).toBe(false);
72+
expect(wrapper.vm.$refs.tooltip.getVisible()).toBe(false);
7373
});
7474
await asyncExpect(() => {
7575
// add `visible` props.
@@ -80,16 +80,16 @@ describe('Tooltip', () => {
8080
});
8181
await asyncExpect(() => {
8282
expect(onVisibleChange).toHaveBeenLastCalledWith(true);
83-
lastCount = onVisibleChange.mock.calls.length;
84-
expect(wrapper.vm.$refs.tooltip.$refs.tooltip.visible).toBe(false);
83+
expect(wrapper.vm.$refs.tooltip.getVisible()).toBe(true);
8584
});
8685
await asyncExpect(() => {
8786
// always trigger onVisibleChange
8887
wrapper.findAll('#hello')[0].element.dispatchEvent(new MouseEvent('mouseleave'));
88+
lastCount = onVisibleChange.mock.calls.length;
8989
});
9090
await asyncExpect(() => {
9191
expect(onVisibleChange.mock.calls.length).toBe(lastCount); // no change with lastCount
92-
expect(wrapper.vm.$refs.tooltip.$refs.tooltip.visible).toBe(false);
92+
expect(wrapper.vm.$refs.tooltip.getVisible()).toBe(false);
9393
});
9494
});
9595
});

0 commit comments

Comments
 (0)