diff --git a/components/_util/BaseMixin.js b/components/_util/BaseMixin.js new file mode 100644 index 000000000..66d6bfa94 --- /dev/null +++ b/components/_util/BaseMixin.js @@ -0,0 +1,16 @@ +export default { + methods: { + setState (state, callback) { + Object.assign(this.$data, state) + this.$nextTick(() => { + callback && callback() + }) + }, + __emit () { // 直接调用listeners,底层组件不需要vueTool记录events + const args = [].slice.call(arguments, 0) + if (args.length && this.$listeners[args[0]]) { + this.$listeners[args[0]](...args.slice(1)) + } + }, + }, +} diff --git a/components/_util/cloneElement.js b/components/_util/cloneElement.js deleted file mode 100644 index ec8b55c1f..000000000 --- a/components/_util/cloneElement.js +++ /dev/null @@ -1,8 +0,0 @@ -export default (node, nodeProps) => { - const { props, style, class: cls, attrs, key } = nodeProps - if (node.componentOptions) { - const propsData = node.componentOptions.propsData - Object.assign(propsData, nodeProps) - } - return node -} diff --git a/components/_util/props-util.js b/components/_util/props-util.js new file mode 100644 index 000000000..68e8f97b2 --- /dev/null +++ b/components/_util/props-util.js @@ -0,0 +1,30 @@ +const hasProp = (instance, prop) => { + const $options = instance.$options || {} + const propsData = $options.propsData || {} + return prop in propsData +} +const filterProps = (props, propsData = {}) => { + const res = {} + Object.keys(props).forEach((k) => { + if (k in propsData || props[k] !== undefined) { + res[k] = props[k] + } + }) + return res +} + +const getOptionProps = (instance) => { + const { $options = {}, $props = {}} = instance + return filterProps($props, $options.propsData) +} + +const getComponentFromProp = (instance, h, prop) => { + const temp = instance[prop] + if (temp !== undefined) { + return typeof temp === 'function' ? temp(h) : temp + } + return instance.$slots[prop] +} + +export { hasProp, filterProps, getOptionProps, getComponentFromProp } +export default hasProp diff --git a/components/_util/vnode.js b/components/_util/vnode.js index e3225d29a..ecf245b82 100644 --- a/components/_util/vnode.js +++ b/components/_util/vnode.js @@ -78,11 +78,30 @@ export function cloneElement (n, nodeProps, clone) { const { style = data.style, class: cls = data.class, attrs = data.attrs, + ref, } = nodeProps node.data = Object.assign(data, { style, attrs, class: cls, on: { ...(data.on || {}), ...on }}) if (key !== undefined) { node.key = key node.data.key = key } + if (typeof ref === 'string') { + node.data.ref = ref + } return node } +export function getComponentName (opts) { + return opts && (opts.Ctor.options.name || opts.tag) +} + +export function isValidElement (ele) { + return !!ele.tag +} + +export function getClass (ele) { + return ele.data && (ele.data.class || ele.data.staticClass) +} + +export function getStyle (ele) { + return ele.data && (ele.data.style || ele.data.staticStyle) +} diff --git a/components/align/Align.vue b/components/align/Align.vue index 36f1d9e6a..f4cdf0bb1 100644 --- a/components/align/Align.vue +++ b/components/align/Align.vue @@ -102,7 +102,8 @@ export default { const props = this.$props if (!props.disabled) { const source = this.$el - this.$emit('align', source, align(source, props.target(), props.align)) + // this.$emit('align', source, align(source, props.target(), props.align)) + this.$listeners.align && this.$listeners.align(source, align(source, props.target(), props.align)) } }, }, diff --git a/components/align/demo/simple.vue b/components/align/demo/simple.vue index 84b11f40f..7c5c991e2 100644 --- a/components/align/demo/simple.vue +++ b/components/align/demo/simple.vue @@ -1,9 +1,9 @@ <script> import Align from '../index' -import StateMixin from '../../_util/StateMixin' +import BaseMixin from '../../_util/BaseMixin' export default { - mixins: [StateMixin], + mixins: [BaseMixin], data () { return { monitor: true, diff --git a/components/badge/ScrollNumber.vue b/components/badge/ScrollNumber.vue index 3bd878912..b81832e98 100644 --- a/components/badge/ScrollNumber.vue +++ b/components/badge/ScrollNumber.vue @@ -1,5 +1,5 @@ <script> -import StateMixin from '../_util/StateMixin' +import BaseMixin from '../_util/BaseMixin' function getNumberArray (num) { return num @@ -20,7 +20,7 @@ export default { default: () => ({}), }, }, - mixins: [StateMixin], + mixins: [BaseMixin], data () { const { count } = this return { diff --git a/components/button/button.vue b/components/button/button.vue index 031d056ec..e82a87c07 100644 --- a/components/button/button.vue +++ b/components/button/button.vue @@ -2,39 +2,13 @@ import Icon from '../icon' const rxTwoCNChar = /^[\u4e00-\u9fa5]{2}$/ const isTwoCNChar = rxTwoCNChar.test.bind(rxTwoCNChar) +import buttonTypes from './buttonTypes' export default { name: 'Button', + __ANT_BUTTON: true, components: { Icon }, props: { - prefixCls: { - default: 'ant-btn', - type: String, - }, - type: { - validator (value) { - return ['primary', 'danger', 'dashed', 'ghost', 'default'].includes(value) - }, - }, - htmlType: { - default: 'button', - validator (value) { - return ['button', 'submit', 'reset'].includes(value) - }, - }, - icon: String, - shape: { - validator (value) { - return ['circle', 'circle-outline'].includes(value) - }, - }, - size: { - validator (value) { - return ['small', 'large', 'default'].includes(value) - }, - }, - loading: [Boolean, Object], - disabled: Boolean, - ghost: Boolean, + ...buttonTypes, }, data () { return { diff --git a/components/button/buttonTypes.js b/components/button/buttonTypes.js new file mode 100644 index 000000000..2104f1aa7 --- /dev/null +++ b/components/button/buttonTypes.js @@ -0,0 +1,12 @@ +import PropTypes from '../_util/vue-types' +export default { + prefixCls: PropTypes.string.def('ant-btn'), + type: PropTypes.oneOf(['primary', 'danger', 'dashed', 'ghost', 'default']).def('default'), + htmlType: PropTypes.oneOf(['button', 'submit', 'reset']).def('button'), + icon: PropTypes.string, + shape: PropTypes.oneOf(['circle', 'circle-outline']), + size: PropTypes.oneOf(['small', 'large', 'default']).def('default'), + loading: PropTypes.oneOfType([PropTypes.bool, PropTypes.object]), + disabled: PropTypes.bool, + ghost: PropTypes.bool, +} diff --git a/components/checkbox/Checkbox.vue b/components/checkbox/Checkbox.vue index 7606af73c..047e7cc45 100644 --- a/components/checkbox/Checkbox.vue +++ b/components/checkbox/Checkbox.vue @@ -13,7 +13,7 @@ </label> </template> <script> -import hasProp from '../_util/hasProp' +import hasProp from '../_util/props-util' export default { name: 'Checkbox', props: { diff --git a/components/checkbox/Group.vue b/components/checkbox/Group.vue index b05228124..62de663fe 100644 --- a/components/checkbox/Group.vue +++ b/components/checkbox/Group.vue @@ -8,7 +8,7 @@ </template> <script> import Checkbox from './Checkbox.vue' -import hasProp from '../_util/hasProp' +import hasProp from '../_util/props-util' export default { name: 'CheckboxGroup', props: { diff --git a/components/index.js b/components/index.js index a349bc1f0..70e9fb2ed 100644 --- a/components/index.js +++ b/components/index.js @@ -10,7 +10,7 @@ export { default as Grid } from './grid' export { default as Rate } from './rate' -export { default as ToolTip } from './tooltip' +export { default as Tooltip } from './tooltip' export { default as Pagination } from './pagination' @@ -29,3 +29,7 @@ export { default as Tabs } from './tabs' export { default as Input } from './input' export { default as Breadcrumb } from './breadcrumb' + +export { default as Popover } from './popover' + +export { default as Popconfirm } from './popconfirm' diff --git a/components/input/Input.vue b/components/input/Input.vue index 9949af8af..8d4e9f494 100644 --- a/components/input/Input.vue +++ b/components/input/Input.vue @@ -2,7 +2,7 @@ import TextArea from './TextArea' import omit from 'omit.js' import inputProps from './inputProps' -import hasProp from '../_util/hasProp' +import hasProp from '../_util/props-util' function fixControlledValue (value) { if (typeof value === 'undefined' || value === null) { diff --git a/components/input/TextArea.vue b/components/input/TextArea.vue index 3ff8d72f5..93118e055 100644 --- a/components/input/TextArea.vue +++ b/components/input/TextArea.vue @@ -2,7 +2,7 @@ import omit from 'omit.js' import inputProps from './inputProps' import calculateNodeHeight from './calculateNodeHeight' -import hasProp from '../_util/hasProp' +import hasProp from '../_util/props-util' function onNextFrame (cb) { if (window.requestAnimationFrame) { diff --git a/components/menu/demo/antd.vue b/components/menu/demo/antd.vue index 841b2cfe4..e1083b02e 100644 --- a/components/menu/demo/antd.vue +++ b/components/menu/demo/antd.vue @@ -1,8 +1,6 @@ <script> -import { cloneElement } from '../../_util/vnode' import Clone from '../../_util/Clone' import Menu, { SubMenu, Item as MenuItem, Divider } from '../src/index' -import { Icon } from 'antd' import animate from 'css-animation' function handleSelect (info) { @@ -74,17 +72,16 @@ export default { </SubMenu> </SubMenu> </SubMenu>) - - function onOpenChange (value) { - console.log('onOpenChange', value) + const onOpenChange = (value) => { + console.log('onOpenChange', value, this.$refs) } const commonMenu = () => ( <Menu onSelect={handleSelect} onOpenChange={onOpenChange}> - <SubMenu key='1' title={<span>sub menu</span>}> + <SubMenu ref='test' key='1' title={<span>sub menu</span>}> <MenuItem key='1-1'> 0-1 </MenuItem> - <MenuItem key='1-2'>0-2</MenuItem> + <MenuItem key='1-2' disabled>0-2</MenuItem> </SubMenu> {nestSubMenu()} <MenuItem key='2'>1</MenuItem> @@ -130,6 +127,7 @@ export default { <div style={{ margin: '20px', width: '400px' }}><Clone childProps={{ mode: 'inline', defaultOpenKeys: ['1'], + defaultSelectedKeys: ['1-2', '4-1'], openAnimation: animation, }} > {commonMenu()} diff --git a/components/menu/src/DOMWrap.vue b/components/menu/src/DOMWrap.vue index fb18dc9a1..98f5cde0c 100644 --- a/components/menu/src/DOMWrap.vue +++ b/components/menu/src/DOMWrap.vue @@ -33,6 +33,7 @@ export default { const Tag = this.$props.tag const tagProps = { attr: { ...otherProps, ...this.$attrs }, + on: this.$listeners, } return <Tag {...tagProps} class={this.class}>{this.$slots.default}</Tag> }, diff --git a/components/menu/src/Menu.vue b/components/menu/src/Menu.vue index 621b2a040..739d23992 100644 --- a/components/menu/src/Menu.vue +++ b/components/menu/src/Menu.vue @@ -1,8 +1,8 @@ <script> import PropTypes from '../../_util/vue-types' import MenuMixin from './MenuMixin' -import StateMixin from '../../_util/StateMixin' -import hasProp from '../../_util/hasProp' +import BaseMixin from '../../_util/BaseMixin' +import hasProp from '../../_util/props-util' import commonPropsType from './commonPropsType' const Menu = { @@ -14,7 +14,7 @@ const Menu = { selectable: PropTypes.bool.def(true), ...commonPropsType, }, - mixins: [StateMixin, MenuMixin], + mixins: [BaseMixin, MenuMixin], data () { const props = this.$props @@ -79,7 +79,7 @@ const Menu = { sSelectedKeys, }) } - this.$emit('select', { + this.__emit('select', { ...selectInfo, sSelectedKeys, }) @@ -87,7 +87,7 @@ const Menu = { }, onClick (e) { - this.$emit('click', e) + this.__emit('click', e) }, onOpenChange (e_) { @@ -119,7 +119,7 @@ const Menu = { if (!hasProp(this, 'openKeys')) { this.setState({ sOpenKeys }) } - this.$emit('openChange', sOpenKeys) + this.__emit('openChange', sOpenKeys) } }, @@ -137,7 +137,7 @@ const Menu = { sSelectedKeys, }) } - this.$emit('deselect', { + this.__emit('deselect', { ...selectInfo, sSelectedKeys, }) @@ -163,7 +163,7 @@ const Menu = { const { sOpenKeys } = this.$data if (sOpenKeys.length) { lastOpen = this.getFlatInstanceArray().filter((c) => { - return c && sOpenKeys.indexOf(c.props.eventKey) !== -1 + return c && sOpenKeys.indexOf(c.eventKey) !== -1 }) } return lastOpen[0] diff --git a/components/menu/src/MenuItem.vue b/components/menu/src/MenuItem.vue index 5df5783cc..8b7c4d27f 100644 --- a/components/menu/src/MenuItem.vue +++ b/components/menu/src/MenuItem.vue @@ -2,7 +2,7 @@ import PropTypes from '../../_util/vue-types' import KeyCode from '../../_util/KeyCode' import { noop } from './util' -import StateMixin from '../../_util/StateMixin' +import BaseMixin from '../../_util/BaseMixin' const MenuItem = { name: 'MenuItem', @@ -14,44 +14,44 @@ const MenuItem = { selectedKeys: PropTypes.array, disabled: PropTypes.bool, title: PropTypes.string, + index: PropTypes.number, inlineIndent: PropTypes.number.def(24), level: PropTypes.number.def(1), mode: PropTypes.oneOf(['horizontal', 'vertical', 'vertical-left', 'vertical-right', 'inline']).def('vertical'), - // onItemHover: PropTypes.func, - // onSelect: PropTypes.func, - // onClick: PropTypes.func, - // onDeselect: PropTypes.func, parentMenu: PropTypes.object, - // onDestroy: PropTypes.func, - // onMouseEnter: PropTypes.func, - // onMouseLeave: PropTypes.func, clearSubMenuTimers: PropTypes.func.def(noop), }, inject: { parentMenuContext: { default: undefined }, }, - mixins: [StateMixin], + mixins: [BaseMixin], isMenuItem: true, beforeDestroy () { const props = this.$props - this.$emit('destroy', props.eventKey) + this.__emit('destroy', props.eventKey) }, methods: { + __emit () { // 直接调用listeners,底层组件不需要vueTool记录events + const args = [].slice.call(arguments, 0) + if (args.length && this.$listeners[args[0]]) { + this.$listeners[args[0]](...args.slice(1)) + } + }, onKeyDown (e) { const keyCode = e.keyCode if (keyCode === KeyCode.ENTER) { - this.$emit('click', e) + this.__emit('click', e) return true } }, onMouseLeave (e) { const { eventKey } = this.$props - this.$emit('itemHover', { + this.__emit('itemHover', { key: eventKey, hover: false, }) - this.$emit('mouseLeave', { + this.__emit('mouseleave', { key: eventKey, domEvent: e, }) @@ -62,11 +62,11 @@ const MenuItem = { if (parentMenuContext && parentMenuContext.subMenuInstance) { parentMenuContext.subMenuInstance.clearSubMenuTimers() } - this.$emit('itemHover', { + this.__emit('itemHover', { key: eventKey, hover: true, }) - this.$emit('mouseEnter', { + this.__emit('mouseenter', { key: eventKey, domEvent: e, }) @@ -81,15 +81,15 @@ const MenuItem = { item: this, domEvent: e, } - this.$emit('click', info) + this.__emit('click', info) if (multiple) { if (selected) { - this.$emit('deselect', info) + this.__emit('deselect', info) } else { - this.$emit('select', info) + this.__emit('select', info) } } else if (!selected) { - this.$emit('select', info) + this.__emit('select', info) } }, diff --git a/components/menu/src/MenuMixin.js b/components/menu/src/MenuMixin.js index 5b1cbe6ea..8250e105b 100644 --- a/components/menu/src/MenuMixin.js +++ b/components/menu/src/MenuMixin.js @@ -1,4 +1,4 @@ -import hasProp from '../../_util/hasProp' +import hasProp from '../../_util/props-util' import KeyCode from '../../_util/KeyCode' import scrollIntoView from 'dom-scroll-into-view' import { getKeyFromChildrenIndex, loopMenuItem } from './util' @@ -9,10 +9,8 @@ function allDisabled (arr) { if (!arr.length) { return true } - return arr.every(c => { - const propsData = c.componentOptions.propsData || {} - return !!propsData.disabled + return !!c.disabled }) } @@ -53,10 +51,6 @@ export default { deep: true, }, }, - - created () { - this.instanceArray = [] - }, methods: { getActiveKey (originalActiveKey) { let activeKey = originalActiveKey @@ -86,23 +80,13 @@ export default { } return activeKey }, - saveRef (index, subIndex, c) { - if (c) { - if (subIndex !== undefined) { - this.instanceArray[index] = this.instanceArray[index] || [] - this.instanceArray[index][subIndex] = c - } else { - this.instanceArray[index] = c - } - } - }, // all keyboard events callbacks run from here at first - onKeyDown (e, callback) { + onKeyDown (e) { const keyCode = e.keyCode let handled this.getFlatInstanceArray().forEach((obj) => { - if (obj && obj.$props.active) { - handled = this.$emit('keydown', e) + if (obj && obj.active) { + handled = obj.onKeyDown(e) } }) if (handled) { @@ -115,15 +99,11 @@ export default { if (activeItem) { e.preventDefault() this.setState({ - sActiveKey: activeItem.$props.eventKey, + sActiveKey: activeItem.eventKey, }, () => { scrollIntoView(activeItem.$el, this.$el, { onlyScrollIfNeeded: true, }) - // https://github.com/react-component/menu/commit/9899a9672f6f028ec3cdf773f1ecea5badd2d33e - if (typeof callback === 'function') { - callback(activeItem) - } }) return 1 } else if (activeItem === undefined) { @@ -143,22 +123,13 @@ export default { }, getFlatInstanceArray () { - let instanceArray = this.instanceArray - const hasInnerArray = instanceArray.some((a) => { - return Array.isArray(a) - }) - if (hasInnerArray) { - instanceArray = [] - this.instanceArray.forEach((a) => { - if (Array.isArray(a)) { - instanceArray.push.apply(instanceArray, a) - } else { - instanceArray.push(a) - } - }) - this.instanceArray = instanceArray + let instance = [] + try { + instance = this.$children[0].$children || [] + } catch (error) { + } - return instanceArray + return instance }, renderCommonMenuItem (child, i, subIndex, extraProps) { @@ -175,7 +146,6 @@ export default { renderMenuItem: this.renderMenuItem, rootPrefixCls: props.prefixCls, index: i, - // parentMenu: this, eventKey: key, active: !childProps.disabled && isActive, multiple: props.multiple, @@ -195,11 +165,7 @@ export default { destroy: this.onDestroy, select: this.onSelect, }, - // ref: childProps.disabled ? undefined : child.ref, - // ref: childProps.disabled ? undefined - // : createChainedFunction(child.ref, saveRef.bind(this, i, subIndex)), } - // !childProps.disabled && this.saveRef(i, subIndex, child.ref) if (props.mode === 'inline') { newChildProps.props.triggerSubMenuAction = 'click' } @@ -210,7 +176,6 @@ export default { }, renderRoot (props, children = []) { - this.instanceArray = [] const className = { [props.prefixCls]: true, [props.class]: true, @@ -236,7 +201,7 @@ export default { domProps.attrs.tabIndex = '0' domProps.on.keydown = this.onKeyDown } - const newChildren = children.map(this.renderMenuItem) + const newChildren = children.map((c, i) => this.renderMenuItem(c, i)) return ( <DOMWrap {...domProps} @@ -259,8 +224,7 @@ export default { // find current activeIndex let activeIndex = -1 children.every((c, ci) => { - const propsData = c.componentOptions.propsData || {} - if (c && propsData.eventKey === sActiveKey) { + if (c && c.eventKey === sActiveKey) { activeIndex = ci return false } @@ -275,8 +239,7 @@ export default { let i = start for (; ;) { const child = children[i] - const propsData = child.componentOptions.propsData || {} - if (!child || propsData.disabled) { + if (!child || child.disabled) { i = (i + 1 + len) % len // complete a loop if (i === start) { diff --git a/components/menu/src/SubMenu.vue b/components/menu/src/SubMenu.vue index fdb38b991..071999cac 100644 --- a/components/menu/src/SubMenu.vue +++ b/components/menu/src/SubMenu.vue @@ -5,7 +5,7 @@ import KeyCode from '../../_util/KeyCode' import SubPopupMenu from './SubPopupMenu' import placements from './placements' import { loopMenuItemRecusively, noop } from './util' -import StateMixin from '../../_util/StateMixin' +import BaseMixin from '../../_util/BaseMixin' let guid = 0 @@ -31,6 +31,7 @@ export default { multiple: PropTypes.bool, active: PropTypes.bool, // TODO: remove isRootMenu: PropTypes.bool, + index: PropTypes.number, // onItemHover: PropTypes.func, // onSelect: PropTypes.func, triggerSubMenuAction: PropTypes.string, @@ -44,18 +45,11 @@ export default { subMenuCloseDelay: PropTypes.number.def(0.1), level: PropTypes.number.def(1), inlineIndent: PropTypes.number.def(24), - // onDeselect: PropTypes.func, - // onDestroy: PropTypes.func, - // onMouseEnter: PropTypes.func, - // onMouseLeave: PropTypes.func, - // onTitleMouseEnter: PropTypes.func, - // onTitleMouseLeave: PropTypes.func, - // onTitleClick: PropTypes.func, }, inject: { parentMenuContext: { default: undefined }, }, - mixins: [StateMixin], + mixins: [BaseMixin], isSubMenu: true, data () { return { @@ -72,7 +66,7 @@ export default { beforeDestroy () { const { eventKey, parentMenuContext } = this - this.$emit('destroy', eventKey) + this.__emit('destroy', eventKey) if (parentMenuContext.subMenuInstance === this) { this.clearSubMenuTimers() } @@ -94,13 +88,10 @@ export default { popupMenu.style.minWidth = `${this.subMenuTitle.offsetWidth}px` }, 0) }, - onDestroy (key) { - this.$emit('destroy', key) - }, onKeyDown (e) { const keyCode = e.keyCode - const menu = this.menuInstance + const menu = this.$refs.menuInstance const isOpen = this.isOpen() if (keyCode === KeyCode.ENTER) { @@ -141,10 +132,6 @@ export default { } }, - onOpenChange (e) { - this.$emit('openChange', e) - }, - onPopupVisibleChange (visible) { this.triggerOpenChange(visible, visible ? 'mouseenter' : 'mouseleave') }, @@ -155,7 +142,7 @@ export default { this.setState({ defaultActiveFirst: false, }) - this.$emit('mouseenter', { + this.__emit('mouseenter', { key, domEvent: e, }) @@ -169,7 +156,7 @@ export default { parentMenuContext.subMenuInstance = this parentMenuContext.subMenuLeaveFn = () => { // trigger mouseleave - this.$emit('mouseleave', { + this.__emit('mouseleave', { key: eventKey, domEvent: e, }) @@ -181,11 +168,11 @@ export default { onTitleMouseEnter (domEvent) { const { eventKey: key } = this.$props this.clearSubMenuTitleLeaveTimer() - this.$emit('itemHover', { + this.__emit('itemHover', { key, hover: true, }) - this.$emit('titleMouseenter', { + this.__emit('titleMouseenter', { key, domEvent, }) @@ -195,11 +182,11 @@ export default { const { eventKey, parentMenuContext } = this parentMenuContext.subMenuInstance = this parentMenuContext.subMenuTitleLeaveFn = () => { - this.$emit('itemHover', { + this.__emit('itemHover', { key: eventKey, hover: false, }) - this.$emit('titleMouseleave', { + this.__emit('titleMouseleave', { key: eventKey, domEvent: e, }) @@ -210,7 +197,7 @@ export default { onTitleClick (e) { const { triggerSubMenuAction, eventKey } = this.$props - this.$emit('itemClick', { + this.__emit('itemClick', { key: eventKey, domEvent: e, test: 111, @@ -225,15 +212,7 @@ export default { }, onSubMenuClick (info) { - this.$emit('click', this.addKeyPath(info)) - }, - - onSelect (info) { - this.$emit('select', info) - }, - - onDeselect (info) { - this.$emit('deselect', info) + this.__emit('click', this.addKeyPath(info)) }, getPrefixCls () { @@ -265,7 +244,7 @@ export default { triggerOpenChange (open, type) { const key = this.$props.eventKey - this.onOpenChange({ + this.__emit('openChange', { key, item: this, trigger: type, @@ -308,6 +287,7 @@ export default { renderChildren (children, vShow) { const props = this.$props const isOpen = this.isOpen() + const { select, deselect, destroy, openChange } = this.$listeners const subPopupMenuProps = { props: { mode: props.mode === 'horizontal' ? 'vertical' : props.mode, @@ -331,10 +311,7 @@ export default { }, on: { click: this.onSubMenuClick, - select: this.onSelect, - deselect: this.onDeselect, - destroy: this.onDestroy, - openChange: this.onOpenChange, + select, deselect, destroy, openChange, }, id: this._menuId, ref: 'menuInstance', diff --git a/components/menu/src/SubPopupMenu.vue b/components/menu/src/SubPopupMenu.vue index 03072fbc5..fda6314b3 100644 --- a/components/menu/src/SubPopupMenu.vue +++ b/components/menu/src/SubPopupMenu.vue @@ -1,7 +1,7 @@ <script> import PropTypes from '../../_util/vue-types' import MenuMixin from './MenuMixin' -import StateMixin from '../../_util/StateMixin' +import BaseMixin from '../../_util/BaseMixin' import commonPropsType from './commonPropsType' import { noop } from './util' export default { @@ -10,26 +10,26 @@ export default { clearSubMenuTimers: PropTypes.func.def(noop), }, - mixins: [MenuMixin, StateMixin], + mixins: [MenuMixin, BaseMixin], methods: { onDeselect (selectInfo) { - this.$emit('deselect', selectInfo) + this.__emit('deselect', selectInfo) }, onSelect (selectInfo) { - this.$emit('select', selectInfo) + this.__emit('select', selectInfo) }, onClick (e) { - this.$emit('click', e) + this.__emit('click', e) }, onOpenChange (e) { - this.$emit('openChange', e) + this.__emit('openChange', e) }, onDestroy (key) { - this.$emit('destroy', key) + this.__emit('destroy', key) }, getOpenTransitionName () { diff --git a/components/popconfirm/demo/basic.vue b/components/popconfirm/demo/basic.vue new file mode 100644 index 000000000..95f6bbb29 --- /dev/null +++ b/components/popconfirm/demo/basic.vue @@ -0,0 +1,29 @@ +<template> +<div> +<md> +## 基本 +最简单的用法。 +</md> + <Popconfirm title="Are you sure delete this task?" @confirm="confirm" @cancel="cancel" okText="Yes" cancelText="No"> + <a href="#">Delete</a> + </Popconfirm> +</div> +</template> + +<script> +import { Popconfirm, Button } from 'antd' +export default { + methods: { + confirm (e) { + console.log(e) + }, + cancel (e) { + console.log(e) + }, + }, + components: { + Popconfirm, + AntButton: Button, + }, +} +</script> diff --git a/components/popconfirm/index.vue b/components/popconfirm/index.vue new file mode 100644 index 000000000..9807ce386 --- /dev/null +++ b/components/popconfirm/index.vue @@ -0,0 +1,118 @@ +<script> +import omit from 'omit.js' +import Tooltip from '../tooltip' +import abstractTooltipProps from '../tooltip/abstractTooltipProps' +import PropTypes from '../_util/vue-types' +import { getOptionProps, hasProp, getComponentFromProp } from '../_util/props-util' +import BaseMixin from '../_util/BaseMixin' +import buttonTypes from '../button/buttonTypes' +import Icon from '../icon' +import Button from '../button' + +export default { + name: 'popconfirm', + props: { + ...abstractTooltipProps, + prefixCls: PropTypes.string.def('ant-popover'), + transitionName: PropTypes.string.def('zoom-big'), + content: PropTypes.any, + title: PropTypes.any, + trigger: abstractTooltipProps.trigger.def('click'), + okType: buttonTypes.type.def('primary'), + okText: PropTypes.any, + cancelText: PropTypes.any, + }, + mixins: [BaseMixin], + model: { + prop: 'visible', + event: 'change', + }, + data () { + return { + sVisible: this.$props.visible, + } + }, + methods: { + onConfirm (e) { + this.setVisible(false) + this.$emit('confirm', e) + }, + + onCancel (e) { + this.setVisible(false) + this.$emit('cancel', e) + }, + + onVisibleChange (sVisible) { + this.setVisible(sVisible) + }, + + setVisible (sVisible) { + const props = this.$props + if (!hasProp(this, 'visible')) { + this.setState({ sVisible }) + } + + const { onVisibleChange } = props + if (onVisibleChange) { + onVisibleChange(sVisible) + } + this.$emit('change', sVisible) + }, + getPopupDomNode () { + return this.$refs.tooltip.getPopupDomNode() + }, + }, + render (h) { + const { prefixCls, okType } = this.$props + const props = getOptionProps(this) + const otherProps = omit(props, [ + 'title', + 'content', + 'cancelText', + 'okText', + ]) + const tooltipProps = { + props: { + ...otherProps, + visible: this.sVisible, + }, + ref: 'tooltip', + on: { + change: this.onVisibleChange, + }, + } + const overlay = ( + <div> + <div class={`${prefixCls}-inner-content`}> + <div class={`${prefixCls}-message`}> + <Icon type='exclamation-circle' /> + <div class={`${prefixCls}-message-title`}> + {getComponentFromProp(this, h, 'title')} + </div> + </div> + <div class={`${prefixCls}-buttons`}> + <Button onClick={this.onCancel} size='small'> + {getComponentFromProp(this, h, 'cancelText')} + </Button> + <Button onClick={this.onConfirm} type={okType} size='small'> + {getComponentFromProp(this, h, 'okText')} + </Button> + </div> + </div> + </div> + ) + return ( + <Tooltip + {...tooltipProps} + > + <template slot='title'> + {overlay} + </template> + {this.$slots.default} + </Tooltip> + ) + }, +} + +</script> diff --git a/components/popconfirm/index.zh-CN.md b/components/popconfirm/index.zh-CN.md new file mode 100644 index 000000000..9442b1fac --- /dev/null +++ b/components/popconfirm/index.zh-CN.md @@ -0,0 +1,34 @@ +--- +category: Components +subtitle: 气泡确认框 +type: Feedback +title: Popconfirm +--- + +点击元素,弹出气泡式的确认框。 + +## 何时使用 + +目标元素的操作需要用户进一步的确认时,在目标元素附近弹出浮层提示,询问用户。 + +和 `confirm` 弹出的全屏居中模态对话框相比,交互形式更轻量。 + +## API + +| 参数 | 说明 | 类型 | 默认值 | +| --- | --- | --- | --- | +| cancelText | 取消按钮文字 | string\|function\|slot | 取消 | +| okText | 确认按钮文字 | string\|function\|slot | 确定 | +| okType | 确认按钮类型 | string | primary | +| title | 确认框的描述 | string\|function\|slot | 无 | + +### 事件 +| 事件名称 | 说明 | 回调参数 | +| cancel | 点击取消时触发 | (e) | +| confirm | 点击确认时触发 | (e) | + +更多属性请参考 [Tooltip](/components/tooltip/#API)。 + +## 注意 + +请确保 `Popconfirm` 的子元素能接受 `mouseenter`、`mouseleave`、`focus`、`click` 事件。 diff --git a/components/popconfirm/style/index.js b/components/popconfirm/style/index.js new file mode 100644 index 000000000..a177f207f --- /dev/null +++ b/components/popconfirm/style/index.js @@ -0,0 +1,5 @@ +import '../../style/index.less' + +// style dependencies +import '../../popover/style' +import '../../button/style' diff --git a/components/popover/demo/arrow-point-at-center.vue b/components/popover/demo/arrow-point-at-center.vue new file mode 100644 index 000000000..096f076f5 --- /dev/null +++ b/components/popover/demo/arrow-point-at-center.vue @@ -0,0 +1,42 @@ +<template> +<div> +<md> +## 箭头指向 +设置了 `arrowPointAtCenter` 后,箭头将指向目标元素的中心。 +</md> + <Popover placement="topLeft"> + <template slot="content"> + <div> + <p>Content</p> + <p>Content</p> + </div> + </template> + <template slot="title"> + <span>Title</span> + </template> + <AntButton>Align edge / 边缘对齐</AntButton> + </Popover> + <Popover placement="topLeft" arrowPointAtCenter> + <template slot="content"> + <div> + <p>Content</p> + <p>Content</p> + </div> + </template> + <template slot="title"> + <span>Title</span> + </template> + <AntButton>Arrow points to center / 箭头指向中心</AntButton> + </Popover> +</div> +</template> + +<script> +import { Popover, Button } from 'antd' +export default { + components: { + Popover, + AntButton: Button, + }, +} +</script> diff --git a/components/popover/demo/basic.vue b/components/popover/demo/basic.vue new file mode 100644 index 000000000..20cb76fd3 --- /dev/null +++ b/components/popover/demo/basic.vue @@ -0,0 +1,27 @@ +<template> +<div> +<md> +## 基本 +最简单的用法,浮层的大小由内容区域决定。 +</md> +<Popover title="Title"> + <template slot="content"> + <div> + <p>Content</p> + <p>Content</p> + </div> + </template> + <AntButton type="primary">Hover me</AntButton> + </Popover> +</div> +</template> + +<script> +import { Popover, Button } from 'antd' +export default { + components: { + Popover, + AntButton: Button, + }, +} +</script> diff --git a/components/popover/demo/control.vue b/components/popover/demo/control.vue new file mode 100644 index 000000000..8d8fa9b47 --- /dev/null +++ b/components/popover/demo/control.vue @@ -0,0 +1,39 @@ +<template> +<div> + <md> + ## 从浮层内关闭 + 使用 `visible` 属性控制浮层显示。 + </md> + <Popover + title="Title" + trigger="click" + v-model="visible" + > + <template slot="content"> + <a @click="hide">Close</a> + </template> + <AntButton type="primary">Click me</AntButton> + </Popover> +</div> +</template> + +<script> +import { Popover, Button } from 'antd' +export default { + data () { + return { + visible: false, + } + }, + methods: { + hide () { + console.log(111) + this.visible = false + }, + }, + components: { + Popover, + AntButton: Button, + }, +} +</script> diff --git a/components/popover/demo/index.vue b/components/popover/demo/index.vue new file mode 100644 index 000000000..d6c50e639 --- /dev/null +++ b/components/popover/demo/index.vue @@ -0,0 +1,30 @@ +<template> + <div> + <h1>Basic</h1> + <Basic /> + <h1>ArrowCenter</h1> + <ArrowCenter /> + <h1>Control</h1> + <Control /> + <h1>Placement</h1> + <Placement /> + <h1>TriggerType</h1> + <TriggerType /> + </div> +</template> +<script> +import Basic from './basic' +import ArrowCenter from './arrow-point-at-center' +import Control from './control' +import Placement from './placement' +import TriggerType from './triggerType' +export default { + components: { + Basic, + ArrowCenter, + Control, + Placement, + TriggerType, + }, +} +</script> diff --git a/components/popover/demo/placement.vue b/components/popover/demo/placement.vue new file mode 100644 index 000000000..2c80b16c6 --- /dev/null +++ b/components/popover/demo/placement.vue @@ -0,0 +1,184 @@ +<template> +<div id="components-popover-demo-placement"> +<md> +## 位置 +位置有 12 个方向。 +</md> + <div :style="{ marginLeft: `${buttonWidth}px`, whiteSpace: 'nowrap' }"> + <Popover placement="topLeft"> + <template slot="content"> + <div> + <p>Content</p> + <p>Content</p> + </div> + </template> + <template slot="title"> + <span>Title</span> + </template> + <AntButton>TL</AntButton> + </Popover> + <Popover placement="top"> + <template slot="content"> + <div> + <p>Content</p> + <p>Content</p> + </div> + </template> + <template slot="title"> + <span>Title</span> + </template> + <AntButton>Top</AntButton> + </Popover> + <Popover placement="topRight"> + <template slot="content"> + <div> + <p>Content</p> + <p>Content</p> + </div> + </template> + <template slot="title"> + <span>Title</span> + </template> + <AntButton>TR</AntButton> + </Popover> + </div> + <div :style="{ width: `${buttonWidth}px`, float: 'left' }"> + <Popover placement="leftTop"> + <template slot="content"> + <div> + <p>Content</p> + <p>Content</p> + </div> + </template> + <template slot="title"> + <span>Title</span> + </template> + <AntButton>LT</AntButton> + </Popover> + <Popover placement="left"> + <template slot="content"> + <div> + <p>Content</p> + <p>Content</p> + </div> + </template> + <template slot="title"> + <span>Title</span> + </template> + <AntButton>Left</AntButton> + </Popover> + <Popover placement="leftBottom"> + <template slot="content"> + <div> + <p>Content</p> + <p>Content</p> + </div> + </template> + <template slot="title"> + <span>Title</span> + </template> + <AntButton>LB</AntButton> + </Popover> + </div> + <div :style="{ width: `${buttonWidth}px`, marginLeft: `${buttonWidth * 4 + 24 }px`}"> + <Popover placement="rightTop"> + <template slot="content"> + <div> + <p>Content</p> + <p>Content</p> + </div> + </template> + <template slot="title"> + <span>Title</span> + </template> + <AntButton>RT</AntButton> + </Popover> + <Popover placement="right"> + <template slot="content"> + <div> + <p>Content</p> + <p>Content</p> + </div> + </template> + <template slot="title"> + <span>Title</span> + </template> + <AntButton>Right</AntButton> + </Popover> + <Popover placement="rightBottom"> + <template slot="content"> + <div> + <p>Content</p> + <p>Content</p> + </div> + </template> + <template slot="title"> + <span>Title</span> + </template> + <AntButton>RB</AntButton> + </Popover> + </div> + <div :style="{ marginLeft: `${buttonWidth}px`, clear: 'both', whiteSpace: 'nowrap' }"> + <Popover placement="bottomLeft"> + <template slot="content"> + <div> + <p>Content</p> + <p>Content</p> + </div> + </template> + <template slot="title"> + <span>Title</span> + </template> + <AntButton>BL</AntButton> + </Popover> + <Popover placement="bottom"> + <template slot="content"> + <div> + <p>Content</p> + <p>Content</p> + </div> + </template> + <template slot="title"> + <span>Title</span> + </template> + <AntButton>Bottom</AntButton> + </Popover> + <Popover placement="bottomRight"> + <template slot="content"> + <div> + <p>Content</p> + <p>Content</p> + </div> + </template> + <template slot="title"> + <span>Title</span> + </template> + <AntButton>BR</AntButton> + </Popover> + </div> +</div> +</template> + +<script> +import { Popover, Button } from 'antd' +export default { + data () { + return { + buttonWidth: 70, + } + }, + components: { + Popover, + AntButton: Button, + }, +} +</script> +<style> +#components-popover-demo-placement .ant-btn { + width: 70px; + text-align: center; + padding: 0; + margin-right: 8px; + margin-bottom: 8px; +} +</style> diff --git a/components/popover/demo/triggerType.vue b/components/popover/demo/triggerType.vue new file mode 100644 index 000000000..bbf14fb98 --- /dev/null +++ b/components/popover/demo/triggerType.vue @@ -0,0 +1,45 @@ +<template> +<div> +<md> +## 三种触发方式 +鼠标移入、聚集、点击。 +</md> + <Popover title="Title" trigger="hover"> + <template slot="content"> + <div> + <p>Content</p> + <p>Content</p> + </div> + </template> + <AntButton type="primary">Hover me</AntButton> + </Popover> + <Popover title="Title" trigger="focus"> + <template slot="content"> + <div> + <p>Content</p> + <p>Content</p> + </div> + </template> + <AntButton type="primary">Focus me</AntButton> + </Popover> + <Popover title="Title" trigger="click"> + <template slot="content"> + <div> + <p>Content</p> + <p>Content</p> + </div> + </template> + <AntButton type="primary">Click me</AntButton> + </Popover> +</div> +</template> + +<script> +import { Popover, Button } from 'antd' +export default { + components: { + Popover, + AntButton: Button, + }, +} +</script> diff --git a/components/popover/index.vue b/components/popover/index.vue new file mode 100644 index 000000000..1089d6f0f --- /dev/null +++ b/components/popover/index.vue @@ -0,0 +1,60 @@ +<script> +import Tooltip from '../tooltip' +import abstractTooltipProps from '../tooltip/abstractTooltipProps' +import PropTypes from '../_util/vue-types' +import { getOptionProps, getComponentFromProp } from '../_util/props-util' + +export default { + name: 'popover', + props: { + ...abstractTooltipProps, + prefixCls: PropTypes.string.def('ant-popover'), + transitionName: PropTypes.string.def('zoom-big'), + content: PropTypes.any, + title: PropTypes.any, + }, + model: { + prop: 'visible', + event: 'change', + }, + methods: { + getPopupDomNode () { + return this.$refs.tooltip.getPopupDomNode() + }, + }, + + render (h) { + const { title, prefixCls, content, $slots } = this + const props = getOptionProps(this) + delete props.title + delete props.content + const tooltipProps = { + props: { + ...props, + }, + ref: 'tooltip', + on: this.$listeners, + } + return ( + <Tooltip + {...tooltipProps} + > + <template slot='title'> + <div> + {(title || $slots.title) && + <div class={`${prefixCls}-title`}> + {getComponentFromProp(this, h, 'title')} + </div> + } + <div class={`${prefixCls}-inner-content`}> + {getComponentFromProp(this, h, 'content')} + </div> + </div> + </template> + {this.$slots.default} + </Tooltip> + ) + }, +} + +</script> diff --git a/components/popover/index.zh-CN.md b/components/popover/index.zh-CN.md new file mode 100644 index 000000000..986b670c5 --- /dev/null +++ b/components/popover/index.zh-CN.md @@ -0,0 +1,27 @@ +--- +category: Components +subtitle: 气泡卡片 +type: Data Display +title: Popover +--- + +点击/鼠标移入元素,弹出气泡式的卡片浮层。 + +## 何时使用 + +当目标元素有进一步的描述和相关操作时,可以收纳到卡片中,根据用户的操作行为进行展现。 + +和 `Tooltip` 的区别是,用户可以对浮层上的元素进行操作,因此它可以承载更复杂的内容,比如链接或按钮等。 + +## API + +| 参数 | 说明 | 类型 | 默认值 | +| --- | --- | --- | --- | +| content | 卡片内容 | string\|function\|slot | 无 | +| title | 卡片标题 | string\|function\|slot | 无 | + +更多属性请参考 [Tooltip](/components/tooltip/#API)。 + +## 注意 + +请确保 `Popover` 的子元素能接受 `mouseenter`、`mouseleave`、`focus`、`click` 事件。 diff --git a/components/popover/style/index.js b/components/popover/style/index.js new file mode 100644 index 000000000..cf31ed80f --- /dev/null +++ b/components/popover/style/index.js @@ -0,0 +1,2 @@ +import '../../style/index.less' +import './index.less' diff --git a/components/popover/style/index.less b/components/popover/style/index.less new file mode 100644 index 000000000..227d2bc5a --- /dev/null +++ b/components/popover/style/index.less @@ -0,0 +1,221 @@ +@import "../../style/themes/default"; +@import "../../style/mixins/index"; + +@popover-prefix-cls: ~"@{ant-prefix}-popover"; + +.@{popover-prefix-cls} { + position: absolute; + top: 0; + left: 0; + z-index: @zindex-popover; + cursor: auto; + user-select: text; + white-space: normal; + font-size: @font-size-base; + line-height: @line-height-base; + font-weight: normal; + text-align: left; + + &:after { + content: ""; + position: absolute; + background: rgba(255, 255, 255, 0.01); + } + + &-hidden { + display: none; + } + + // Offset the popover to account for the popover arrow + &-placement-top, + &-placement-topLeft, + &-placement-topRight { + padding-bottom: @popover-distance; + } + + &-placement-right, + &-placement-rightTop, + &-placement-rightBottom { + padding-left: @popover-distance; + } + + &-placement-bottom, + &-placement-bottomLeft, + &-placement-bottomRight { + padding-top: @popover-distance; + } + + &-placement-left, + &-placement-leftTop, + &-placement-leftBottom { + padding-right: @popover-distance; + } + + &-inner { + background-color: @popover-bg; + background-clip: padding-box; + border-radius: @border-radius-base; + box-shadow: @box-shadow-base; + } + + &-title { + min-width: @popover-min-width; + margin: 0; // reset heading margin + padding: 8px 16px; + min-height: 32px; + border-bottom: 1px solid @border-color-split; + color: @popover-color; + font-weight: 500; + } + + &-inner-content { + padding: 8px 16px; + color: @popover-color; + } + + &-message { + padding: 8px 0 16px; + font-size: @font-size-base; + color: @popover-color; + > .@{iconfont-css-prefix} { + color: @warning-color; + line-height: 17px; + position: absolute; + } + &-title { + padding-left: 20px; + } + } + + &-buttons { + text-align: right; + margin-bottom: 8px; + button { + margin-left: 8px; + } + } + + // Arrows + // .popover-arrow is outer, .popover-arrow:after is inner + + &-arrow { + &, + &:after { + position: absolute; + display: block; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; + } + } + + &-arrow { + border-width: @popover-arrow-outer-width; + } + + &-arrow:after { + border-width: @popover-arrow-width; + content: ""; + } + + &-placement-top > &-content > &-arrow, + &-placement-topLeft > &-content > &-arrow, + &-placement-topRight > &-content > &-arrow { + border-bottom-width: 0; + border-top-color: @popover-arrow-outer-color; + bottom: @popover-distance - @popover-arrow-outer-width; + &:after { + content: " "; + bottom: 1px; + margin-left: -@popover-arrow-width; + border-bottom-width: 0; + border-top-color: @popover-arrow-color; + } + } + &-placement-top > &-content > &-arrow { + left: 50%; + margin-left: -@popover-arrow-outer-width; + } + &-placement-topLeft > &-content > &-arrow { + left: 16px; + } + &-placement-topRight > &-content > &-arrow { + right: 16px; + } + + &-placement-right > &-content > &-arrow, + &-placement-rightTop > &-content > &-arrow, + &-placement-rightBottom > &-content > &-arrow { + left: @popover-distance - @popover-arrow-outer-width; + border-left-width: 0; + border-right-color: @popover-arrow-outer-color; + &:after { + content: " "; + left: 1px; + bottom: -@popover-arrow-width; + border-left-width: 0; + border-right-color: @popover-arrow-color; + } + } + &-placement-right > &-content > &-arrow { + top: 50%; + margin-top: -@popover-arrow-outer-width; + } + &-placement-rightTop > &-content > &-arrow { + top: 12px; + } + &-placement-rightBottom > &-content > &-arrow { + bottom: 12px; + } + + &-placement-bottom > &-content > &-arrow, + &-placement-bottomLeft > &-content > &-arrow, + &-placement-bottomRight > &-content > &-arrow { + border-top-width: 0; + border-bottom-color: @popover-arrow-outer-color; + top: @popover-distance - @popover-arrow-outer-width; + &:after { + content: " "; + top: 1px; + margin-left: -@popover-arrow-width; + border-top-width: 0; + border-bottom-color: @popover-arrow-color; + } + } + &-placement-bottom > &-content > &-arrow { + left: 50%; + margin-left: -@popover-arrow-outer-width; + } + &-placement-bottomLeft > &-content > &-arrow { + left: 16px; + } + &-placement-bottomRight > &-content > &-arrow { + right: 16px; + } + + &-placement-left > &-content > &-arrow, + &-placement-leftTop > &-content > &-arrow, + &-placement-leftBottom > &-content > &-arrow { + right: @popover-distance - @popover-arrow-outer-width; + border-right-width: 0; + border-left-color: @popover-arrow-outer-color; + &:after { + content: " "; + right: 1px; + border-right-width: 0; + border-left-color: @popover-arrow-color; + bottom: -@popover-arrow-width; + } + } + &-placement-left > &-content > &-arrow { + top: 50%; + margin-top: -@popover-arrow-outer-width; + } + &-placement-leftTop > &-content > &-arrow { + top: 12px; + } + &-placement-leftBottom > &-content > &-arrow { + bottom: 12px; + } +} diff --git a/components/radio/Radio.vue b/components/radio/Radio.vue index 60e4b6bfe..616f3c765 100644 --- a/components/radio/Radio.vue +++ b/components/radio/Radio.vue @@ -13,7 +13,7 @@ </label> </template> <script> -import hasProp from '../_util/hasProp' +import hasProp from '../_util/props-util' export default { name: 'Radio', props: { diff --git a/components/rate/Rate.vue b/components/rate/Rate.vue index 7f22e1144..4ccedf738 100644 --- a/components/rate/Rate.vue +++ b/components/rate/Rate.vue @@ -3,7 +3,7 @@ import Star from './Star.vue' import Icon from '../icon' import { getOffsetLeft } from './util' import { cloneVNodes } from '../_util/vnode' -import hasProp from '../_util/hasProp' +import hasProp from '../_util/props-util' export default { name: 'Rate', diff --git a/components/style.js b/components/style.js index ec801667d..108572eed 100644 --- a/components/style.js +++ b/components/style.js @@ -10,5 +10,8 @@ import './avatar/style' import './badge/style' import './tabs/style' import './input/style' +import './tooltip/style' +import './popover/style' +import './popconfirm/style' import './menu/style' diff --git a/components/tabs/index.js b/components/tabs/index.js index 087cc67eb..b91a2b6fb 100644 --- a/components/tabs/index.js +++ b/components/tabs/index.js @@ -1,6 +1,6 @@ import Tabs from './index.vue' -import TabPane from './TabPane' -import TabContent from './TabContent' +import TabPane from './src/TabPane' +import TabContent from './src/TabContent' Tabs.TabPane = TabPane export default Tabs export { TabPane, TabContent } diff --git a/components/tabs/index.vue b/components/tabs/index.vue index 703e8af1d..aed9aa73b 100644 --- a/components/tabs/index.vue +++ b/components/tabs/index.vue @@ -1,7 +1,7 @@ <script> -import Tabs from './Tabs' +import Tabs from './src/Tabs' import isFlexSupported from '../_util/isFlexSupported' -import hasProp from '../_util/hasProp' +import hasProp from '../_util/props-util' export default { props: { prefixCls: { type: String, default: 'ant-tabs' }, @@ -75,7 +75,6 @@ export default { tabPosition, tabBarStyle, hideAdd, - onTabClick, onPrevClick, onNextClick, animated, @@ -114,36 +113,38 @@ export default { } }) const tabBarProps = { - inkBarAnimated, - onTabClick, - onPrevClick, - onNextClick, + props: { + hideAdd, + removeTab: this.removeTab, + createNewTab: this.createNewTab, + inkBarAnimated, + }, + on: { + // tabClick: onTabClick, + prevClick: onPrevClick, + nextClick: onNextClick, + }, style: tabBarStyle, - hideAdd, - removeTab: this.removeTab, - createNewTab: this.createNewTab, } const tabContentProps = { - animated: tabPaneAnimated, - animatedWithMargin: true, + props: { + animated: tabPaneAnimated, + animatedWithMargin: true, + }, } - const self = this const tabsProps = { props: { prefixCls, tabBarPosition: tabPosition, - onChange: this.handleChange, tabBarProps: tabBarProps, tabContentProps: tabContentProps, destroyInactiveTabPane, defaultActiveKey, type, - onTabClick: this.onTabClick, }, on: { - change (val) { - self.handleChange(val) - }, + change: this.handleChange, + tabClick: this.onTabClick, }, } if (hasProp(this, 'activeKey')) { diff --git a/components/tabs/index.zh-CN b/components/tabs/index.zh-CN.md similarity index 100% rename from components/tabs/index.zh-CN rename to components/tabs/index.zh-CN.md diff --git a/components/tabs/InkTabBar.vue b/components/tabs/src/InkTabBar.vue similarity index 100% rename from components/tabs/InkTabBar.vue rename to components/tabs/src/InkTabBar.vue diff --git a/components/tabs/InkTabBarMixin.js b/components/tabs/src/InkTabBarMixin.js similarity index 100% rename from components/tabs/InkTabBarMixin.js rename to components/tabs/src/InkTabBarMixin.js diff --git a/components/tabs/KeyCode.js b/components/tabs/src/KeyCode.js similarity index 100% rename from components/tabs/KeyCode.js rename to components/tabs/src/KeyCode.js diff --git a/components/tabs/ScrollableInkTabBar.vue b/components/tabs/src/ScrollableInkTabBar.vue similarity index 77% rename from components/tabs/ScrollableInkTabBar.vue rename to components/tabs/src/ScrollableInkTabBar.vue index f04450b9f..5a65407d0 100644 --- a/components/tabs/ScrollableInkTabBar.vue +++ b/components/tabs/src/ScrollableInkTabBar.vue @@ -2,10 +2,11 @@ import InkTabBarMixin from './InkTabBarMixin' import ScrollableTabBarMixin from './ScrollableTabBarMixin' import TabBarMixin from './TabBarMixin' +import BaseMixin from '../../_util/BaseMixin' export default { name: 'ScrollableInkTabBar', - mixins: [TabBarMixin, InkTabBarMixin, ScrollableTabBarMixin], + mixins: [TabBarMixin, InkTabBarMixin, ScrollableTabBarMixin, BaseMixin], render (h) { const inkBarNode = this.getInkBarNode() const tabs = this.getTabs(h) diff --git a/components/tabs/ScrollableTabBar.vue b/components/tabs/src/ScrollableTabBar.vue similarity index 78% rename from components/tabs/ScrollableTabBar.vue rename to components/tabs/src/ScrollableTabBar.vue index 7abc2d748..7b38d9637 100644 --- a/components/tabs/ScrollableTabBar.vue +++ b/components/tabs/src/ScrollableTabBar.vue @@ -1,10 +1,11 @@ <script> import ScrollableTabBarMixin from './ScrollableTabBarMixin' import TabBarMixin from './TabBarMixin' +import BaseMixin from '../../_util/BaseMixin' export default { name: 'ScrollableTabBar', - mixins: [TabBarMixin, ScrollableTabBarMixin], + mixins: [TabBarMixin, ScrollableTabBarMixin, BaseMixin], render (h) { const inkBarNode = this.getInkBarNode() const tabs = this.getTabs(h) diff --git a/components/tabs/ScrollableTabBarMixin.js b/components/tabs/src/ScrollableTabBarMixin.js similarity index 97% rename from components/tabs/ScrollableTabBarMixin.js rename to components/tabs/src/ScrollableTabBarMixin.js index 6df86318b..d872ad5aa 100644 --- a/components/tabs/ScrollableTabBarMixin.js +++ b/components/tabs/src/ScrollableTabBarMixin.js @@ -6,8 +6,6 @@ function noop () { export default { props: { scrollAnimated: { type: Boolean, default: true }, - onPrevClick: { type: Function, default: noop }, - onNextClick: { type: Function, default: noop }, }, data () { @@ -201,7 +199,7 @@ export default { }, prevClick (e) { - this.$props.onPrevClick(e) + this.__emit('prevClick', e) const navWrapNode = this.$refs.navWrap const navWrapNodeWH = this.getOffsetWH(navWrapNode) const { offset } = this @@ -209,7 +207,7 @@ export default { }, nextClick (e) { - this.$props.onNextClick(e) + this.__emit('nextClick', e) const navWrapNode = this.$refs.navWrap const navWrapNodeWH = this.getOffsetWH(navWrapNode) const { offset } = this diff --git a/components/tabs/TabBar.vue b/components/tabs/src/TabBar.vue similarity index 69% rename from components/tabs/TabBar.vue rename to components/tabs/src/TabBar.vue index 423f97cc7..8e65f6a7d 100644 --- a/components/tabs/TabBar.vue +++ b/components/tabs/src/TabBar.vue @@ -1,9 +1,8 @@ <script> import TabBarMixin from './TabBarMixin' -function noop () { -} +import BaseMixin from '../../_util/BaseMixin' export default { - mixins: [TabBarMixin], + mixins: [TabBarMixin, BaseMixin], name: 'TabBar', props: { prefixCls: { @@ -15,14 +14,6 @@ export default { type: String, }, disabled: Boolean, - onKeyDown: { - default: noop, - type: Function, - }, - onTabClick: { - default: noop, - type: Function, - }, activeKey: String, panels: Array, }, diff --git a/components/tabs/TabBarMixin.js b/components/tabs/src/TabBarMixin.js similarity index 94% rename from components/tabs/TabBarMixin.js rename to components/tabs/src/TabBarMixin.js index 5ebb0028d..56fc9b9cb 100644 --- a/components/tabs/TabBarMixin.js +++ b/components/tabs/src/TabBarMixin.js @@ -1,4 +1,4 @@ -import Icon from '../icon' +import Icon from '../../icon' function noop () { } export default { @@ -12,14 +12,6 @@ export default { type: String, }, disabled: Boolean, - onKeyDown: { - default: noop, - type: Function, - }, - onTabClick: { - default: noop, - type: Function, - }, activeKey: String, panels: Array, hideAdd: Boolean, @@ -51,7 +43,7 @@ export default { } else { } const onClick = () => { - !disabled && this.onTabClick(tabKey) + !disabled && this.__emit('tabClick', tabKey) } let tabC = typeof tab === 'function' ? child.tab(h, tabKey) : tab @@ -86,6 +78,9 @@ export default { return rst }, + onKeyDown (e) { + this.__emit('keydown', e) + }, getRootNode (contents, createElement) { const { prefixCls, onKeyDown, tabBarPosition, hideAdd, diff --git a/components/tabs/TabContent.vue b/components/tabs/src/TabContent.vue similarity index 100% rename from components/tabs/TabContent.vue rename to components/tabs/src/TabContent.vue diff --git a/components/tabs/TabPane.vue b/components/tabs/src/TabPane.vue similarity index 100% rename from components/tabs/TabPane.vue rename to components/tabs/src/TabPane.vue diff --git a/components/tabs/Tabs.vue b/components/tabs/src/Tabs.vue similarity index 91% rename from components/tabs/Tabs.vue rename to components/tabs/src/Tabs.vue index 1a80817e8..babb0f479 100644 --- a/components/tabs/Tabs.vue +++ b/components/tabs/src/Tabs.vue @@ -1,9 +1,10 @@ <script> -import Icon from '../icon' +import Icon from '../../icon' import KeyCode from './KeyCode' import TabContent from './TabContent' import ScrollableInkTabBar from './ScrollableInkTabBar' -import hasProp from '../_util/hasProp' +import hasProp from '../../_util/props-util' +import BaseMixin from '../../_util/BaseMixin' function getDefaultActiveKey (t) { let activeKey t.$slots.default && t.$slots.default.forEach(({ componentOptions = {}, key: tabKey }) => { @@ -23,8 +24,7 @@ function activeKeyIsValid (t, key) { }) return key !== undefined && keys.indexOf(key) >= 0 } -function noop () { -} + export default { name: 'Tabs', components: { Icon }, @@ -32,6 +32,7 @@ export default { prop: 'activeKey', event: 'change', }, + mixins: [BaseMixin], props: { prefixCls: { default: 'ant-tabs', @@ -53,8 +54,6 @@ export default { return ['line', 'card', 'editable-card'].includes(value) }, }, - onChange: { type: Function, default: noop }, - onTabClick: { type: Function, default: noop }, }, data () { return { @@ -90,7 +89,7 @@ export default { return activeKey }, handleTabClick (activeKey) { - this.onTabClick(activeKey) + this.__emit('tabClick', activeKey) this.setActiveKey(activeKey) }, @@ -112,7 +111,7 @@ export default { if (!hasProp(this, 'activeKey')) { this.stateActiveKey = activeKey } - this.onChange(activeKey) + this.__emit('change', activeKey) } }, @@ -172,25 +171,31 @@ export default { }) const tabContentProps = { props: { - ...this.tabContentProps, + ...this.tabContentProps.props, prefixCls, tabBarPosition, activeKey: stateActiveKey, destroyInactiveTabPane, - onChange: setActiveKey, + // onChange: setActiveKey, + }, + on: { + change: setActiveKey, }, } const tabBarProps = { props: { - ...this.tabBarProps, + ...this.tabBarProps.props, panels: panels, prefixCls: prefixCls, - onKeyDown: onNavKeyDown, tabBarPosition: tabBarPosition, - onTabClick: handleTabClick, activeKey: stateActiveKey, }, style: this.tabBarProps.style || {}, + on: { + ...this.tabBarProps.on, + keydown: onNavKeyDown, + tabClick: handleTabClick, + }, } const contents = [ <ScrollableInkTabBar diff --git a/components/tabs/utils.js b/components/tabs/src/utils.js similarity index 100% rename from components/tabs/utils.js rename to components/tabs/src/utils.js diff --git a/components/tooltip/abstractTooltipProps.js b/components/tooltip/abstractTooltipProps.js new file mode 100644 index 000000000..9faf70877 --- /dev/null +++ b/components/tooltip/abstractTooltipProps.js @@ -0,0 +1,19 @@ +import PropTypes from '../_util/vue-types' +export default { + trigger: PropTypes.oneOf(['hover', 'focus', 'click']).def('hover'), + visible: PropTypes.bool, + placement: PropTypes.oneOf(['top', 'left', 'right', 'bottom', + 'topLeft', 'topRight', 'bottomLeft', 'bottomRight', + 'leftTop', 'leftBottom', 'rightTop', 'rightBottom']).def('top'), + transitionName: PropTypes.string.def('zoom-big-fast'), + // onVisibleChange: PropTypes.func, + overlayStyle: PropTypes.object.def({}), + overlayClassName: PropTypes.string, + prefixCls: PropTypes.string.def('ant-tooltip'), + mouseEnterDelay: PropTypes.number.def(0.1), + mouseLeaveDelay: PropTypes.number.def(0.1), + getTooltipContainer: PropTypes.func, + getPopupContainer: PropTypes.func, + arrowPointAtCenter: PropTypes.bool.def(false), + autoAdjustOverflow: PropTypes.oneOfType([PropTypes.bool, PropTypes.object]).def(true), +} diff --git a/components/tooltip/demo/arrow-point-at-center.vue b/components/tooltip/demo/arrow-point-at-center.vue new file mode 100644 index 000000000..643386649 --- /dev/null +++ b/components/tooltip/demo/arrow-point-at-center.vue @@ -0,0 +1,24 @@ +<template> +<div> +<md> +## 箭头指向 +设置了 `arrowPointAtCenter` 后,箭头将指向目标元素的中心。 +</md> + <Tooltip placement="topLeft" title="Prompt Text"> + <AntButton>Align edge / 边缘对齐</AntButton> + </Tooltip> + <Tooltip placement="topLeft" title="Prompt Text" arrowPointAtCenter> + <AntButton>Arrow points to center / 箭头指向中心</AntButton> + </Tooltip> +</div> +</template> + +<script> +import { Tooltip, Button } from 'antd' +export default { + components: { + Tooltip, + AntButton: Button, + }, +} +</script> diff --git a/components/tooltip/demo/auto-adjust-overflow.vue b/components/tooltip/demo/auto-adjust-overflow.vue new file mode 100644 index 000000000..585815999 --- /dev/null +++ b/components/tooltip/demo/auto-adjust-overflow.vue @@ -0,0 +1,43 @@ +<template> +<div> + <md> + ## 自动调整位置 + 气泡框不可见时自动调整位置。 + </md> + <div :style="wrapStyles"> + <Tooltip placement="left" title="Prompt Text" :getPopupContainer="getPopupContainer"> + <AntButton>Adjust automatically / 自动调整</AntButton> + </Tooltip> + <br /> + <Tooltip placement="left" title="Prompt Text" :getPopupContainer="getPopupContainer" :autoAdjustOverflow="false"> + <AntButton>Ingore / 不处理</AntButton> + </Tooltip> + </div> +</div> +</template> + +<script> +import { Tooltip, Button } from 'antd' +const wrapStyles = { + overflow: 'hidden', + position: 'relative', + padding: '24px', + border: '1px solid #e9e9e9', +} +export default { + data () { + return { + wrapStyles, + } + }, + methods: { + getPopupContainer (trigger) { + return trigger.parentElement + }, + }, + components: { + Tooltip, + AntButton: Button, + }, +} +</script> diff --git a/components/tooltip/demo/basic.vue b/components/tooltip/demo/basic.vue new file mode 100644 index 000000000..b60af9327 --- /dev/null +++ b/components/tooltip/demo/basic.vue @@ -0,0 +1,23 @@ +<template> +<div> +<md> +## 基本 +最简单的用法。 +</md> +<Tooltip> + <template slot='title'> + prompt text + </template> + Tooltip will show when mouse enter. +</Tooltip> +</div> +</template> + +<script> +import { Tooltip } from 'antd' +export default { + components: { + Tooltip, + }, +} +</script> diff --git a/components/tooltip/demo/index.vue b/components/tooltip/demo/index.vue index 178db3bff..b8c54ed5b 100644 --- a/components/tooltip/demo/index.vue +++ b/components/tooltip/demo/index.vue @@ -1,97 +1,26 @@ <template> <div> - <tool-tip - placement="top" - :title="showText" - :autoAdjustOverflow="autoAdjustOverflow" - > - <h1 @click="boom" class="test">撞到边缘翻转位置 & 点击更新</h1> - </tool-tip> - <ant-button @click="reverse" type="primary">{{autoAdjustOverflow ? '启用' : '关闭'}}自动调整中</ant-button> - <div class="box"> - <h2>切换arrowPointAtCenter模式</h2> - <ant-button @click="change">{{arrowPointAtCenter}}</ant-button> - <table> - <tr v-for="(tr, index) in table" :key="index"> - <td v-for="(td, i) in tr" :key="i"> - <tool-tip - v-if="td" - :placement="td" - :title="td" - :arrowPointAtCenter="arrowPointAtCenter" - > - <AntButton type="primary">{{td}}</AntButton> - </tool-tip> - </td> - </tr> - </table> - </div> - <div> - <p> - <tool-tip :arrowPointAtCenter="true" title="Consider using the NamedModulesPlugin for module names." placement="topLeft"> - <ant-button>arrowPointAtCenter arrowPointAtCenter arrowPointAtCenter</ant-button> - </tool-tip> - </p> - </div> + <h1>Basic</h1> + <Basic /> + <h1>ArrowCenter</h1> + <ArrowCenter /> + <h1>AutoAdjust</h1> + <AutoAdjust /> + <h1>Placement</h1> + <Placement /> </div> </template> - <script> - import { ToolTip, Button } from 'antd' - import 'antd/button/style' - export default { - name: 'tooltip-basic', - data() { - return { - show: true, - showText: '你好啊,233', - table: [ - ['', 'topLeft', 'top', 'topRight', ''], - ['leftTop', '', '', '', 'rightTop'], - ['left', '', '', '', 'right'], - ['leftBottom', '', '', '', 'rightBottom'], - ['', 'bottomLeft', 'bottom', 'bottomRight', ''], - ], - arrowPointAtCenter: false, - autoAdjustOverflow: true, - } - }, - methods: { - boom() { - if (this.showText.length % 20) { - this.showText += '3' - } else { - this.showText += ' ' - } - }, - change() { - this.arrowPointAtCenter = !this.arrowPointAtCenter - }, - reverse() { - this.autoAdjustOverflow = !this.autoAdjustOverflow - } - }, - components: { - ToolTip, - AntButton: Button, - } - } +import Basic from './basic' +import ArrowCenter from './arrow-point-at-center' +import AutoAdjust from './auto-adjust-overflow' +import Placement from './placement' +export default { + components: { + Basic, + ArrowCenter, + AutoAdjust, + Placement, + }, +} </script> -<style scoped lang="less"> - .test { - margin: 20px; - display: inline-block; - } - .box { - margin: 100px; - } - table { - td { - padding: 20px; - } - p { - text-align: center; - vertical-align: middle; - } - } -</style> diff --git a/components/tooltip/demo/placement.vue b/components/tooltip/demo/placement.vue new file mode 100644 index 000000000..36aec0438 --- /dev/null +++ b/components/tooltip/demo/placement.vue @@ -0,0 +1,112 @@ +<template> +<div id="components-tooltip-demo-placement"> +<md> +## 位置 +位置有 12 个方向。 +</md> + <div :style="{ marginLeft: `${buttonWidth}px`, whiteSpace: 'nowrap' }"> + <Tooltip placement="topLeft"> + <template slot="title"> + <span>prompt text</span> + </template> + <AntButton>TL</AntButton> + </Tooltip> + <Tooltip placement="top"> + <template slot="title"> + <span>prompt text</span> + </template> + <AntButton>Top</AntButton> + </Tooltip> + <Tooltip placement="topRight"> + <template slot="title"> + <span>prompt text</span> + </template> + <AntButton>TR</AntButton> + </Tooltip> + </div> + <div :style="{ width: `${buttonWidth}px`, float: 'left' }"> + <Tooltip placement="leftTop"> + <template slot="title"> + <span>prompt text</span> + </template> + <AntButton>LT</AntButton> + </Tooltip> + <Tooltip placement="left"> + <template slot="title"> + <span>prompt text</span> + </template> + <AntButton>Left</AntButton> + </Tooltip> + <Tooltip placement="leftBottom"> + <template slot="title"> + <span>prompt text</span> + </template> + <AntButton>LB</AntButton> + </Tooltip> + </div> + <div :style="{ width: `${buttonWidth}px`, marginLeft: `${buttonWidth * 4 + 24 }px`}"> + <Tooltip placement="rightTop"> + <template slot="title"> + <span>prompt text</span> + </template> + <AntButton>RT</AntButton> + </Tooltip> + <Tooltip placement="right"> + <template slot="title"> + <span>prompt text</span> + </template> + <AntButton>Right</AntButton> + </Tooltip> + <Tooltip placement="rightBottom"> + <template slot="title"> + <span>prompt text</span> + </template> + <AntButton>RB</AntButton> + </Tooltip> + </div> + <div :style="{ marginLeft: `${buttonWidth}px`, clear: 'both', whiteSpace: 'nowrap' }"> + <Tooltip placement="bottomLeft"> + <template slot="title"> + <span>prompt text</span> + </template> + <AntButton>BL</AntButton> + </Tooltip> + <Tooltip placement="bottom"> + <template slot="title"> + <span>prompt text</span> + </template> + <AntButton>Bottom</AntButton> + </Tooltip> + <Tooltip placement="bottomRight"> + <template slot="title"> + <span>prompt text</span> + </template> + <AntButton>BR</AntButton> + </Tooltip> + </div> +</div> +</template> + +<script> +import { Tooltip, Button } from 'antd' +export default { + data () { + return { + buttonWidth: 70, + } + }, + components: { + Tooltip, + AntButton: Button, + }, +} +</script> +<style> +#components-tooltip-demo-placement .ant-btn { + width: 70px; + text-align: center; + padding: 0; + margin-right: 8px; + margin-bottom: 8px; +} +</style> diff --git a/components/tooltip/index.js b/components/tooltip/index.js index b93dd566a..4b5f7d8bc 100644 --- a/components/tooltip/index.js +++ b/components/tooltip/index.js @@ -1,4 +1,3 @@ import ToolTip from './tooltip.vue' -import './style' export default ToolTip diff --git a/components/tooltip/index.zh-CN.md b/components/tooltip/index.zh-CN.md new file mode 100644 index 000000000..9cecf0f03 --- /dev/null +++ b/components/tooltip/index.zh-CN.md @@ -0,0 +1,45 @@ +--- +category: Components +subtitle: 文字提示 +type: Data Display +title: Tooltip +--- + +简单的文字提示气泡框。 + +## 何时使用 + +鼠标移入则显示提示,移出消失,气泡浮层不承载复杂文本和操作。 + +可用来代替系统默认的 `title` 提示,提供一个`按钮/文字/操作`的文案解释。 + +## API + +| 参数 | 说明 | 类型 | 默认值 | +| --- | --- | --- | --- | +| title | 提示文字 | `string` `function` `slot` | 无 | + +### 共同的 API + +以下 API 为 Tooltip、Popconfirm、Popover 共享的 API。 + +| 参数 | 说明 | 类型 | 默认值 | +| --- | --- | --- | --- | +| arrowPointAtCenter | 箭头是否指向目标元素中 | boolean | `false` | +| autoAdjustOverflow | 气泡被遮挡时自动调整位置 | boolean | `true` | +| getPopupContainer | 浮层渲染父节点,默认渲染到 body 上 | Function(triggerNode) | () => document.body | +| mouseEnterDelay | 鼠标移入后延时多少才显示 Tooltip,单位:秒 | number | 0 | +| mouseLeaveDelay | 鼠标移出后延时多少才隐藏 Tooltip,单位:秒 | number | 0.1 | +| overlayClassName | 卡片类名 | string | 无 | +| overlayStyle | 卡片样式 | object | 无 | +| placement | 气泡框位置,可选 `top` `left` `right` `bottom` `topLeft` `topRight` `bottomLeft` `bottomRight` `leftTop` `leftBottom` `rightTop` `rightBottom` | string | top | +| trigger | 触发行为,可选 `hover/focus/click` | string | hover | +| visible(v-model) | 用于手动控制浮层显隐 | boolean | false | + +### 事件 +| 事件名称 | 说明 | 回调参数 | +| visibleChange | 显示隐藏变换时触发 | (visible) | + +## 注意 + +请确保 `Tooltip` 的子元素能接受 `mouseenter`、`mouseleave`、`focus`、`click` 事件。 diff --git a/components/tooltip/placements.js b/components/tooltip/placements.js new file mode 100644 index 000000000..f3b6bd8e8 --- /dev/null +++ b/components/tooltip/placements.js @@ -0,0 +1,88 @@ +import { placements as rcPlacements } from './src/placements' + +const autoAdjustOverflowEnabled = { + adjustX: 1, + adjustY: 1, +} + +const autoAdjustOverflowDisabled = { + adjustX: 0, + adjustY: 0, +} + +const targetOffset = [0, 0] + +export function getOverflowOptions (autoAdjustOverflow) { + if (typeof autoAdjustOverflow === 'boolean') { + return autoAdjustOverflow ? autoAdjustOverflowEnabled : autoAdjustOverflowDisabled + } + return { + ...autoAdjustOverflowDisabled, + ...autoAdjustOverflow, + } +} + +export default function getPlacements (config) { + const { arrowWidth = 5, horizontalArrowShift = 16, verticalArrowShift = 12, autoAdjustOverflow = true } = config + const placementMap = { + left: { + points: ['cr', 'cl'], + offset: [-4, 0], + }, + right: { + points: ['cl', 'cr'], + offset: [4, 0], + }, + top: { + points: ['bc', 'tc'], + offset: [0, -4], + }, + bottom: { + points: ['tc', 'bc'], + offset: [0, 4], + }, + topLeft: { + points: ['bl', 'tc'], + offset: [-(horizontalArrowShift + arrowWidth), -4], + }, + leftTop: { + points: ['tr', 'cl'], + offset: [-4, -(verticalArrowShift + arrowWidth)], + }, + topRight: { + points: ['br', 'tc'], + offset: [horizontalArrowShift + arrowWidth, -4], + }, + rightTop: { + points: ['tl', 'cr'], + offset: [4, -(verticalArrowShift + arrowWidth)], + }, + bottomRight: { + points: ['tr', 'bc'], + offset: [horizontalArrowShift + arrowWidth, 4], + }, + rightBottom: { + points: ['bl', 'cr'], + offset: [4, verticalArrowShift + arrowWidth], + }, + bottomLeft: { + points: ['tl', 'bc'], + offset: [-(horizontalArrowShift + arrowWidth), 4], + }, + leftBottom: { + points: ['br', 'cl'], + offset: [-4, verticalArrowShift + arrowWidth], + }, + } + Object.keys(placementMap).forEach(key => { + placementMap[key] = config.arrowPointAtCenter ? { + ...placementMap[key], + overflow: getOverflowOptions(autoAdjustOverflow), + targetOffset, + } : { + ...rcPlacements[key], + overflow: getOverflowOptions(autoAdjustOverflow), + } + }) + return placementMap +} diff --git a/components/tooltip/src/Tooltip.vue b/components/tooltip/src/Tooltip.vue new file mode 100644 index 000000000..67577d786 --- /dev/null +++ b/components/tooltip/src/Tooltip.vue @@ -0,0 +1,101 @@ +<script> +import PropTypes from '../../_util/vue-types' +import Trigger from '../../trigger' +import { placements } from './placements' +import hasProp from '../../_util/props-util' +function noop () {} +export default { + props: { + trigger: PropTypes.any.def('hover'), + defaultVisible: PropTypes.bool, + visible: PropTypes.bool, + placement: PropTypes.string.def('right'), + transitionName: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.object, + ]), + animation: PropTypes.any, + afterVisibleChange: PropTypes.func.def(() => {}), + overlay: PropTypes.any, + overlayStyle: PropTypes.object, + overlayClassName: PropTypes.string, + prefixCls: PropTypes.string.def('rc-tooltip'), + mouseEnterDelay: PropTypes.number.def(0), + mouseLeaveDelay: PropTypes.number.def(0.1), + getTooltipContainer: PropTypes.func, + destroyTooltipOnHide: PropTypes.bool.def(false), + align: PropTypes.object.def({}), + arrowContent: PropTypes.any.def(null), + tipId: PropTypes.string, + builtinPlacements: PropTypes.object, + }, + methods: { + getPopupElement (h) { + const { arrowContent, overlay, prefixCls, tipId } = this.$props + return ([ + <div class={`${prefixCls}-arrow`} key='arrow'> + {this.$slots.arrowContent} + {typeof arrowContent === 'function' ? arrowContent(h) : arrowContent} + </div>, + <div class={`${prefixCls}-inner`} key='content' id={tipId}> + {typeof overlay === 'function' ? overlay(h) : overlay} + {this.$slots.overlay} + </div>, + ]) + }, + + getPopupDomNode () { + return this.$refs.trigger.getPopupDomNode() + }, + }, + render (h) { + const { + overlayClassName, trigger, + mouseEnterDelay, mouseLeaveDelay, + overlayStyle, prefixCls, + afterVisibleChange, + transitionName, animation, + placement, align, + destroyTooltipOnHide, + defaultVisible, getTooltipContainer, + ...restProps + } = this.$props + const extraProps = { ...restProps } + if (hasProp(this, 'visible')) { + extraProps.popupVisible = this.$props.visible + } + const triggerProps = { + props: { + popupClassName: overlayClassName, + prefixCls: prefixCls, + action: trigger, + builtinPlacements: placements, + popupPlacement: placement, + popupAlign: align, + getPopupContainer: getTooltipContainer, + afterPopupVisibleChange: afterVisibleChange, + popupTransitionName: transitionName, + popupAnimation: animation, + defaultPopupVisible: defaultVisible, + destroyPopupOnHide: destroyTooltipOnHide, + mouseLeaveDelay: mouseLeaveDelay, + popupStyle: overlayStyle, + mouseEnterDelay: mouseEnterDelay, + ...extraProps, + }, + on: { + popupVisibleChange: this.$listeners.visibleChange || noop, + popupAlign: this.$listeners.popupAlign || noop, + }, + ref: 'trigger', + } + return (<Trigger {...triggerProps}> + <template slot='popup'> + {this.getPopupElement(h)} + </template> + {this.$slots.default} + </Trigger>) + }, +} + +</script> diff --git a/components/tooltip/src/index.js b/components/tooltip/src/index.js new file mode 100644 index 000000000..331785904 --- /dev/null +++ b/components/tooltip/src/index.js @@ -0,0 +1,3 @@ +import Tooltip from './Tooltip' + +export default Tooltip diff --git a/components/tooltip/src/placements.js b/components/tooltip/src/placements.js new file mode 100644 index 000000000..80917822e --- /dev/null +++ b/components/tooltip/src/placements.js @@ -0,0 +1,83 @@ +const autoAdjustOverflow = { + adjustX: 1, + adjustY: 1, +} + +const targetOffset = [0, 0] + +export const placements = { + left: { + points: ['cr', 'cl'], + overflow: autoAdjustOverflow, + offset: [-4, 0], + targetOffset, + }, + right: { + points: ['cl', 'cr'], + overflow: autoAdjustOverflow, + offset: [4, 0], + targetOffset, + }, + top: { + points: ['bc', 'tc'], + overflow: autoAdjustOverflow, + offset: [0, -4], + targetOffset, + }, + bottom: { + points: ['tc', 'bc'], + overflow: autoAdjustOverflow, + offset: [0, 4], + targetOffset, + }, + topLeft: { + points: ['bl', 'tl'], + overflow: autoAdjustOverflow, + offset: [0, -4], + targetOffset, + }, + leftTop: { + points: ['tr', 'tl'], + overflow: autoAdjustOverflow, + offset: [-4, 0], + targetOffset, + }, + topRight: { + points: ['br', 'tr'], + overflow: autoAdjustOverflow, + offset: [0, -4], + targetOffset, + }, + rightTop: { + points: ['tl', 'tr'], + overflow: autoAdjustOverflow, + offset: [4, 0], + targetOffset, + }, + bottomRight: { + points: ['tr', 'br'], + overflow: autoAdjustOverflow, + offset: [0, 4], + targetOffset, + }, + rightBottom: { + points: ['bl', 'br'], + overflow: autoAdjustOverflow, + offset: [4, 0], + targetOffset, + }, + bottomLeft: { + points: ['tl', 'bl'], + overflow: autoAdjustOverflow, + offset: [0, 4], + targetOffset, + }, + leftBottom: { + points: ['br', 'bl'], + overflow: autoAdjustOverflow, + offset: [-4, 0], + targetOffset, + }, +} + +export default placements diff --git a/components/tooltip/tooltip.vue b/components/tooltip/tooltip.vue index 97065950d..c66fd4730 100644 --- a/components/tooltip/tooltip.vue +++ b/components/tooltip/tooltip.vue @@ -1,121 +1,127 @@ <script> -import Vue from 'vue' +import { cloneElement, isValidElement, getClass, getStyle } from '../_util/vnode' +import RcTooltip from './src/tooltip' +import getPlacements from './placements' +import PropTypes from '../_util/vue-types' +import hasProp from '../_util/props-util' +import abstractTooltipProps from './abstractTooltipProps' + +const splitObject = (obj, keys) => { + const picked = {} + const omited = { ...obj } + keys.forEach(key => { + if (obj && key in obj) { + picked[key] = obj[key] + delete omited[key] + } + }) + return { picked, omited } +} + export default { - name: 'ToolTip', + name: 'Tooltip', props: { - title: String, - prefixCls: { - default: 'ant-tooltip', - }, - placement: { - default: 'top', - validator: val => ['top', 'left', 'right', 'bottom', 'topLeft', 'topRight', 'bottomLeft', 'bottomRight', 'leftTop', 'leftBottom', 'rightTop', 'rightBottom'].includes(val), - }, - transitionName: { - default: 'zoom-big-fast', - }, - mouseEnterDelay: { - default: 0.1, - }, - mouseLeaveDelay: { - default: 0.1, - }, - arrowPointAtCenter: { - default: false, - }, - autoAdjustOverflow: { - default: true, - }, + ...abstractTooltipProps, + title: PropTypes.any, + }, + model: { + prop: 'visible', + event: 'change', }, data () { return { - vnode: null, - visible: false, - left: 0, - top: 0, - realPlacement: this.placement, - t1: null, - t2: null, + sVisible: !!this.$props.visible, } }, - computed: { - classes () { - const { prefixCls } = this - return { - [`${prefixCls}`]: true, - } + watch: { + visible (val) { + this.sVisible = val }, }, methods: { - checkPosition (popup, text, placement) { - const { top, left, bottom, right } = text - const reg = /(top|bottom|left|right)(.*)/ - const [, abstractPos, suffix] = placement.match(reg) - let ret = placement - // we can change the position many times - if (abstractPos === 'left' && left < popup.width) ret = 'right' + suffix - if (abstractPos === 'right' && document.documentElement.clientWidth - right < popup.width) ret = 'left' + suffix - if (abstractPos === 'top' && top < popup.height) ret = 'bottom' + suffix - if (abstractPos === 'bottom' && document.documentElement.clientHeight - bottom < popup.height) ret = 'left' + suffix - return ret - }, - mountNode (callback) { - if (this.vnode) { - callback() - return + onVisibleChange (visible) { + if (!hasProp(this, 'visible')) { + this.sVisible = this.isNoTitle() ? false : visible } - const div = document.createElement('div') - document.body.appendChild(div) - const that = this - const vnode = new Vue({ - data () { - return { - left: 0, - top: 0, - } - }, - methods: { - hideSelf (e) { - if (that.t1) { - clearTimeout(that.t1) - that.t1 = null - } - if (that.mouseLeaveDelay) { - that.t2 = window.setTimeout(() => { - if (e.relatedTarget === that.$el) { - return - } - that.visible = false - }, +that.mouseLeaveDelay * 1e3) - } - }, - }, - render (h) { - return ( - <transition name={that.transitionName}> - <div - v-show={that.visible} - class={`ant-tooltip ant-tooltip-placement-${that.realPlacement}`} - style={{ left: this.left + 'px', top: this.top + 'px' }} - onMouseleave={this.hideSelf} - > - <div class='ant-tooltip-content'> - <div class='ant-tooltip-arrow'/> - <div class='ant-tooltip-inner'> - <span>{that.title}</span> - </div> - </div> - </div> - </transition> - ) - }, - }).$mount(div) - this.$nextTick(() => { - this.vnode = vnode - callback() + if (!this.isNoTitle()) { + this.$emit('change', visible) + } + }, + + getPopupDomNode () { + return this.$refs.tooltip.getPopupDomNode() + }, + + getPlacements () { + const { builtinPlacements, arrowPointAtCenter, autoAdjustOverflow } = this.$props + return builtinPlacements || getPlacements({ + arrowPointAtCenter, + verticalArrowShift: 8, + autoAdjustOverflow, }) }, - onPopupAlign: (placement, domNode, target, align) => { + + isHoverTrigger () { + const { trigger } = this.$props + if (!trigger || trigger === 'hover') { + return true + } + if (Array.isArray(trigger)) { + return trigger.indexOf('hover') >= 0 + } + return false + }, + + // Fix Tooltip won't hide at disabled button + // mouse events don't trigger at disabled button in Chrome + // https://github.com/react-component/tooltip/issues/18 + getDisabledCompatibleChildren (ele) { + const isAntBtn = ele.componentOptions && ele.componentOptions.Ctor.options.__ANT_BUTTON + if (((isAntBtn && ele.componentOptions.propsData.disabled) || (ele.tag === 'button' && ele.data && ele.data.attrs.disabled !== false)) && this.isHoverTrigger()) { + // Pick some layout related style properties up to span + // Prevent layout bugs like https://github.com/ant-design/ant-design/issues/5254 + const { picked, omited } = splitObject( + getStyle(ele), + ['position', 'left', 'right', 'top', 'bottom', 'float', 'display', 'zIndex'], + ) + const spanStyle = { + display: 'inline-block', // default inline-block is important + ...picked, + cursor: 'not-allowed', + } + const buttonStyle = { + ...omited, + pointerEvents: 'none', + } + const spanCls = getClass(ele) + const child = cloneElement(ele, { + style: buttonStyle, + class: null, + }) + return ( + <span style={spanStyle} class={spanCls}> + {child} + </span> + ) + } + return ele + }, + + isNoTitle () { + const { $slots, title } = this + return !$slots.title && !title + }, + + // 动态设置动画点 + onPopupAlign (domNode, align) { + const placements = this.getPlacements() + // 当前返回的位置 + const placement = Object.keys(placements).filter( + key => ( + placements[key].points[0] === align.points[0] && + placements[key].points[1] === align.points[1] + ), + )[0] if (!placement) { return } @@ -135,118 +141,48 @@ export default { } else if (placement.indexOf('right') >= 0 || placement.indexOf('Left') >= 0) { transformOrigin.left = `${-align.offset[0]}px` } - target.style.transformOrigin = `${transformOrigin.left} ${transformOrigin.top}` - }, - addEventHandle (old, fn) { - if (!old) { - return fn - } else if (Array.isArray(old)) { - return old.indexOf(fn) > -1 ? old : old.concat(fn) - } else { - return old === fn ? old : [old, fn] - } - }, - computeOffset (popup, text, placement, scale) { - let { width, height, top, left } = text - // you cant change the properties of DOMRect - top += window.scrollY - left += window.scrollX - // FIXME: we can get the numbers from scale, but that's not what we really want - const p = { width: popup.width / scale, height: popup.height / scale } - const ret = { left, top } - - if (/top/.test(placement)) ret.top -= p.height - if (/bottom/.test(placement)) ret.top += height - if (/left/.test(placement)) ret.left -= p.width - if (/right/.test(placement)) ret.left += width - - // FIXME: magic number 20 & 14 comes from the offset of triangle - if (/Left/.test(placement)) { - if (this.arrowPointAtCenter) ret.left += width / 2 - 20 - } else if (/Right/.test(placement)) { - ret.left += (width - p.width) - if (this.arrowPointAtCenter) ret.left -= width / 2 - 20 - } else if (/(top)|(bottom)/.test(placement)) { - ret.left += (width - p.width) / 2 - } - if (/Top/.test(placement)) { - if (this.arrowPointAtCenter) ret.top += height / 2 - 14 - } else if (/Bottom/.test(placement)) { - ret.top += (height - p.height) - if (this.arrowPointAtCenter) ret.top -= height / 2 - 14 - } else if (/(left)|(right)/.test(placement)) { - ret.top += (height - p.height) / 2 - } - return ret - }, - showNode () { - this.mountNode(() => { - this.visible = true - this.$nextTick(() => { - const popup = this.vnode.$el.getBoundingClientRect() - const [, scale = 1] = window.getComputedStyle(this.vnode.$el).transform.match(/matrix\((.*?),/) || [] - const content = this.$el.getBoundingClientRect() - const place = this.autoAdjustOverflow ? this.checkPosition(popup, content, this.placement, scale) : this.placement - this.realPlacement = place - const { left, top } = this.computeOffset(popup, content, place, scale) - this.vnode.left = left - this.vnode.top = top - }) - this.onPopupAlign(this.realPlacement, this.$el, this.vnode.$el, { offset: [0, 0] }) - }) - }, - hideNode (e) { - if (!this.vnode) return - if (e.relatedTarget === this.vnode.$el) { - return - } - this.visible = false - }, - checkShow (e) { - if (this.t2) { - clearTimeout(this.t2) - this.t2 = null - } - if (this.mouseEnterDelay) { - this.t1 = window.setTimeout(() => { - this.showNode(e) - }, +this.mouseEnterDelay * 1e3) - } - }, - checkHide (e) { - if (this.t1) { - clearTimeout(this.t1) - this.t1 = null - } - if (this.mouseLeaveDelay) { - this.t2 = window.setTimeout(() => { - this.hideNode(e) - }, +this.mouseLeaveDelay * 1e3) - } + domNode.style.transformOrigin = `${transformOrigin.left} ${transformOrigin.top}` }, }, + render (h) { - const inner = this.$slots.default[0] - inner.data = inner.data || {} - inner.data.on = inner.data.on || {} - inner.data.on.mouseenter = this.addEventHandle(inner.data.on.mouseenter, this.checkShow) - inner.data.on.mouseleave = this.addEventHandle(inner.data.on.mouseleave, this.checkHide) - - return this.$slots.default[0] - }, - updated () { - if (!this.vnode) return - const popup = this.vnode.$el.getBoundingClientRect() - const [, scale = 1] = window.getComputedStyle(this.vnode.$el).transform.match(/matrix\((.*?),/) || [] - const content = this.$el.getBoundingClientRect() - const { left, top } = this.computeOffset(popup, content, this.realPlacement, scale) - this.vnode.left = left - this.vnode.top = top - }, - beforeDestroy () { - if (!this.vnode) return - this.vnode.$el.remove() - this.vnode.$destroy() + const { $props, $data, $slots } = this + const { title, prefixCls, openClassName, getPopupContainer, getTooltipContainer } = $props + const children = ($slots.default || []).filter(c => c.tag || c.text.trim() !== '')[0] + let sVisible = $data.sVisible + // Hide tooltip when there is no title + if (!hasProp(this, 'visible') && this.isNoTitle()) { + sVisible = false + } + if (!children) { + return null + } + const child = this.getDisabledCompatibleChildren(isValidElement(children) ? children : <span>{children}</span>) + const childCls = { + [openClassName || `${prefixCls}-open`]: true, + } + const tooltipProps = { + props: { + ...$props, + getTooltipContainer: getPopupContainer || getTooltipContainer, + builtinPlacements: this.getPlacements(), + visible: sVisible, + }, + ref: 'tooltip', + on: { + visibleChange: this.onVisibleChange, + popupAlign: this.onPopupAlign, + }, + } + return ( + <RcTooltip {...tooltipProps}> + <template slot='overlay'> + {typeof title === 'function' ? title(h) : title} + {$slots.title} + </template> + {sVisible ? cloneElement(child, { class: childCls }) : child} + </RcTooltip> + ) }, } </script> diff --git a/components/trigger/Popup.vue b/components/trigger/Popup.vue index 1e68745ad..4587ad612 100644 --- a/components/trigger/Popup.vue +++ b/components/trigger/Popup.vue @@ -3,6 +3,7 @@ import PropTypes from '../_util/vue-types' import Align from '../align' import PopupInner from './PopupInner' import LazyRenderBox from './LazyRenderBox' +import { noop } from './utils' export default { props: { @@ -56,13 +57,11 @@ export default { onAlign (popupDomNode, align) { const props = this.$props const currentAlignClassName = props.getClassNameFromAlign(align) - // FIX: https://github.com/react-component/trigger/issues/56 - // FIX: https://github.com/react-component/tooltip/issues/79 if (this.currentAlignClassName !== currentAlignClassName) { + popupDomNode.className = popupDomNode.className.replace(this.currentAlignClassName, currentAlignClassName) this.currentAlignClassName = currentAlignClassName - popupDomNode.className = this.getClassName(currentAlignClassName) } - this.$emit('align', popupDomNode, align) + this.$listeners.align && this.$listeners.align(popupDomNode, align) }, getPopupDomNode () { @@ -95,30 +94,17 @@ export default { getClassName (currentAlignClassName) { return `${this.$props.prefixCls} ${this.$props.popupClassName} ${currentAlignClassName}` }, - onMouseEnter (e) { - this.$emit('mouseenter', e) - }, - onMouseLeave (e) { - this.$emit('mouseleave', e) - }, - beforeEnter (el) { - try { - // this.$refs.alignInstance && this.$refs.alignInstance.forceAlign() - } catch (error) { - - } - this.$refs.alignInstance && this.$refs.alignInstance.forceAlign() - }, afterLeave (el) { if (this.destroyPopupOnHide) { this.destroyPopup = true } }, getPopupElement () { - const { $props: props, onMouseEnter, onMouseLeave, $slots } = this + const { $props: props, $slots, $listeners } = this const { align, visible, prefixCls, animation } = props - const className = this.getClassName(this.currentAlignClassName || - props.getClassNameFromAlign(align)) + const { mouseenter, mouseleave } = $listeners + this.currentAlignClassName = this.currentAlignClassName || props.getClassNameFromAlign(align) + const className = this.getClassName(this.currentAlignClassName) // const hiddenClassName = `${prefixCls}-hidden` if (!visible) { this.currentAlignClassName = null @@ -131,8 +117,8 @@ export default { }, class: `${className}`, on: { - mouseenter: onMouseEnter, - mouseleave: onMouseLeave, + mouseenter: mouseenter || noop, + mouseleave: mouseleave || noop, }, ref: 'popupInstance', style: { ...this.getZIndexStyle() }, @@ -144,7 +130,6 @@ export default { } return (<transition {...transitionProps} - onBeforeEnter={this.beforeEnter} onAfterLeave={this.afterLeave} > <Align diff --git a/components/trigger/PopupInner.vue b/components/trigger/PopupInner.vue index 323452c8f..707e96223 100644 --- a/components/trigger/PopupInner.vue +++ b/components/trigger/PopupInner.vue @@ -8,22 +8,14 @@ export default { prefixCls: PropTypes.string, visible: PropTypes.bool, }, - methods: { - onMouseEnter (e) { - this.$emit('mouseenter', e) - }, - onMouseLeave (e) { - this.$emit('mouseleave', e) - }, - }, render () { const { prefixCls, visible } = this.$props - const { onMouseEnter, onMouseLeave } = this + const { $listeners } = this + const divProps = { + on: $listeners, + } return ( - <div - onMouseenter={onMouseEnter} - onMouseleave={onMouseLeave} - > + <div {...divProps}> <LazyRenderBox class={`${prefixCls}-content`} visible={visible}> {this.$slots.default} </LazyRenderBox> diff --git a/components/trigger/index.md b/components/trigger/index.md index 6887d36f5..69f9cb643 100644 --- a/components/trigger/index.md +++ b/components/trigger/index.md @@ -140,13 +140,13 @@ </tr> <tr> <td>popupVisibleChange</td> - <td>$emit(visible)</td> + <td>$emit</td> <td></td> <td>call when popup visible is changed</td> </tr> <tr> <td>popupAlign</td> - <td>$emit(popupDomNode, align)</td> + <td>$emit</td> <td></td> <td>callback when popup node is aligned</td> </tr> diff --git a/components/trigger/index.vue b/components/trigger/index.vue index 4c08b339e..e65c0bc27 100644 --- a/components/trigger/index.vue +++ b/components/trigger/index.vue @@ -1,12 +1,12 @@ <script> import PropTypes from '../_util/vue-types' import contains from '../_util/Dom/contains' -import hasProp from '../_util/hasProp' +import hasProp from '../_util/props-util' import addEventListener from '../_util/Dom/addEventListener' import warning from '../_util/warning' import Popup from './Popup' -import { getAlignFromPlacement, getPopupClassNameFromAlign } from './utils' -import StateMixin from '../_util/StateMixin' +import { getAlignFromPlacement, getPopupClassNameFromAlign, noop } from './utils' +import BaseMixin from '../_util/BaseMixin' import { cloneElement, cloneVNode } from '../_util/vnode' function returnEmptyString () { @@ -27,8 +27,8 @@ export default { showAction: PropTypes.any.def([]), hideAction: PropTypes.any.def([]), getPopupClassNameFromAlign: PropTypes.any.def(returnEmptyString), - // onPopupVisibleChange: PropTypes.func, - // afterPopupVisibleChange: PropTypes.func, + // onPopupVisibleChange: PropTypes.func.def(noop), + afterPopupVisibleChange: PropTypes.func.def(noop), popup: PropTypes.any, popupStyle: PropTypes.object.def({}), prefixCls: PropTypes.string.def('rc-trigger-popup'), @@ -51,7 +51,7 @@ export default { destroyPopupOnHide: PropTypes.bool.def(false), mask: PropTypes.bool.def(false), maskClosable: PropTypes.bool.def(true), - // onPopupAlign: PropTypes.func, + // onPopupAlign: PropTypes.func.def(noop), popupAlign: PropTypes.object.def({}), popupVisible: PropTypes.bool, defaultPopupVisible: PropTypes.bool.def(false), @@ -62,7 +62,7 @@ export default { maskAnimation: PropTypes.string, }, - mixins: [StateMixin], + mixins: [BaseMixin], data () { const props = this.$props let popupVisible @@ -96,7 +96,7 @@ export default { }, sPopupVisible (val) { this.$nextTick(() => { - this.$emit('afterPopupVisibleChange', val) + this.afterPopupVisibleChange(val) }) }, }, @@ -258,11 +258,10 @@ export default { }, getRootDomNode () { - console.log('this.$el.children', this.$el.children) return this.$el.children ? this.$el.children[0] : this.$el }, - getPopupClassFromAlign (align) { + handleGetPopupClassFromAlign (align) { const className = [] const props = this.$props const { popupPlacement, builtinPlacements, prefixCls } = props @@ -283,10 +282,7 @@ export default { } return popupAlign }, - onPopupAlign () { - this.$emit('popupAlign', ...arguments) - }, - getComponent () { + getComponent (h) { const mouseProps = {} if (this.isMouseEnterToShow()) { mouseProps.mouseenter = this.onPopupMouseenter @@ -295,8 +291,8 @@ export default { mouseProps.mouseleave = this.onPopupMouseleave } const { prefixCls, destroyPopupOnHide, sPopupVisible, - popupStyle, popupClassName, action, onPopupAlign, - popupAnimation, getPopupClassFromAlign, getRootDomNode, + popupStyle, popupClassName, action, + popupAnimation, handleGetPopupClassFromAlign, getRootDomNode, mask, zIndex, popupTransitionName, getPopupAlign, maskAnimation, maskTransitionName, popup, $slots, getContainer } = this const popupProps = { @@ -307,7 +303,7 @@ export default { action, align: getPopupAlign(), animation: popupAnimation, - getClassNameFromAlign: getPopupClassFromAlign, + getClassNameFromAlign: handleGetPopupClassFromAlign, getRootDomNode, mask, zIndex, @@ -318,7 +314,7 @@ export default { popupClassName, }, on: { - align: onPopupAlign, + align: this.$listeners.popupAlign || noop, ...mouseProps, }, ref: 'popup', @@ -329,7 +325,7 @@ export default { {...popupProps} ref='popup' > - {typeof popup === 'function' ? popup() : popup} + {typeof popup === 'function' ? popup(h) : popup} {popup === undefined ? $slots.popup : null} </Popup> ) @@ -359,7 +355,7 @@ export default { }) this.$forceUpdate() } - this.$emit('popupVisibleChange', sPopupVisible) + this.$listeners.popupVisibleChange && this.$listeners.popupVisibleChange(sPopupVisible) } }, @@ -473,7 +469,7 @@ export default { this.setPopupVisible(false) }, }, - render () { + render (h) { const children = this.$slots.default if (children.length > 1) { warning(false, 'Trigger $slots.default.length > 1, just support only one default', true) @@ -522,7 +518,7 @@ export default { const trigger = cloneElement(cloneVNode(child), newChildProps) const { sPopupVisible, forceRender } = this if (sPopupVisible || forceRender || this._component) { - this._component = this.getComponent() + this._component = this.getComponent(h) } else { this._component = null } diff --git a/components/trigger/utils.js b/components/trigger/utils.js index e2af70378..eddd8178f 100644 --- a/components/trigger/utils.js +++ b/components/trigger/utils.js @@ -21,7 +21,5 @@ export function getPopupClassNameFromAlign (builtinPlacements, prefixCls, align) } return '' } - -export function saveRef (name, component) { - this[name] = component +export function noop () { } diff --git a/contributors.md b/contributors.md index 031d1dac8..9f097fc4c 100644 --- a/contributors.md +++ b/contributors.md @@ -7,6 +7,10 @@ Checkbox | done Radio | done Tabs | done Tag | done +ToolTip | done +Popconfirm +Popover | done +Menu Carousel Mention Input | done |select完成后补全demo @@ -22,18 +26,14 @@ TimePicker ##万 Grid Col -ToolTip Affix Alert BackTop Dropdown Layout message -Menu Modal notification -Popconfirm -Popover Anchor Tree TreeSelect diff --git a/examples/index.less b/examples/index.less index 89a9f27d5..ac6ec4a44 100644 --- a/examples/index.less +++ b/examples/index.less @@ -1,3 +1,3 @@ -.icon-test{ - font-size: 35px; +#app { + padding: 50px; } diff --git a/examples/md.vue b/examples/md.vue index 9dcad410f..c907d5dda 100644 --- a/examples/md.vue +++ b/examples/md.vue @@ -1,5 +1,5 @@ <template> - <div v-html="marked($slots.default[0].text.trim() || '')" /> + <div style="padding: 10px 0;" v-html="marked($slots.default[0].text.trim() || '')" /> </template> <script> import marked from 'marked' @@ -16,7 +16,6 @@ marked.setOptions({ export default { name: 'md', data () { - console.log(this.$slots.default) return { marked, } diff --git a/package.json b/package.json index 118cbff8f..e5f1126a0 100644 --- a/package.json +++ b/package.json @@ -67,10 +67,9 @@ "style-loader": "^0.18.2", "stylelint": "^8.1.1", "stylelint-config-standard": "^17.0.0", - "vue": "^2.4.4", "vue-loader": "^13.0.5", "vue-router": "^3.0.1", - "vue-template-compiler": "^2.4.4", + "vue-template-compiler": "^2.5.13", "webpack": "^3.6.0", "webpack-dev-server": "^2.8.2" },