From 905bfd586f310e8851b2e13bf180ce48d4aeb0a2 Mon Sep 17 00:00:00 2001 From: tanjinzhou <415800467@qq.com> Date: Wed, 10 Jun 2020 18:20:57 +0800 Subject: [PATCH] feat: update tooltip --- components/tooltip/Tooltip.jsx | 45 +- components/vc-tooltip/Content.jsx | 1 + components/vc-tooltip/Tooltip.jsx | 10 +- components/vc-trigger/LazyRenderBox.jsx | 16 +- components/vc-trigger/Popup.jsx | 50 +- components/vc-trigger/Trigger.jsx | 96 ++-- components/vc-trigger/t.js | 654 ------------------------ examples/index.js | 2 + 8 files changed, 106 insertions(+), 768 deletions(-) delete mode 100644 components/vc-trigger/t.js diff --git a/components/tooltip/Tooltip.jsx b/components/tooltip/Tooltip.jsx index 4cc15af6b..ccf580e03 100644 --- a/components/tooltip/Tooltip.jsx +++ b/components/tooltip/Tooltip.jsx @@ -1,15 +1,16 @@ -import { inject, cloneVNode, isVNode } from 'vue'; +import { inject } from 'vue'; import VcTooltip from '../vc-tooltip'; import getPlacements from './placements'; import PropTypes from '../_util/vue-types'; import { hasProp, getComponent, - getClass, getStyle, filterEmpty, getSlot, + isValidElement, } from '../_util/props-util'; +import { cloneElement } from '../_util/vnode'; import { ConfigConsumerProps } from '../config-provider'; import abstractTooltipProps from './abstractTooltipProps'; @@ -27,6 +28,7 @@ const splitObject = (obj, keys) => { const props = abstractTooltipProps(); export default { name: 'ATooltip', + inheritAttrs: false, model: { prop: 'visible', event: 'visibleChange', @@ -80,18 +82,14 @@ export default { // mouse events don't trigger at disabled button in Chrome // https://github.com/react-component/tooltip/issues/18 getDisabledCompatibleChildren(ele) { - const options = (ele.componentOptions && ele.componentOptions.Ctor.options) || {}; - if ( - ((options.__ANT_BUTTON === true || - options.__ANT_SWITCH === true || - options.__ANT_CHECKBOX === true) && - (ele.componentOptions.propsData.disabled || - ele.componentOptions.propsData.disabled === '')) || - (ele.tag === 'button' && - ele.data && - ele.data.attrs && - ele.data.attrs.disabled !== undefined) + ((typeof ele.type === 'object' && + (ele.type.__ANT_BUTTON === true || + ele.type.__ANT_SWITCH === true || + ele.type.__ANT_CHECKBOX === true)) || + ele.type === 'button') && + ele.props && + (ele.props.disabled || 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 @@ -115,16 +113,14 @@ export default { ...omitted, pointerEvents: 'none', }; - const spanCls = getClass(ele); - const child = cloneVNode(ele, { - style: buttonStyle, - class: null, - }); - return ( - - {child} - + const child = cloneElement( + ele, + { + style: buttonStyle, + }, + true, ); + return {child}; } return ele; }, @@ -191,10 +187,11 @@ export default { return null; } const child = this.getDisabledCompatibleChildren( - isVNode(children) ? children : {children}, + isValidElement(children) ? children : {children}, ); const childCls = { [openClassName || `${prefixCls}-open`]: true, + [child.props && child.props.class]: child.props && child.props.class, }; const tooltipProps = { ...$attrs, @@ -210,7 +207,7 @@ export default { }; return ( - {sVisible ? cloneVNode(child, { class: childCls }) : child} + {sVisible ? cloneElement(child, { class: childCls }) : child} ); }, diff --git a/components/vc-tooltip/Content.jsx b/components/vc-tooltip/Content.jsx index ddcf3b8c4..2665d3555 100644 --- a/components/vc-tooltip/Content.jsx +++ b/components/vc-tooltip/Content.jsx @@ -1,6 +1,7 @@ import PropTypes from '../_util/vue-types'; export default { + name: 'Content', props: { prefixCls: PropTypes.string, overlay: PropTypes.any, diff --git a/components/vc-tooltip/Tooltip.jsx b/components/vc-tooltip/Tooltip.jsx index 5f28d4168..7dc609a75 100644 --- a/components/vc-tooltip/Tooltip.jsx +++ b/components/vc-tooltip/Tooltip.jsx @@ -5,6 +5,8 @@ import Content from './Content'; import { hasProp, getComponent, getOptionProps } from '../_util/props-util'; function noop() {} export default { + name: 'Tooltip', + inheritAttrs: false, props: { trigger: PropTypes.any.def(['hover']), defaultVisible: PropTypes.bool, @@ -91,12 +93,8 @@ export default { onPopupVisibleChange: $attrs.onVisibleChange || noop, onPopupAlign: $attrs.onPopupAlign || noop, ref: 'trigger', + popup: this.getPopupElement(), }; - return ( - - - {this.$slots.default && this.$slots.default()} - - ); + return {this.$slots.default && this.$slots.default()[0]}; }, }; diff --git a/components/vc-trigger/LazyRenderBox.jsx b/components/vc-trigger/LazyRenderBox.jsx index df7c1932d..36c936027 100644 --- a/components/vc-trigger/LazyRenderBox.jsx +++ b/components/vc-trigger/LazyRenderBox.jsx @@ -1,20 +1,22 @@ import PropTypes from '../_util/vue-types'; export default { + name: 'LazyRenderBox', + inheritAttrs: false, props: { visible: PropTypes.bool, hiddenClassName: PropTypes.string, }, render() { - const { hiddenClassName, visible } = this.$props; + const { hiddenClassName } = this.$props; const child = this.$slots.default && this.$slots.default(); if (hiddenClassName || (child && child.length > 1)) { - const cls = ''; - if (!visible && hiddenClassName) { - // cls += ` ${hiddenClassName}` - } - return
{child}
; + // const cls = ''; + // if (!visible && hiddenClassName) { + // // cls += ` ${hiddenClassName}` + // } + return
{child}
; } - return child; + return child && child[0]; }, }; diff --git a/components/vc-trigger/Popup.jsx b/components/vc-trigger/Popup.jsx index 7f7390d1d..32d04f94e 100644 --- a/components/vc-trigger/Popup.jsx +++ b/components/vc-trigger/Popup.jsx @@ -5,7 +5,7 @@ import PopupInner from './PopupInner'; import LazyRenderBox from './LazyRenderBox'; import animate from '../_util/css-animation'; import BaseMixin from '../_util/BaseMixin'; -import { getListeners } from '../_util/props-util'; +import { getListeners, splitAttrs } from '../_util/props-util'; export default { name: 'VCTriggerPopup', @@ -35,6 +35,7 @@ export default { }, data() { this.domEl = null; + this.currentAlignClassName = undefined; return { // Used for stretch stretchChecked: false, @@ -152,12 +153,15 @@ export default { }, getClassName(currentAlignClassName) { - return `${this.$props.prefixCls} ${this.$props.popupClassName} ${currentAlignClassName}`; + return `${this.$props.prefixCls} ${this.$attrs.class || ''} ${ + this.$props.popupClassName + } ${currentAlignClassName}`; }, getPopupElement() { - const { $props: props, $slots, getTransitionName } = this; + const { $props: props, $attrs, $slots, getTransitionName } = this; const { stretchChecked, targetHeight, targetWidth } = this.$data; - + const { style = {} } = $attrs; + const onEvents = splitAttrs($attrs).onEvents; const { align, visible, @@ -200,30 +204,26 @@ export default { } } const popupInnerProps = { - props: { - prefixCls, - visible, - // hiddenClassName, - }, + prefixCls, + visible, + // hiddenClassName, class: className, - on: getListeners(this), + ...onEvents, ref: 'popupInstance', - style: { ...sizeStyle, ...popupStyle, ...this.getZIndexStyle() }, + style: { ...sizeStyle, ...popupStyle, ...style, ...this.getZIndexStyle() }, }; let transitionProps = { - props: { - appear: true, - css: false, - }, + appear: true, + css: false, }; const transitionName = getTransitionName(); let useTransition = !!transitionName; const transitionEvent = { - beforeEnter: () => { + onBeforeEnter: () => { // el.style.display = el.__vOriginalDisplay // this.$refs.alignInstance.forceAlign(); }, - enter: (el, done) => { + onEnter: (el, done) => { // render 后 vue 会移除通过animate动态添加的 class导致动画闪动,延迟两帧添加动画class,可以进一步定位或者重写 transition 组件 this.$nextTick(() => { if (this.$refs.alignInstance) { @@ -236,21 +236,17 @@ export default { } }); }, - beforeLeave: () => { + onBeforeLeave: () => { this.domEl = null; }, - leave: (el, done) => { + onLeave: (el, done) => { animate(el, `${transitionName}-leave`, done); }, }; - + transitionProps = { ...transitionProps, ...transitionEvent }; if (typeof animation === 'object') { useTransition = true; - const { on = {}, props = {} } = animation; - transitionProps.props = { ...transitionProps.props, ...props }; - transitionProps.on = { ...transitionEvent, ...on }; - } else { - transitionProps.on = transitionEvent; + transitionProps = { ...transitionProps, ...animation }; } if (!useTransition) { transitionProps = {}; @@ -267,7 +263,7 @@ export default { align={align} onAlign={this.onAlign} > - {$slots.default} + {$slots.default && $slots.default()} ) : null} @@ -285,7 +281,7 @@ export default { align={align} onAlign={this.onAlign} > - {$slots.default} + {$slots.default && $slots.default()} ); diff --git a/components/vc-trigger/Trigger.jsx b/components/vc-trigger/Trigger.jsx index 3c959fad1..d052ee767 100644 --- a/components/vc-trigger/Trigger.jsx +++ b/components/vc-trigger/Trigger.jsx @@ -1,15 +1,8 @@ -import { cloneVNode, inject, provide } from 'vue'; -import ref from 'vue-ref'; +import { inject, provide } from 'vue'; +import antRef from '../_util/ant-ref'; import PropTypes from '../_util/vue-types'; import contains from '../vc-util/Dom/contains'; -import { - hasProp, - getComponentFromProp, - getEvents, - filterEmpty, - getSlot, - getListeners, -} from '../_util/props-util'; +import { hasProp, getComponent, getEvents, filterEmpty, getSlot } from '../_util/props-util'; import { requestAnimationTimeout, cancelAnimationTimeout } from '../_util/requestAnimationTimeout'; import addEventListener from '../vc-util/Dom/addEventListener'; import warning from '../_util/warning'; @@ -17,6 +10,9 @@ import Popup from './Popup'; import { getAlignFromPlacement, getAlignPopupClassName, noop } from './utils'; import BaseMixin from '../_util/BaseMixin'; import Portal from '../_util/Portal'; +import classNames from 'classnames'; +import { cloneElement } from '../_util/vnode'; +import createRefHooks from '../_util/createRefHooks'; function returnEmptyString() { return ''; @@ -38,7 +34,7 @@ const ALL_HANDLERS = [ export default { name: 'Trigger', - directives: { 'ant-ref': ref }, + directives: { 'ant-ref': antRef }, mixins: [BaseMixin], inheritAttrs: false, props: { @@ -89,6 +85,7 @@ export default { this.fireEvents(h, e); }; }); + this._component = null; return { prevPopupVisible: popupVisible, sPopupVisible: popupVisible, @@ -328,8 +325,7 @@ export default { }, getRootDomNode() { - return this.$el; - // return this.$el.children[0] || this.$el + return this.$refs.trigger.$el || this.$refs.trigger; }, handleGetPopupClassFromAlign(align) { @@ -367,14 +363,14 @@ export default { const self = this; const mouseProps = {}; if (this.isMouseEnterToShow()) { - mouseProps.mouseenter = self.onPopupMouseenter; + mouseProps.onMouseenter = self.onPopupMouseenter; } if (this.isMouseLeaveToHide()) { - mouseProps.mouseleave = self.onPopupMouseleave; + mouseProps.onMouseleave = self.onPopupMouseleave; } - mouseProps.mousedown = this.onPopupMouseDown; - mouseProps.touchstart = this.onPopupMouseDown; - const { handleGetPopupClassFromAlign, getRootDomNode, getContainer } = self; + mouseProps.onMousedown = this.onPopupMouseDown; + mouseProps.onTouchstart = this.onPopupMouseDown; + const { handleGetPopupClassFromAlign, getRootDomNode, getContainer, $attrs } = self; const { prefixCls, destroyPopupOnHide, @@ -393,34 +389,31 @@ export default { const { sPopupVisible, point } = this.$data; const align = this.getPopupAlign(); const popupProps = { - props: { - prefixCls, - destroyPopupOnHide, - visible: sPopupVisible, - point: alignPoint && point, - action, - align, - animation: popupAnimation, - getClassNameFromAlign: handleGetPopupClassFromAlign, - stretch, - getRootDomNode, - mask, - zIndex, - transitionName: popupTransitionName, - maskAnimation, - maskTransitionName, - getContainer, - popupClassName, - popupStyle, - }, - on: { - align: getListeners(this).popupAlign || noop, - ...mouseProps, - }, + prefixCls, + destroyPopupOnHide, + visible: sPopupVisible, + point: alignPoint && point, + action, + align, + animation: popupAnimation, + getClassNameFromAlign: handleGetPopupClassFromAlign, + stretch, + getRootDomNode, + mask, + zIndex, + transitionName: popupTransitionName, + maskAnimation, + maskTransitionName, + getContainer, + popupClassName, + popupStyle, + onAlign: $attrs.onPopupAlign || noop, + ...mouseProps, + ...createRefHooks(this.savePopup), }; return ( - - {getComponentFromProp(self, 'popup')} + + {getComponent(self, 'popup')} ); }, @@ -521,7 +514,7 @@ export default { createTwoChains(event) { let fn = () => {}; - const events = getListeners(this); + const events = getEvents(this); if (this.childOriginEvents[event] && events[event]) { return this[`fire${event}`]; } @@ -564,8 +557,8 @@ export default { return action.indexOf('focus') !== -1 || hideAction.indexOf('blur') !== -1; }, forcePopupAlign() { - if (this.$data.sPopupVisible && this._component && this._component.$refs.alignInstance) { - this._component.$refs.alignInstance.forceAlign(); + if (this.$data.sPopupVisible && this._component && this._component.refs.alignInstance) { + this._component.refs.alignInstance.forceAlign(); } }, fireEvents(type, e) { @@ -591,7 +584,7 @@ export default { this.childOriginEvents = getEvents(this); const newChildProps = { key: 'trigger', - class: $attrs.class, + ref: 'trigger', }; if (this.isContextmenuToShow()) { @@ -634,8 +627,11 @@ export default { } }; } - - const trigger = cloneVNode(child, newChildProps); + const childrenClassName = classNames(child && child.props && child.props.class, $attrs.class); + if (childrenClassName) { + newChildProps.class = childrenClassName; + } + const trigger = cloneElement(child, newChildProps); let portal; // prevent unmounting after it's rendered if (sPopupVisible || this._component || forceRender) { diff --git a/components/vc-trigger/t.js b/components/vc-trigger/t.js deleted file mode 100644 index 14aa67a21..000000000 --- a/components/vc-trigger/t.js +++ /dev/null @@ -1,654 +0,0 @@ -import { cloneVNode, inject, provide } from 'vue'; -import ref from 'vue-ref'; -import PropTypes from '../_util/vue-types'; -import contains from '../vc-util/Dom/contains'; -import { - hasProp, - getComponentFromProp, - getEvents, - filterEmpty, - getSlot, - getListeners, -} from '../_util/props-util'; -import { requestAnimationTimeout, cancelAnimationTimeout } from '../_util/requestAnimationTimeout'; -import addEventListener from '../vc-util/Dom/addEventListener'; -import warning from '../_util/warning'; -import Popup from './Popup'; -import { getAlignFromPlacement, getAlignPopupClassName, noop } from './utils'; -import BaseMixin from '../_util/BaseMixin'; -import Portal from '../_util/Portal'; - -function returnEmptyString() { - return ''; -} - -function returnDocument() { - return window.document; -} -const ALL_HANDLERS = [ - 'click', - 'mousedown', - 'touchstart', - 'mouseenter', - 'mouseleave', - 'focus', - 'blur', - 'contextmenu', -]; - -export default { - name: 'Trigger', - directives: { 'ant-ref': ref }, - mixins: [BaseMixin], - props: { - action: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.string)]).def([]), - showAction: PropTypes.any.def([]), - hideAction: PropTypes.any.def([]), - getPopupClassNameFromAlign: PropTypes.any.def(returnEmptyString), - // onPopupVisibleChange: PropTypes.func.def(noop), - afterPopupVisibleChange: PropTypes.func.def(noop), - popup: PropTypes.any, - popupStyle: PropTypes.object.def(() => ({})), - prefixCls: PropTypes.string.def('rc-trigger-popup'), - popupClassName: PropTypes.string.def(''), - popupPlacement: PropTypes.string, - builtinPlacements: PropTypes.object, - popupTransitionName: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), - popupAnimation: PropTypes.any, - mouseEnterDelay: PropTypes.number.def(0), - mouseLeaveDelay: PropTypes.number.def(0.1), - zIndex: PropTypes.number, - focusDelay: PropTypes.number.def(0), - blurDelay: PropTypes.number.def(0.15), - getPopupContainer: PropTypes.func, - getDocument: PropTypes.func.def(returnDocument), - forceRender: PropTypes.bool, - destroyPopupOnHide: PropTypes.bool.def(false), - mask: PropTypes.bool.def(false), - maskClosable: PropTypes.bool.def(true), - // onPopupAlign: PropTypes.func.def(noop), - popupAlign: PropTypes.object.def(() => ({})), - popupVisible: PropTypes.bool, - defaultPopupVisible: PropTypes.bool.def(false), - maskTransitionName: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), - maskAnimation: PropTypes.string, - stretch: PropTypes.string, - alignPoint: PropTypes.bool, // Maybe we can support user pass position in the future - }, - data() { - const props = this.$props; - let popupVisible; - if (hasProp(this, 'popupVisible')) { - popupVisible = !!props.popupVisible; - } else { - popupVisible = !!props.defaultPopupVisible; - } - ALL_HANDLERS.forEach(h => { - this[`fire${h}`] = e => { - this.fireEvents(h, e); - }; - }); - return { - prevPopupVisible: popupVisible, - sPopupVisible: popupVisible, - point: null, - }; - }, - watch: { - popupVisible(val) { - if (val !== undefined) { - this.prevPopupVisible = this.sPopupVisible; - this.sPopupVisible = val; - } - }, - }, - created() { - provide('vcTriggerContext', this); - }, - setup() { - return { - vcTriggerContext: inject('configProvider', {}), - savePopupRef: inject('vcTriggerContext', noop), - dialogContext: inject('dialogContext', null), - }; - }, - deactivated() { - this.setPopupVisible(false); - }, - mounted() { - this.$nextTick(() => { - this.updatedCal(); - }); - }, - - updated() { - this.$nextTick(() => { - this.updatedCal(); - }); - }, - - beforeDestroy() { - this.clearDelayTimer(); - this.clearOutsideHandler(); - clearTimeout(this.mouseDownTimeout); - }, - methods: { - updatedCal() { - const props = this.$props; - const state = this.$data; - - // We must listen to `mousedown` or `touchstart`, edge case: - // https://github.com/ant-design/ant-design/issues/5804 - // https://github.com/react-component/calendar/issues/250 - // https://github.com/react-component/trigger/issues/50 - if (state.sPopupVisible) { - let currentDocument; - if (!this.clickOutsideHandler && (this.isClickToHide() || this.isContextmenuToShow())) { - currentDocument = props.getDocument(); - this.clickOutsideHandler = addEventListener( - currentDocument, - 'mousedown', - this.onDocumentClick, - ); - } - // always hide on mobile - if (!this.touchOutsideHandler) { - currentDocument = currentDocument || props.getDocument(); - this.touchOutsideHandler = addEventListener( - currentDocument, - 'touchstart', - this.onDocumentClick, - ); - } - // close popup when trigger type contains 'onContextmenu' and document is scrolling. - if (!this.contextmenuOutsideHandler1 && this.isContextmenuToShow()) { - currentDocument = currentDocument || props.getDocument(); - this.contextmenuOutsideHandler1 = addEventListener( - currentDocument, - 'scroll', - this.onContextmenuClose, - ); - } - // close popup when trigger type contains 'onContextmenu' and window is blur. - if (!this.contextmenuOutsideHandler2 && this.isContextmenuToShow()) { - this.contextmenuOutsideHandler2 = addEventListener( - window, - 'blur', - this.onContextmenuClose, - ); - } - } else { - this.clearOutsideHandler(); - } - }, - onMouseenter(e) { - const { mouseEnterDelay } = this.$props; - this.fireEvents('mouseenter', e); - this.delaySetPopupVisible(true, mouseEnterDelay, mouseEnterDelay ? null : e); - }, - - onMouseMove(e) { - this.fireEvents('mousemove', e); - this.setPoint(e); - }, - - onMouseleave(e) { - this.fireEvents('mouseleave', e); - this.delaySetPopupVisible(false, this.$props.mouseLeaveDelay); - }, - - onPopupMouseenter() { - this.clearDelayTimer(); - }, - - onPopupMouseleave(e) { - if ( - e && - e.relatedTarget && - !e.relatedTarget.setTimeout && - this._component && - this._component.getPopupDomNode && - contains(this._component.getPopupDomNode(), e.relatedTarget) - ) { - return; - } - this.delaySetPopupVisible(false, this.$props.mouseLeaveDelay); - }, - - onFocus(e) { - this.fireEvents('focus', e); - // incase focusin and focusout - this.clearDelayTimer(); - if (this.isFocusToShow()) { - this.focusTime = Date.now(); - this.delaySetPopupVisible(true, this.$props.focusDelay); - } - }, - - onMousedown(e) { - this.fireEvents('mousedown', e); - this.preClickTime = Date.now(); - }, - - onTouchstart(e) { - this.fireEvents('touchstart', e); - this.preTouchTime = Date.now(); - }, - - onBlur(e) { - if (!contains(e.target, e.relatedTarget || document.activeElement)) { - this.fireEvents('blur', e); - this.clearDelayTimer(); - if (this.isBlurToHide()) { - this.delaySetPopupVisible(false, this.$props.blurDelay); - } - } - }, - - onContextmenu(e) { - e.preventDefault(); - this.fireEvents('contextmenu', e); - this.setPopupVisible(true, e); - }, - - onContextmenuClose() { - if (this.isContextmenuToShow()) { - this.close(); - } - }, - - onClick(event) { - this.fireEvents('click', event); - // focus will trigger click - if (this.focusTime) { - let preTime; - if (this.preClickTime && this.preTouchTime) { - preTime = Math.min(this.preClickTime, this.preTouchTime); - } else if (this.preClickTime) { - preTime = this.preClickTime; - } else if (this.preTouchTime) { - preTime = this.preTouchTime; - } - if (Math.abs(preTime - this.focusTime) < 20) { - return; - } - this.focusTime = 0; - } - this.preClickTime = 0; - this.preTouchTime = 0; - // Only prevent default when all the action is click. - // https://github.com/ant-design/ant-design/issues/17043 - // https://github.com/ant-design/ant-design/issues/17291 - if ( - this.isClickToShow() && - (this.isClickToHide() || this.isBlurToHide()) && - event && - event.preventDefault - ) { - event.preventDefault(); - } - if (event && event.domEvent) { - event.domEvent.preventDefault(); - } - const nextVisible = !this.$data.sPopupVisible; - if ((this.isClickToHide() && !nextVisible) || (nextVisible && this.isClickToShow())) { - this.setPopupVisible(!this.$data.sPopupVisible, event); - } - }, - onPopupMouseDown(...args) { - const { vcTriggerContext = {} } = this; - this.hasPopupMouseDown = true; - - clearTimeout(this.mouseDownTimeout); - this.mouseDownTimeout = setTimeout(() => { - this.hasPopupMouseDown = false; - }, 0); - - if (vcTriggerContext.onPopupMouseDown) { - vcTriggerContext.onPopupMouseDown(...args); - } - }, - - onDocumentClick(event) { - if (this.$props.mask && !this.$props.maskClosable) { - return; - } - const target = event.target; - const root = this.$el; - if (!contains(root, target) && !this.hasPopupMouseDown) { - this.close(); - } - }, - getPopupDomNode() { - if (this._component && this._component.getPopupDomNode) { - return this._component.getPopupDomNode(); - } - return null; - }, - - getRootDomNode() { - return this.$el; - // return this.$el.children[0] || this.$el - }, - - handleGetPopupClassFromAlign(align) { - const className = []; - const props = this.$props; - const { - popupPlacement, - builtinPlacements, - prefixCls, - alignPoint, - getPopupClassNameFromAlign, - } = props; - if (popupPlacement && builtinPlacements) { - className.push(getAlignPopupClassName(builtinPlacements, prefixCls, align, alignPoint)); - } - if (getPopupClassNameFromAlign) { - className.push(getPopupClassNameFromAlign(align)); - } - return className.join(' '); - }, - - getPopupAlign() { - const props = this.$props; - const { popupPlacement, popupAlign, builtinPlacements } = props; - if (popupPlacement && builtinPlacements) { - return getAlignFromPlacement(builtinPlacements, popupPlacement, popupAlign); - } - return popupAlign; - }, - savePopup(node) { - this._component = node; - this.savePopupRef(node); - }, - getComponent() { - const self = this; - const mouseProps = {}; - if (this.isMouseEnterToShow()) { - mouseProps.mouseenter = self.onPopupMouseenter; - } - if (this.isMouseLeaveToHide()) { - mouseProps.mouseleave = self.onPopupMouseleave; - } - mouseProps.mousedown = this.onPopupMouseDown; - mouseProps.touchstart = this.onPopupMouseDown; - const { handleGetPopupClassFromAlign, getRootDomNode, getContainer } = self; - const { - prefixCls, - destroyPopupOnHide, - popupClassName, - action, - popupAnimation, - popupTransitionName, - popupStyle, - mask, - maskAnimation, - maskTransitionName, - zIndex, - stretch, - alignPoint, - } = self.$props; - const { sPopupVisible, point } = this.$data; - const align = this.getPopupAlign(); - const popupProps = { - props: { - prefixCls, - destroyPopupOnHide, - visible: sPopupVisible, - point: alignPoint && point, - action, - align, - animation: popupAnimation, - getClassNameFromAlign: handleGetPopupClassFromAlign, - stretch, - getRootDomNode, - mask, - zIndex, - transitionName: popupTransitionName, - maskAnimation, - maskTransitionName, - getContainer, - popupClassName, - popupStyle, - }, - on: { - align: getListeners(this).popupAlign || noop, - ...mouseProps, - }, - }; - return ( - - {getComponentFromProp(self, 'popup')} - - ); - }, - - getContainer() { - const { $props: props, dialogContext } = this; - const popupContainer = document.createElement('div'); - // Make sure default popup container will never cause scrollbar appearing - // https://github.com/react-component/trigger/issues/41 - popupContainer.style.position = 'absolute'; - popupContainer.style.top = '0'; - popupContainer.style.left = '0'; - popupContainer.style.width = '100%'; - const mountNode = props.getPopupContainer - ? props.getPopupContainer(this.$el, dialogContext) - : props.getDocument().body; - mountNode.appendChild(popupContainer); - this.popupContainer = popupContainer; - return popupContainer; - }, - - setPopupVisible(sPopupVisible, event) { - const { alignPoint, sPopupVisible: prevPopupVisible } = this; - this.clearDelayTimer(); - if (prevPopupVisible !== sPopupVisible) { - if (!hasProp(this, 'popupVisible')) { - this.setState({ - sPopupVisible, - prevPopupVisible, - }); - } - const listeners = getListeners(this); - listeners.popupVisibleChange && listeners.popupVisibleChange(sPopupVisible); - } - // Always record the point position since mouseEnterDelay will delay the show - if (alignPoint && event) { - this.setPoint(event); - } - }, - - setPoint(point) { - const { alignPoint } = this.$props; - if (!alignPoint || !point) return; - - this.setState({ - point: { - pageX: point.pageX, - pageY: point.pageY, - }, - }); - }, - handlePortalUpdate() { - if (this.prevPopupVisible !== this.sPopupVisible) { - this.afterPopupVisibleChange(this.sPopupVisible); - } - }, - delaySetPopupVisible(visible, delayS, event) { - const delay = delayS * 1000; - this.clearDelayTimer(); - if (delay) { - const point = event ? { pageX: event.pageX, pageY: event.pageY } : null; - this.delayTimer = requestAnimationTimeout(() => { - this.setPopupVisible(visible, point); - this.clearDelayTimer(); - }, delay); - } else { - this.setPopupVisible(visible, event); - } - }, - - clearDelayTimer() { - if (this.delayTimer) { - cancelAnimationTimeout(this.delayTimer); - this.delayTimer = null; - } - }, - - clearOutsideHandler() { - if (this.clickOutsideHandler) { - this.clickOutsideHandler.remove(); - this.clickOutsideHandler = null; - } - - if (this.contextmenuOutsideHandler1) { - this.contextmenuOutsideHandler1.remove(); - this.contextmenuOutsideHandler1 = null; - } - - if (this.contextmenuOutsideHandler2) { - this.contextmenuOutsideHandler2.remove(); - this.contextmenuOutsideHandler2 = null; - } - - if (this.touchOutsideHandler) { - this.touchOutsideHandler.remove(); - this.touchOutsideHandler = null; - } - }, - - createTwoChains(event) { - let fn = () => {}; - const events = getListeners(this); - if (this.childOriginEvents[event] && events[event]) { - return this[`fire${event}`]; - } - fn = this.childOriginEvents[event] || events[event] || fn; - return fn; - }, - - isClickToShow() { - const { action, showAction } = this.$props; - return action.indexOf('click') !== -1 || showAction.indexOf('click') !== -1; - }, - - isContextmenuToShow() { - const { action, showAction } = this.$props; - return action.indexOf('contextmenu') !== -1 || showAction.indexOf('contextmenu') !== -1; - }, - - isClickToHide() { - const { action, hideAction } = this.$props; - return action.indexOf('click') !== -1 || hideAction.indexOf('click') !== -1; - }, - - isMouseEnterToShow() { - const { action, showAction } = this.$props; - return action.indexOf('hover') !== -1 || showAction.indexOf('mouseenter') !== -1; - }, - - isMouseLeaveToHide() { - const { action, hideAction } = this.$props; - return action.indexOf('hover') !== -1 || hideAction.indexOf('mouseleave') !== -1; - }, - - isFocusToShow() { - const { action, showAction } = this.$props; - return action.indexOf('focus') !== -1 || showAction.indexOf('focus') !== -1; - }, - - isBlurToHide() { - const { action, hideAction } = this.$props; - return action.indexOf('focus') !== -1 || hideAction.indexOf('blur') !== -1; - }, - forcePopupAlign() { - if (this.$data.sPopupVisible && this._component && this._component.$refs.alignInstance) { - this._component.$refs.alignInstance.forceAlign(); - } - }, - fireEvents(type, e) { - if (this.childOriginEvents[type]) { - this.childOriginEvents[type](e); - } - this.__emit(type, e); - }, - - close() { - this.setPopupVisible(false); - }, - }, - render() { - const { sPopupVisible } = this; - const children = filterEmpty(getSlot(this)); - const { forceRender, alignPoint } = this.$props; - - if (children.length > 1) { - warning(false, 'Trigger $slots.default.length > 1, just support only one default', true); - } - const child = children[0]; - this.childOriginEvents = getEvents(child); - const newChildProps = { - props: {}, - on: {}, - key: 'trigger', - }; - - if (this.isContextmenuToShow()) { - newChildProps.on.contextmenu = this.onContextmenu; - } else { - newChildProps.on.contextmenu = this.createTwoChains('contextmenu'); - } - - if (this.isClickToHide() || this.isClickToShow()) { - newChildProps.on.click = this.onClick; - newChildProps.on.mousedown = this.onMousedown; - newChildProps.on.touchstart = this.onTouchstart; - } else { - newChildProps.on.click = this.createTwoChains('click'); - newChildProps.on.mousedown = this.createTwoChains('mousedown'); - newChildProps.on.touchstart = this.createTwoChains('onTouchstart'); - } - if (this.isMouseEnterToShow()) { - newChildProps.on.mouseenter = this.onMouseenter; - if (alignPoint) { - newChildProps.on.mousemove = this.onMouseMove; - } - } else { - newChildProps.on.mouseenter = this.createTwoChains('mouseenter'); - } - if (this.isMouseLeaveToHide()) { - newChildProps.on.mouseleave = this.onMouseleave; - } else { - newChildProps.on.mouseleave = this.createTwoChains('mouseleave'); - } - - if (this.isFocusToShow() || this.isBlurToHide()) { - newChildProps.on.focus = this.onFocus; - newChildProps.on.blur = this.onBlur; - } else { - newChildProps.on.focus = this.createTwoChains('focus'); - newChildProps.on.blur = e => { - if (e && (!e.relatedTarget || !contains(e.target, e.relatedTarget))) { - this.createTwoChains('blur')(e); - } - }; - } - - const trigger = cloneVNode(child, newChildProps); - let portal; - // prevent unmounting after it's rendered - if (sPopupVisible || this._component || forceRender) { - portal = ( - - ); - } - return [portal, trigger]; - }, -}; diff --git a/examples/index.js b/examples/index.js index 411e1b710..6ec0cdff0 100644 --- a/examples/index.js +++ b/examples/index.js @@ -17,6 +17,7 @@ import PageHeader from 'ant-design-vue/page-header'; import Skeleton from 'ant-design-vue/skeleton'; import Empty from 'ant-design-vue/empty'; import Timeline from 'ant-design-vue/timeline'; +import Tooltip from 'ant-design-vue/tooltip'; import 'ant-design-vue/style.js'; createApp(App) @@ -36,4 +37,5 @@ createApp(App) .use(Spin) .use(Empty) .use(Timeline) + .use(Tooltip) .mount('#app');