diff --git a/.eslintignore b/.eslintignore index 4c64b6f77..0c337a6d9 100644 --- a/.eslintignore +++ b/.eslintignore @@ -2,3 +2,4 @@ node_modules/ **/*.spec.* **/style/ *.html +/components/test/* diff --git a/CHANGELOG.en-US.md b/CHANGELOG.en-US.md index fc095d189..9491b0bfb 100644 --- a/CHANGELOG.en-US.md +++ b/CHANGELOG.en-US.md @@ -2,6 +2,40 @@ --- +## 1.2.0 +`2018-12-16` +### Synchronize with antd 3.10.x + +- 🔥🔥🔥 replaced font icons with svg icons which bring benefits below:: + - Complete offline usage of icon, no dependency of alipay cdn font icon file and no more empty square during downloading than no need to deploy icon font files locally either. + - Much more display accuracy in lower-level screens. + - Support multiple colors for icon. + - No need to change built-in icons with overriding styles by providing more props in component. + - 🌟 Add the `theme` attribute to set the theme style of the icon. + - 🌟 Added `component` attribute, you can externally pass a component to customize the control rendering result. + - 🌟 The `twoToneColor` property is added to control the theme color of the two-color icon. + - 🌟 Added static methods `Icon.getTowToneColor()` and `Icon.setTwoToneColor(...)` to globally get and set the theme color of all two-color icons. + - 🌟 The new static method `Icon.createFromIconfontCN({...})` is added to make it easier to use icons hosted on [`iconfont.cn`](http://iconfont.cn/). +- 🔥 Added a new component `Skeleton`. +- 🔥 Menu will automatically close up to fit width in `horizontal` mode. +- 🔥 The `placement` of the drawer supports `top` and `bottom` to accommodate more scenes. +- 🌟 The following components add a `suffixIcon` prop, which is used to set the icon behind the input box. For details, please refer to the documentation. + - Cascader + - DatePicker + - Select + - TreeSelect + - TimePicker +- 🌟 Added Modal.open for optional icon dialog. +- 🌟 Modal.info adds the configuration of `getContainer`. +- 🌟 Improve RangePicker footer UI by merging them. +- 🌟 The Anchor component adds `onClick` property. +- 🌟 The Tab component adds the `renderTabBar` property. +- 🌟 The Input component adds the `select` method. +- 🌟 Steps adds the `initial` attribute. +- 🌟 Upload adds `openFileDialogOnClick` prop to allow setting whether to open the upload dialog when the component is clicked. +- 🌟 InputNumber adds `decimalSeparator` prop to allow setting a custom decimal. +- 🐞 Fix a lot of hidden bugs that have not yet been issued, and then not list them one by one. + ## 1.1.10 `2018-12-7` diff --git a/CHANGELOG.zh-CN.md b/CHANGELOG.zh-CN.md index b49d7f92a..e1639ced3 100644 --- a/CHANGELOG.zh-CN.md +++ b/CHANGELOG.zh-CN.md @@ -2,6 +2,40 @@ --- +## 1.2.0 +`2018-12-16` +### 与antd 3.10.x同步 + +- 🔥🔥🔥 使用了 svg 图标替换了原先的 font 图标,从而带来了以下优势: + 可以离线化使用,不需要从支付宝 cdn 下载字体文件,图标不会因为网络问题呈现方块,也无需字体文件本地部署。 + 在低端设备上 svg 有更好的清晰度。 + 支持多色图标。 + 对于内建图标的更换可以提供更多 API,而不需要进行样式覆盖。 + - 🌟 新增 `theme` 属性,可以设置图标的主题风格。 + - 🌟 新增 `component` 属性,可以外部传入一个组件来自定义控制渲染结果。 + - 🌟 新增 `twoToneColor` 属性,可以控制双色图标的主题色。 + - 🌟 新增静态方法 `Icon.getTowToneColor()` 和 `Icon.setTwoToneColor(...)`,可以全局性的获取和设置所有双色图标的主题色。 + - 🌟 新增静态方法 `Icon.createFromIconfontCN({...})`,可以更加方便地使用 [`iconfont.cn`](http://iconfont.cn/) 上托管的图标。 +- 🔥 增加了一个新组件`Skeleton` +- 🔥 Menu 在 `horizontal` 模式下会自动收起来适应宽度。 +- 🔥 Drawer 的 `placement` 支持 `top` 和 `bottom`,可以适应更多场景。 +- 🌟 以下组件均新增了 `suffixIcon` 属性,用于设置输入框后面的图标,具体用法可以参考文档。 + - Cascader + - DatePicker + - Select + - TreeSelect + - TimePicker +- 🌟 新增 Modal.open 方法,用于可自定义图标的快捷对话框。 +- 🌟 Modal.info 增加 `getContainer` 的配置。 +- 🌟 合并优化了 RangePicker 的日历页脚 UI。 +- 🌟 Anchor 组件增加 `click` 事件。 +- 🌟 Tab 组件增加 `renderTabBar` 属性。 +- 🌟 Input 组件增加 `select` 方法。 +- 🌟 Steps 增加 `initial` 属性。 +- 🌟 Upload 组件新增 `openFileDialogOnClick` 属性,用于设置点击组件时是否打开上传对话框。 +- 🌟 InputNumber 组件新增 `decimalSeparator` 属性,用于设置自定义的小数点。 +- 🐞 修复众多隐蔽暂未提issue的bug,再此不在一一列出 + ## 1.1.10 `2018-12-7` diff --git a/components/_util/FormDecoratorDirective.js b/components/_util/FormDecoratorDirective.js index 1fc7ecf23..746f9dc4c 100644 --- a/components/_util/FormDecoratorDirective.js +++ b/components/_util/FormDecoratorDirective.js @@ -1,7 +1,11 @@ +export function antDecorator (Vue) { + return Vue.directive('decorator', { + }) +} + export default { // just for tag install: (Vue, options) => { - Vue.directive('decorator', { - }) + antDecorator(Vue) }, } diff --git a/components/_util/antDirective.js b/components/_util/antDirective.js new file mode 100644 index 000000000..fb89c3135 --- /dev/null +++ b/components/_util/antDirective.js @@ -0,0 +1,9 @@ +import { antInput } from './antInputDirective' +import { antDecorator } from './FormDecoratorDirective' + +export default { + install: (Vue, options) => { + antInput(Vue) + antDecorator(Vue) + }, +} diff --git a/components/_util/antInputDirective.js b/components/_util/antInputDirective.js index c6812322e..aeba9f943 100644 --- a/components/_util/antInputDirective.js +++ b/components/_util/antInputDirective.js @@ -49,26 +49,30 @@ if (isIE9) { }) } -export default { - install: (Vue, options) => { - Vue.directive('ant-input', { - inserted (el, binding, vnode, oldVnode) { - if (vnode.tag === 'textarea' || isTextInputType(el.type)) { - if (!binding.modifiers || !binding.modifiers.lazy) { - el.addEventListener('compositionstart', onCompositionStart) - el.addEventListener('compositionend', onCompositionEnd) - // Safari < 10.2 & UIWebView doesn't fire compositionend when - // switching focus before confirming composition choice - // this also fixes the issue where some browsers e.g. iOS Chrome - // fires "change" instead of "input" on autocomplete. - el.addEventListener('change', onCompositionEnd) - /* istanbul ignore if */ - if (isIE9) { - el.vmodel = true - } +export function antInput (Vue) { + return Vue.directive('ant-input', { + inserted (el, binding, vnode, oldVnode) { + if (vnode.tag === 'textarea' || isTextInputType(el.type)) { + if (!binding.modifiers || !binding.modifiers.lazy) { + el.addEventListener('compositionstart', onCompositionStart) + el.addEventListener('compositionend', onCompositionEnd) + // Safari < 10.2 & UIWebView doesn't fire compositionend when + // switching focus before confirming composition choice + // this also fixes the issue where some browsers e.g. iOS Chrome + // fires "change" instead of "input" on autocomplete. + el.addEventListener('change', onCompositionEnd) + /* istanbul ignore if */ + if (isIE9) { + el.vmodel = true } } - }, - }) + } + }, + }) +} + +export default { + install: (Vue, options) => { + antInput(Vue) }, } diff --git a/components/_util/antRefDirective.js b/components/_util/antRefDirective.js deleted file mode 100644 index c7e3b7425..000000000 --- a/components/_util/antRefDirective.js +++ /dev/null @@ -1,15 +0,0 @@ -export default { - install: (Vue, options) => { - Vue.directive('ant-ref', { - bind: function (el, binding, vnode) { - binding.value(vnode.componentInstance ? vnode.componentInstance : vnode.elm) - }, - update: function (el, binding, vnode) { - binding.value(vnode.componentInstance ? vnode.componentInstance : vnode.elm) - }, - unbind: function (el, binding, vnode) { - binding.value(null) - }, - }) - }, -} diff --git a/components/_util/css-animation/Event.js b/components/_util/css-animation/Event.js index 06e69a471..d35c67345 100644 --- a/components/_util/css-animation/Event.js +++ b/components/_util/css-animation/Event.js @@ -1,4 +1,22 @@ -const EVENT_NAME_MAP = { +const START_EVENT_NAME_MAP = { + transitionstart: { + transition: 'transitionstart', + WebkitTransition: 'webkitTransitionStart', + MozTransition: 'mozTransitionStart', + OTransition: 'oTransitionStart', + msTransition: 'MSTransitionStart', + }, + + animationstart: { + animation: 'animationstart', + WebkitAnimation: 'webkitAnimationStart', + MozAnimation: 'mozAnimationStart', + OAnimation: 'oAnimationStart', + msAnimation: 'MSAnimationStart', + }, +} + +const END_EVENT_NAME_MAP = { transitionend: { transition: 'transitionend', WebkitTransition: 'webkitTransitionEnd', @@ -16,6 +34,7 @@ const EVENT_NAME_MAP = { }, } +const startEvents = [] const endEvents = [] function detectEvents () { @@ -23,24 +42,31 @@ function detectEvents () { const style = testEl.style if (!('AnimationEvent' in window)) { - delete EVENT_NAME_MAP.animationend.animation + delete START_EVENT_NAME_MAP.animationstart.animation + delete END_EVENT_NAME_MAP.animationend.animation } if (!('TransitionEvent' in window)) { - delete EVENT_NAME_MAP.transitionend.transition + delete START_EVENT_NAME_MAP.transitionstart.transition + delete END_EVENT_NAME_MAP.transitionend.transition } - for (const baseEventName in EVENT_NAME_MAP) { - if (EVENT_NAME_MAP.hasOwnProperty(baseEventName)) { - const baseEvents = EVENT_NAME_MAP[baseEventName] - for (const styleName in baseEvents) { - if (styleName in style) { - endEvents.push(baseEvents[styleName]) - break + function process (EVENT_NAME_MAP, events) { + for (const baseEventName in EVENT_NAME_MAP) { + if (EVENT_NAME_MAP.hasOwnProperty(baseEventName)) { + const baseEvents = EVENT_NAME_MAP[baseEventName] + for (const styleName in baseEvents) { + if (styleName in style) { + events.push(baseEvents[styleName]) + break + } } } } } + + process(START_EVENT_NAME_MAP, startEvents) + process(END_EVENT_NAME_MAP, endEvents) } if (typeof window !== 'undefined' && typeof document !== 'undefined') { @@ -56,6 +82,31 @@ function removeEventListener (node, eventName, eventListener) { } const TransitionEvents = { + // Start events + startEvents, + + addStartEventListener (node, eventListener) { + if (startEvents.length === 0) { + window.setTimeout(eventListener, 0) + return + } + startEvents.forEach((startEvent) => { + addEventListener(node, startEvent, eventListener) + }) + }, + + removeStartEventListener (node, eventListener) { + if (startEvents.length === 0) { + return + } + startEvents.forEach((startEvent) => { + removeEventListener(node, startEvent, eventListener) + }) + }, + + // End events + endEvents, + addEndEventListener (node, eventListener) { if (endEvents.length === 0) { window.setTimeout(eventListener, 0) @@ -66,8 +117,6 @@ const TransitionEvents = { }) }, - endEvents, - removeEndEventListener (node, eventListener) { if (endEvents.length === 0) { return @@ -79,4 +128,3 @@ const TransitionEvents = { } export default TransitionEvents - diff --git a/components/_util/css-animation/index.js b/components/_util/css-animation/index.js index 252b49d24..0766ff613 100644 --- a/components/_util/css-animation/index.js +++ b/components/_util/css-animation/index.js @@ -1,3 +1,5 @@ +// https://github.com/yiminghe/css-animation 1.5.0 + import Event from './Event' import classes from 'component-classes' import { requestAnimationTimeout, cancelAnimationTimeout } from '../requestAnimationTimeout' diff --git a/components/_util/props-util.js b/components/_util/props-util.js index a8304000e..9da6e289f 100644 --- a/components/_util/props-util.js +++ b/components/_util/props-util.js @@ -95,19 +95,22 @@ const getOptionProps = (instance) => { return filterProps($props, $options.propsData) } -const getComponentFromProp = (instance, prop) => { +const getComponentFromProp = (instance, prop, options = instance, execute = true) => { if (instance.$createElement) { const h = instance.$createElement const temp = instance[prop] if (temp !== undefined) { - return typeof temp === 'function' ? temp(h) : temp + return typeof temp === 'function' && execute ? temp(h, options) : temp } - return instance.$slots[prop] + return instance.$slots[prop] || + (instance.$scopedSlots[prop] && execute && instance.$scopedSlots[prop](options)) || + (instance.$scopedSlots[prop] && instance.$scopedSlots[prop]) || + undefined } else { const h = instance.context.$createElement const temp = getPropsData(instance)[prop] if (temp !== undefined) { - return typeof temp === 'function' ? temp(h) : temp + return typeof temp === 'function' && execute ? temp(h, options) : temp } const slotsProp = [] const componentOptions = instance.componentOptions || {}; @@ -231,7 +234,7 @@ const initDefaultProps = (propTypes, defaultProps) => { export function mergeProps () { const args = [].slice.call(arguments, 0) const props = {} - args.forEach((p, i) => { + args.forEach((p = {}, i) => { for (const [k, v] of Object.entries(p)) { props[k] = props[k] || {} if (isPlainObject(v)) { diff --git a/components/_util/raf.js b/components/_util/raf.js new file mode 100644 index 000000000..ef632d25b --- /dev/null +++ b/components/_util/raf.js @@ -0,0 +1,30 @@ +import raf from 'raf' + +let id = 0 +const ids = {} + +// Support call raf with delay specified frame +export default function wrapperRaf (callback, delayFrames = 1) { + const myId = id++ + let restFrames = delayFrames + + function internalCallback () { + restFrames -= 1 + + if (restFrames <= 0) { + callback() + delete ids[id] + } else { + ids[id] = raf(internalCallback) + } + } + + ids[id] = raf(internalCallback) + + return myId +} + +wrapperRaf.cancel = function (id) { + raf.cancel(ids[id]) + delete ids[id] +} diff --git a/components/_util/store/connect.jsx b/components/_util/store/connect.jsx index 05601589d..0bce77360 100644 --- a/components/_util/store/connect.jsx +++ b/components/_util/store/connect.jsx @@ -26,6 +26,7 @@ export default function connect (mapStateToProps) { }, data () { this.store = this.storeContext.store + this.preProps = { ...omit(getOptionProps(this), ['__propsSymbol__']) } return { subscribed: finnalMapStateToProps(this.store.getState(), this.$props), } @@ -49,10 +50,10 @@ export default function connect (mapStateToProps) { if (!this.unsubscribe) { return } - - const nextState = finnalMapStateToProps(this.store.getState(), this.$props) - if (!shallowEqual(this.subscribed, nextState)) { - this.subscribed = nextState + const props = getOptionProps(this) + const nextSubscribed = finnalMapStateToProps(this.store.getState(), props) + if (!shallowEqual(this.preProps, props) || !shallowEqual(this.subscribed, nextSubscribed)) { + this.subscribed = nextSubscribed } }, @@ -74,8 +75,10 @@ export default function connect (mapStateToProps) { }, }, render () { + this.preProps = { ...this.$props } const { $listeners, $slots = {}, $attrs, $scopedSlots, subscribed, store } = this const props = getOptionProps(this) + this.preProps = { ...omit(props, ['__propsSymbol__']) } const wrapProps = { props: { ...props, diff --git a/components/_util/vnode.js b/components/_util/vnode.js index 06b6235a1..7d5e01603 100644 --- a/components/_util/vnode.js +++ b/components/_util/vnode.js @@ -51,7 +51,7 @@ export function cloneVNodes (vnodes, deep) { return res } -export function cloneElement (n, nodeProps, deep) { +export function cloneElement (n, nodeProps = {}, deep) { let ele = n if (Array.isArray(n)) { ele = filterEmpty(n)[0] diff --git a/components/_util/wave.jsx b/components/_util/wave.jsx index 82d4c733f..ac262e156 100644 --- a/components/_util/wave.jsx +++ b/components/_util/wave.jsx @@ -1,12 +1,26 @@ import TransitionEvents from './css-animation/Event' +import raf from '../_util/raf' +let styleForPesudo + +// Where el is the DOM element you'd like to test for visibility +function isHidden (element) { + if (process.env.NODE_ENV === 'test') { + return false + } + return !element || element.offsetParent === null +} export default { name: 'Wave', props: ['insertExtraNode'], mounted () { this.$nextTick(() => { - this.instance = this.bindAnimationEvent(this.$el) + const node = this.$el + if (node.nodeType !== 1) { + return + } + this.instance = this.bindAnimationEvent(node) }) }, @@ -14,6 +28,10 @@ export default { if (this.instance) { this.instance.cancel() } + if (this.clickWaveTimeoutId) { + clearTimeout(this.clickWaveTimeoutId) + } + this.destroy = true }, methods: { isNotGrey (color) { @@ -25,7 +43,7 @@ export default { }, onClick (node, waveColor) { - if (node.className.indexOf('-leave') >= 0) { + if (!node || isHidden(node) || node.className.indexOf('-leave') >= 0) { return } this.removeExtraStyleNode() @@ -37,6 +55,7 @@ export default { node.removeAttribute(attributeName) node.setAttribute(attributeName, 'true') // Not white or transparnt or grey + styleForPesudo = styleForPesudo || document.createElement('style') if (waveColor && waveColor !== '#ffffff' && waveColor !== 'rgb(255, 255, 255)' && @@ -44,14 +63,17 @@ export default { !/rgba\(\d*, \d*, \d*, 0\)/.test(waveColor) && // any transparent rgba color waveColor !== 'transparent') { extraNode.style.borderColor = waveColor - this.styleForPesudo = document.createElement('style') - this.styleForPesudo.innerHTML = + + styleForPesudo.innerHTML = `[ant-click-animating-without-extra-node]:after { border-color: ${waveColor}; }` - document.body.appendChild(this.styleForPesudo) + if (!document.body.contains(styleForPesudo)) { + document.body.appendChild(styleForPesudo) + } } if (insertExtraNode) { node.appendChild(extraNode) } + TransitionEvents.addStartEventListener(node, this.onTransitionStart) TransitionEvents.addEndEventListener(node, this.onTransitionEnd) }, @@ -64,7 +86,7 @@ export default { } const onClick = (e) => { // Fix radio button click twice - if (e.target.tagName === 'INPUT') { + if (e.target.tagName === 'INPUT' || isHidden(e.target)) { return } this.resetEffect(node) @@ -74,6 +96,13 @@ export default { getComputedStyle(node).getPropertyValue('border-color') || getComputedStyle(node).getPropertyValue('background-color') this.clickWaveTimeoutId = window.setTimeout(() => this.onClick(node, waveColor), 0) + raf.cancel(this.animationStartId) + this.animationStart = true + + // Render to trigger transition event cost 3 frames. Let's delay 10 frames to reset this. + this.animationStartId = raf(() => { + this.animationStart = false + }, 10) } node.addEventListener('click', onClick, true) return { @@ -98,9 +127,21 @@ export default { if (insertExtraNode && this.extraNode && node.contains(this.extraNode)) { node.removeChild(this.extraNode) } + TransitionEvents.removeStartEventListener(node, this.onTransitionStart) TransitionEvents.removeEndEventListener(node, this.onTransitionEnd) }, + onTransitionStart (e) { + if (this.destroy) return + const node = this.$el + if (!e || e.target !== node) { + return + } + + if (!this.animationStart) { + this.resetEffect(node) + } + }, onTransitionEnd (e) { if (!e || e.animationName !== 'fadeEffect') { return @@ -108,9 +149,8 @@ export default { this.resetEffect(e.target) }, removeExtraStyleNode () { - if (this.styleForPesudo && document.body.contains(this.styleForPesudo)) { - document.body.removeChild(this.styleForPesudo) - this.styleForPesudo = null + if (styleForPesudo) { + styleForPesudo.innerHTML = '' } }, }, diff --git a/components/affix/demo/index.vue b/components/affix/demo/index.vue index ef83a3199..d953c643f 100644 --- a/components/affix/demo/index.vue +++ b/components/affix/demo/index.vue @@ -21,6 +21,7 @@ Please note that Affix should not cover other content on the page, especially wh export default { category: 'Components', subtitle: '固钉', + zhType: '导航', type: 'Navigation', title: 'Affix', render () { diff --git a/components/affix/index.jsx b/components/affix/index.jsx index 8aade3b47..a9fb0b25b 100644 --- a/components/affix/index.jsx +++ b/components/affix/index.jsx @@ -147,8 +147,8 @@ const Affix = { }, updatePosition (e) { - let { offsetTop } = this const { offsetBottom, offset, target = getDefaultTarget } = this + let { offsetTop } = this const targetNode = target() // Backwards support diff --git a/components/alert/__tests__/__snapshots__/demo.test.js.snap b/components/alert/__tests__/__snapshots__/demo.test.js.snap index 23160bc1e..b42763a71 100644 --- a/components/alert/__tests__/__snapshots__/demo.test.js.snap +++ b/components/alert/__tests__/__snapshots__/demo.test.js.snap @@ -2,10 +2,18 @@ exports[`renders ./components/alert/demo/banner.md correctly 1`] = `
-
Warning text

-
Very long warning text warning text text text text text text text

+
Warning text

+
Very long warning text warning text text text text text text text

Warning text without icon

-
Error text
+
Error text
`; @@ -13,13 +21,31 @@ exports[`renders ./components/alert/demo/basic.md correctly 1`] = `
-
Warning Text Warning Text Warning TextW arning Text Warning Text Warning TextWarning Text
-
Error TextError Description Error Description Error Description Error Description Error Description Error Description
+
Warning Text Warning Text Warning TextW arning Text Warning Text Warning TextWarning Text
+
Error TextError Description Error Description Error Description Error Description Error Description Error Description
`; exports[`renders ./components/alert/demo/close-text.md correctly 1`] = `
Info TextClose Now
`; +exports[`renders ./components/alert/demo/custom-icon.md correctly 1`] = ` +
+
showIcon = false
+
Success Tips
+
Informational Notes
+
Warning
+
Error
+
Success TipsDetailed description and advices about successful copywriting.
+
Informational NotesAdditional description and informations about copywriting.
+
WarningThis is a warning notice about copywriting.
+
ErrorThis is an error message about copywriting.
+
+`; + exports[`renders ./components/alert/demo/description.md correctly 1`] = `
Success Text

@@ -33,20 +59,42 @@ exports[`renders ./components/alert/demo/description.md correctly 1`] = ` exports[`renders ./components/alert/demo/icon.md correctly 1`] = `

-
Success Tips
-
Informational Notes
-
Warning
-
Error
-
Success TipsDetailed description and advices about successful copywriting.
-
Informational NotesAdditional description and informations about copywriting.
-
WarningThis is a warning notice about copywriting.
-
ErrorThis is an error message about copywriting.
+
Success Tips
+
Informational Notes
+
Warning
+
Error
+
Success TipsDetailed description and advices about successful copywriting.
+
Informational NotesAdditional description and informations about copywriting.
+
WarningThis is a warning notice about copywriting.
+
ErrorThis is an error message about copywriting.
`; exports[`renders ./components/alert/demo/smooth-closed.md correctly 1`] = `
-
Alert Message Text
+
Alert Message Text
`; diff --git a/components/alert/demo/custom-icon.md b/components/alert/demo/custom-icon.md new file mode 100644 index 000000000..1b74407a6 --- /dev/null +++ b/components/alert/demo/custom-icon.md @@ -0,0 +1,63 @@ + +#### 自定义图标 +可口的图标让信息类型更加醒目。 + + + +#### Custom Icon +Decent icon make information more clear and more friendly. + + +```html + +``` diff --git a/components/alert/demo/index.vue b/components/alert/demo/index.vue index 92d6be4db..9d693b75f 100644 --- a/components/alert/demo/index.vue +++ b/components/alert/demo/index.vue @@ -29,6 +29,7 @@ export default { category: 'Components', subtitle: '警告提示', type: 'Feedback', + zhType: '反馈', title: 'Alert', render () { return ( diff --git a/components/alert/demo/smooth-closed.md b/components/alert/demo/smooth-closed.md index 2fe9bcc99..bb51e5654 100644 --- a/components/alert/demo/smooth-closed.md +++ b/components/alert/demo/smooth-closed.md @@ -1,6 +1,6 @@ #### 平滑地卸载 -平滑、自然的卸载提示 +平滑、自然的卸载提示。 diff --git a/components/alert/index.en-US.md b/components/alert/index.en-US.md index 127d8cbd1..b59801449 100644 --- a/components/alert/index.en-US.md +++ b/components/alert/index.en-US.md @@ -8,7 +8,7 @@ | closable | Whether Alert can be closed | boolean | - | | closeText | Close text to show | string\|slot | - | | description | Additional content of Alert | string\|slot | - | -| iconType | Icon type, effective when `showIcon` is `true` | string | - | +| icon | Custom icon, effective when `showIcon` is `true` | vnode \| slot | - | | message | Content of Alert | string\|slot | - | | showIcon | Whether to show icon | boolean | false, in `banner` mode default is true | | type | Type of Alert styles, options: `success`, `info`, `warning`, `error` | string | `info`, in `banner` mode default is `warning` | diff --git a/components/alert/index.jsx b/components/alert/index.jsx index 9f9512569..a02b244a1 100644 --- a/components/alert/index.jsx +++ b/components/alert/index.jsx @@ -4,7 +4,8 @@ import classNames from 'classnames' import BaseMixin from '../_util/BaseMixin' import PropTypes from '../_util/vue-types' import getTransitionProps from '../_util/getTransitionProps' -import { getComponentFromProp } from '../_util/props-util' +import { getComponentFromProp, isValidElement } from '../_util/props-util' +import { cloneElement } from '../_util/vnode' function noop () { } export const AlertProps = { /** @@ -28,6 +29,7 @@ export const AlertProps = { iconType: PropTypes.string, prefixCls: PropTypes.string, banner: PropTypes.bool, + icon: PropTypes.any, } const Alert = { @@ -69,11 +71,14 @@ const Alert = { const closeText = getComponentFromProp(this, 'closeText') const description = getComponentFromProp(this, 'description') const message = getComponentFromProp(this, 'message') + const icon = getComponentFromProp(this, 'icon') // banner模式默认有 Icon showIcon = banner && showIcon === undefined ? true : showIcon // banner模式默认为警告 type = banner && type === undefined ? 'warning' : type || 'info' - + let iconTheme = 'filled' + // should we give a warning? + // warning(!iconType, `The property 'iconType' is deprecated. Use the property 'icon' instead.`); if (!iconType) { switch (type) { case 'success': @@ -83,7 +88,7 @@ const Alert = { iconType = 'info-circle' break case 'error': - iconType = 'cross-circle' + iconType = 'close-circle' break case 'warning': iconType = 'exclamation-circle' @@ -94,7 +99,7 @@ const Alert = { // use outline icon in alert with description if (description) { - iconType += '-o' + iconTheme = 'outlined' } } @@ -113,9 +118,21 @@ const Alert = { const closeIcon = closable ? ( - {closeText || } + {closeText || } ) : null + + const iconNode = icon && ( + isValidElement(icon) + ? cloneElement( + icon, + { + class: `${prefixCls}-icon`, + }, + ) : {icon}) || ( + + ) + const transitionProps = getTransitionProps(`${prefixCls}-slide-up`, { appear: false, afterLeave: this.animationEnd, @@ -123,7 +140,7 @@ const Alert = { return closed ? null : (
- {showIcon ? : null} + {showIcon ? iconNode : null} {message} {description} {closeIcon} diff --git a/components/alert/index.zh-CN.md b/components/alert/index.zh-CN.md index d3d16725a..5393b048b 100644 --- a/components/alert/index.zh-CN.md +++ b/components/alert/index.zh-CN.md @@ -8,7 +8,7 @@ | closable | 默认不显示关闭按钮 | boolean | 无 | | closeText | 自定义关闭按钮 | string\|slot | 无 | | description | 警告提示的辅助性文字介绍 | string\|slot | 无 | -| iconType | 自定义图标类型,`showIcon` 为 `true` 时有效 | string | - | +| icon | 自定义图标,`showIcon` 为 `true` 时有效 | vnode \| slot | - | | message | 警告提示内容 | string\|slot | 无 | | showIcon | 是否显示辅助图标 | boolean | false,`banner` 模式下默认值为 true | | type | 指定警告提示的样式,有四种选择 `success`、`info`、`warning`、`error` | string | `info`,`banner` 模式下默认值为 `warning` | diff --git a/components/alert/style/index.less b/components/alert/style/index.less index a5654533a..a876d6921 100644 --- a/components/alert/style/index.less +++ b/components/alert/style/index.less @@ -18,7 +18,7 @@ } &-icon { - top: 8px + @font-size-base * @line-height-base / 2 - @font-size-base / 2 + 1px; + top: 8px + @font-size-base * @line-height-base / 2 - @font-size-base / 2; left: 16px; position: absolute; } @@ -30,34 +30,34 @@ } &-success { - border: @border-width-base @border-style-base ~`colorPalette("@{success-color}", 3)`; - background-color: ~`colorPalette("@{success-color}", 1)`; + border: @border-width-base @border-style-base @alert-success-border-color; + background-color: @alert-success-bg-color; .@{alert-prefix-cls}-icon { - color: @success-color; + color: @alert-success-icon-color; } } &-info { - border: @border-width-base @border-style-base ~`colorPalette("@{info-color}", 3)`; - background-color: ~`colorPalette("@{info-color}", 1)`; + border: @border-width-base @border-style-base @alert-info-border-color; + background-color: @alert-info-bg-color; .@{alert-prefix-cls}-icon { - color: @info-color; + color: @alert-info-icon-color; } } &-warning { - border: @border-width-base @border-style-base ~`colorPalette("@{warning-color}", 3)`; - background-color: ~`colorPalette("@{warning-color}", 1)`; + border: @border-width-base @border-style-base @alert-warning-border-color; + background-color: @alert-warning-bg-color; .@{alert-prefix-cls}-icon { - color: @warning-color; + color: @alert-warning-icon-color; } } &-error { - border: @border-width-base @border-style-base ~`colorPalette("@{error-color}", 3)`; - background-color: ~`colorPalette("@{error-color}", 1)`; + border: @border-width-base @border-style-base @alert-error-border-color; + background-color: @alert-error-bg-color; .@{alert-prefix-cls}-icon { - color: @error-color; + color: @alert-error-icon-color; } } @@ -70,7 +70,7 @@ overflow: hidden; cursor: pointer; - .@{iconfont-css-prefix}-cross { + .@{iconfont-css-prefix}-close { color: @alert-close-color; transition: color .3s; &:hover { diff --git a/components/anchor/Anchor.jsx b/components/anchor/Anchor.jsx index bc469b038..a0ea8be4a 100644 --- a/components/anchor/Anchor.jsx +++ b/components/anchor/Anchor.jsx @@ -4,7 +4,7 @@ import addEventListener from '../_util/Dom/addEventListener' import Affix from '../affix' import getScroll from '../_util/getScroll' import raf from 'raf' -import { initDefaultProps, getClass, getStyle } from '../_util/props-util' +import { initDefaultProps, getClass } from '../_util/props-util' import BaseMixin from '../_util/BaseMixin' function getDefaultContainer () { @@ -71,7 +71,6 @@ function scrollTo (href, offsetTop = 0, getContainer, callback = () => { }) { } } raf(frameFunc) - history.pushState(null, '', href) } export const AnchorProps = { @@ -117,6 +116,7 @@ export default { $data: this.$data, scrollTo: this.handleScrollTo, }, + antAnchorContext: this, } }, diff --git a/components/anchor/AnchorLink.jsx b/components/anchor/AnchorLink.jsx index 92ab3cbb9..eebad878e 100644 --- a/components/anchor/AnchorLink.jsx +++ b/components/anchor/AnchorLink.jsx @@ -16,6 +16,7 @@ export default { }), inject: { antAnchor: { default: {}}, + antAnchorContext: { default: {}}, }, mounted () { @@ -32,8 +33,14 @@ export default { }, }, methods: { - handleClick () { + handleClick (e) { this.antAnchor.scrollTo(this.href) + const { scrollTo } = this.antAnchor + const { href, title } = this.$props + if (this.antAnchorContext.$emit) { + this.antAnchorContext.$emit('click', e, { title, href }) + } + scrollTo(href) }, }, render () { diff --git a/components/anchor/__tests__/Anchor.test.js b/components/anchor/__tests__/Anchor.test.js index faf97f3c8..bc3aa0bde 100644 --- a/components/anchor/__tests__/Anchor.test.js +++ b/components/anchor/__tests__/Anchor.test.js @@ -151,4 +151,31 @@ describe('Anchor Render', () => { expect(wrapper.vm.$refs.anchor.links).toEqual(['#API_1']) }) }) + + it('Anchor onClick event', () => { + let event + let link + const handleClick = (...arg) => ([event, link] = arg) + + const href = '#API' + const title = 'API' + + const wrapper = mount( + { + render () { + return ( + + + + ) + }, + } + ) + + wrapper.find(`a[href="${href}"]`).trigger('click') + + wrapper.vm.$refs.anchorRef.handleScroll() + expect(event).not.toBe(undefined) + expect(link).toEqual({ href, title }) + }) }) diff --git a/components/anchor/__tests__/__snapshots__/demo.test.js.snap b/components/anchor/__tests__/__snapshots__/demo.test.js.snap index 953443964..39f784438 100644 --- a/components/anchor/__tests__/__snapshots__/demo.test.js.snap +++ b/components/anchor/__tests__/__snapshots__/demo.test.js.snap @@ -18,6 +18,20 @@ exports[`renders ./components/anchor/demo/basic.md correctly 1`] = `
`; +exports[`renders ./components/anchor/demo/onClick.md correctly 1`] = ` + +`; + exports[`renders ./components/anchor/demo/static.md correctly 1`] = `
diff --git a/components/anchor/demo/index.vue b/components/anchor/demo/index.vue index 8dfe8c482..9b7fdd9af 100644 --- a/components/anchor/demo/index.vue +++ b/components/anchor/demo/index.vue @@ -1,6 +1,7 @@ +``` diff --git a/components/anchor/index.en-US.md b/components/anchor/index.en-US.md index 9537f3589..017b9d55f 100644 --- a/components/anchor/index.en-US.md +++ b/components/anchor/index.en-US.md @@ -12,6 +12,11 @@ | offsetTop | Pixels to offset from top when calculating position of scroll | number | 0 | | showInkInFixed | Whether show ink-balls in Fixed mode | boolean | false | +### Events +| Events Name | Description | Arguments | +| --- | --- | --- | +| click | set the handler to handle `click` event | Function(e: Event, link: Object) | + ### Link Props | Property | Description | Type | Default | diff --git a/components/anchor/index.zh-CN.md b/components/anchor/index.zh-CN.md index ac3a084f2..094cd5585 100644 --- a/components/anchor/index.zh-CN.md +++ b/components/anchor/index.zh-CN.md @@ -12,6 +12,11 @@ | offsetTop | 距离窗口顶部达到指定偏移量后触发 | number | | | showInkInFixed | 固定模式是否显示小圆点 | boolean | false | +### 事件 +| 事件名称 | 说明 | 回调参数 | +| --- | --- | --- | +| click | `click` 事件的 handler | Function(e: Event, link: Object) | + ### Link Props | 成员 | 说明 | 类型 | 默认值 | diff --git a/components/anchor/style/index.less b/components/anchor/style/index.less index 235eb7815..3a5ddec9a 100644 --- a/components/anchor/style/index.less +++ b/components/anchor/style/index.less @@ -51,8 +51,8 @@ } &-link { - padding: 8px 0 8px 16px; - line-height: 1; + padding: 7px 0 7px 16px; + line-height: 1.143; &-title { display: block; @@ -62,7 +62,7 @@ white-space: nowrap; overflow: hidden; text-overflow: ellipsis; - margin-bottom: 8px; + margin-bottom: 6px; &:only-child { margin-bottom: 0; @@ -75,7 +75,7 @@ } &-link &-link { - padding-top: 6px; - padding-bottom: 6px; + padding-top: 5px; + padding-bottom: 5px; } } diff --git a/components/auto-complete/__tests__/__snapshots__/demo.test.js.snap b/components/auto-complete/__tests__/__snapshots__/demo.test.js.snap index caad4b292..dc5a08f8e 100644 --- a/components/auto-complete/__tests__/__snapshots__/demo.test.js.snap +++ b/components/auto-complete/__tests__/__snapshots__/demo.test.js.snap @@ -10,7 +10,7 @@ exports[`renders ./components/auto-complete/demo/basic.md correctly 1`] = `
 
-
+
`; @@ -23,10 +23,10 @@ exports[`renders ./components/auto-complete/demo/certain-category.md correctly 1
input here
- + @@ -41,7 +41,7 @@ exports[`renders ./components/auto-complete/demo/custom.md correctly 1`] = `
 
- + `; @@ -56,7 +56,7 @@ exports[`renders ./components/auto-complete/demo/non-case-sensitive.md correctly
 
- + `; @@ -71,7 +71,7 @@ exports[`renders ./components/auto-complete/demo/options.md correctly 1`] = `
 
- + `; @@ -84,10 +84,10 @@ exports[`renders ./components/auto-complete/demo/uncertain-category.md correctly
input here
- + diff --git a/components/auto-complete/demo/index.vue b/components/auto-complete/demo/index.vue index 8cf58f7e2..ab77ca2b2 100644 --- a/components/auto-complete/demo/index.vue +++ b/components/auto-complete/demo/index.vue @@ -18,13 +18,14 @@ const md = { Autocomplete function of input field. ## When To Use When there is a need for autocomplete functionality. -## Examples +## Examples `, } export default { category: 'Components', subtitle: '自动完成', type: 'Data Entry', + zhType: '数据录入', cols: 2, title: 'AutoComplete', render () { diff --git a/components/auto-complete/index.en-US.md b/components/auto-complete/index.en-US.md index 87e8f74ef..8eb8d3a54 100644 --- a/components/auto-complete/index.en-US.md +++ b/components/auto-complete/index.en-US.md @@ -12,12 +12,14 @@ | slot="default" (for customize input element) | customize input element | HTMLInputElement / HTMLTextAreaElement | `` | | dataSource | Data source for autocomplete | slot \| [DataSourceItemType](https://github.com/vueComponent/ant-design-vue/blob/724d53b907e577cf5880c1e6742d4c3f924f8f49/components/auto-complete/index.vue#L9)\[] | | | defaultActiveFirstOption | Whether active first option by default | boolean | true | -| defaultValue | Initial selected option. | string\|string\[]\|{ key: string, label: string\|vNodes }\|Array<{ key: string, label: string\|vNodes }> | - | +| defaultValue | Initial selected option. | string\|string\[]\| - | | disabled | Whether disabled select | boolean | false | | filterOption | If true, filter options by input, if function, filter options against it. The function will receive two arguments, `inputValue` and `option`, if the function returns `true`, the option will be included in the filtered set; Otherwise, it will be excluded. | boolean or function(inputValue, option) | true | | optionLabelProp | Which prop value of option will render as content of select. | string | `children` | | placeholder | placeholder of input | string | - | | value(v-model) | selected option | string\|string\[]\|{ key: string, label: string\|vNodes }\|Array<{ key: string, label: string\|vNodes }> | - | +| defaultOpen | Initial open state of dropdown | boolean | - | +| open | Controlled open state of dropdown | boolean | - | ### events | Events Name | Description | Arguments | @@ -27,6 +29,7 @@ | focus | Called when entering the component | function() | | search | Called when searching items. | function(value) | - | | select | Called when a option is selected. param is option's value and option instance. | function(value, option) | +| dropdownVisibleChange | Call when dropdown open | function(open) | ## Methods diff --git a/components/auto-complete/index.jsx b/components/auto-complete/index.jsx index ad1e24224..d6e677569 100644 --- a/components/auto-complete/index.jsx +++ b/components/auto-complete/index.jsx @@ -122,9 +122,7 @@ const AutoComplete = { }, class: cls, ref: 'select', - on: { - ...$listeners, - }, + on: $listeners, } return ( Large



-


+
+ Forward
`; diff --git a/components/button/__tests__/__snapshots__/index.test.js.snap b/components/button/__tests__/__snapshots__/index.test.js.snap index 0a9dc15e2..834e92354 100644 --- a/components/button/__tests__/__snapshots__/index.test.js.snap +++ b/components/button/__tests__/__snapshots__/index.test.js.snap @@ -6,15 +6,35 @@ exports[`Button fixbug renders {0} , 0 and {false} 2`] = ``; -exports[`Button renders Chinese characters correctly 1`] = ``; +exports[`Button renders Chinese characters correctly 1`] = ` + +`; -exports[`Button renders Chinese characters correctly 2`] = ``; +exports[`Button renders Chinese characters correctly 2`] = ` + +`; -exports[`Button renders Chinese characters correctly 3`] = ``; +exports[`Button renders Chinese characters correctly 3`] = ` + +`; -exports[`Button renders Chinese characters correctly 4`] = ``; +exports[`Button renders Chinese characters correctly 4`] = ` + +`; -exports[`Button renders Chinese characters correctly 5`] = ``; +exports[`Button renders Chinese characters correctly 5`] = ` + +`; exports[`Button renders correctly 1`] = ``; diff --git a/components/button/button.jsx b/components/button/button.jsx index 7a49ebffa..36a21e8fb 100644 --- a/components/button/button.jsx +++ b/components/button/button.jsx @@ -65,7 +65,10 @@ export default { methods: { fixTwoCNChar () { // Fix for HOC usage like - const node = this.$el + const node = this.$refs.buttonNode + if (!node) { + return + } const buttonText = node.textContent || node.innerText if (this.isNeedInserted() && isTwoCNChar(buttonText)) { if (!this.hasTwoCNChar) { @@ -76,9 +79,10 @@ export default { } }, handleClick (event) { - // this.clicked = true - // clearTimeout(this.timeout) - // this.timeout = setTimeout(() => (this.clicked = false), 500) + const { sLoading } = this.$data + if (sLoading) { + return + } this.$emit('click', event) }, insertSpace (child, needInserted) { @@ -101,6 +105,9 @@ export default { const { htmlType, classes, icon, disabled, handleClick, sLoading, $slots, $attrs, $listeners } = this + const now = new Date() + const isChristmas = now.getMonth() === 11 && now.getDate() === 25 + const title = isChristmas ? 'Ho Ho Ho!' : $attrs.title const buttonProps = { props: { }, @@ -108,8 +115,9 @@ export default { ...$attrs, type: htmlType, disabled, + title, }, - class: classes, + class: { ...classes, christmas: isChristmas }, on: { ...$listeners, click: handleClick, @@ -118,16 +126,17 @@ export default { const iconType = sLoading ? 'loading' : icon const iconNode = iconType ? : null const kids = $slots.default && $slots.default.length === 1 ? this.insertSpace($slots.default[0], this.isNeedInserted()) : $slots.default + if ('href' in $attrs) { return ( - + {iconNode}{kids} ) } else { return ( - diff --git a/components/button/demo/index.vue b/components/button/demo/index.vue index a39748f17..29c380c90 100644 --- a/components/button/demo/index.vue +++ b/components/button/demo/index.vue @@ -26,6 +26,7 @@ const md = { export default { category: 'Components', type: 'General', + zhType: '通用', title: 'Button', subtitle: '按钮', render () { diff --git a/components/button/index.en-US.md b/components/button/index.en-US.md index 01844a0d4..9c3ea1361 100644 --- a/components/button/index.en-US.md +++ b/components/button/index.en-US.md @@ -17,7 +17,7 @@ To get a customized button, just set `type`/`shape`/`size`/`loading`/`disabled`. ### events | Events Name | Description | Arguments | | --- | --- | --- | -| click | set the handler to handle `click` event | function(e) | +| click | set the handler to handle `click` event | (event) => void | `` will be rendered into ``, and all the properties which are not listed above will be transferred to the ``,并且除了上表中的属性,其它属性都会直接传到 ``。 +`Hello world!` 最终会被渲染为 ``,并且除了上表中的属性,其它属性都会直接传到原生 button 上。 diff --git a/components/button/style/index.less b/components/button/style/index.less index d0029d7af..fee28d969 100644 --- a/components/button/style/index.less +++ b/components/button/style/index.less @@ -12,7 +12,10 @@ // Button styles // ----------------------------- .@{btn-prefix-cls} { - line-height: @line-height-base; + // Fixing https://github.com/ant-design/ant-design/issues/12978 + // It is a render problem of chrome, which is only happened in the codesandbox demo + // 0.001px solution works and I don't why + line-height: @line-height-base - 0.001; .btn; .btn-default; @@ -21,6 +24,7 @@ > i, > span { pointer-events: none; + display: inline-block; } &-primary { @@ -78,7 +82,7 @@ right: -1px; background: #fff; opacity: 0.35; - content: ''; + content: ""; border-radius: inherit; z-index: 1; transition: opacity .2s; @@ -149,7 +153,7 @@ letter-spacing: .34em; } - &-two-chinese-chars > * { + &-two-chinese-chars > *:not(.@{iconfont-css-prefix}) { letter-spacing: .34em; margin-right: -.34em; } @@ -157,6 +161,31 @@ &-block { width: 100%; } + + .christmas&-primary:before { + content: ""; + display: block; + position: absolute; + top: -6px; + left: 0; + right: 0; + background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAE0AAAAXCAYAAABOHMIhAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAABiZJREFUeNrsWMtPlFcUvzPMwIDysLyRR4uATDHWCiVgSmRlios2DeiiXUFs0nRBd6arxqQhJDapkYXhP4BqDKTQhZaFNQSCaBEVJjwdHsNr5DUMDDPDzPT3u7nTDEgRKrKgc5KT+z3uufec33de99P4fD4RpL2RNgjB3kn35MkTeRERESFiYmLkGBoaKnQ6nWSNRvPPZFxr+vv7k6KioiIdDsfa8vLyQkFBgcP3Bnel3MDAQArWI0eFhISE87nb7bZ7PJ4VvLYuLi5O5+fnu9+kMNfq6+tLjIyMzMY6KeBEbK/XarXReI3lPDZMWcc4v7GxYV1dXR3Jy8ub2E5HPvJ6vRSSDH0ku1wuAfsEZOV1IEFHoeNFdHS0yMrK2knR0Lm5uR+hxLdQMjbwHTZbB41h8RGwCdc9MzMzneHh4bGJiYlf4SN8ijkfwqiIncCAAR7Iz2GPSShudjqdfeCeqampvwBQfFxc3JdYqwTv8gB8/F48A8BgKecE14V+L7ju2tpae05OzkuCCZvkPOj8mizmC6vVKtmPu+bx48cC3qI1mUyFUOyywWD4SHlELBaLJmCHNcwAghuAOujtuF4FqHO4nsX4EsAS3I4TJ04ME1h8PDE9PS09TYZoY2Pj1729vd6lpSVfkDYTPG0UkfNDRUWFgQ5Gb2Mh0N29e9eG/GQfHh4W8/PzwUy/ObQ/gMfVVlZW1iAiZdQxp3nv3LljRoL/5erVq1UIxzSiiVD9X4EDYATynCwAzGO858hCQRoaGmJFZNJz8YIcBc4BF966dau6sLAwBxVSJCUlCSThQwuU3W6XkYUok1Vzm5znQx5bbm9v77p+/frPeNSNRzZ/ISBwrG4ZR48eLamtrf2+uLjYSEG9Xi/wTISFhQlWGXohyzO/CJlVl23KQRLbABoaHx+/Z1lUZ/Hq1SsJFj3JT3hmHx8fnydPTEzMj46OziHPW2w22wxeD4Kfgadh/4YEzU8Az4DhffAn5eXlX1y6dKkEoCTspAQ9Mjs7+0BBo8Fms1lkZGTsOo0QLLRNkvnR+fEJzIMHD0xtbW39CL8JTFtSbAOvBIyLHIGVm9VzE2gKuDAMSSpcT6KXyT137lx2cnLyMXhcGDb3wq3XuWF3d/fCzZs3P0c4v5eSknJQbYLo7Ox0gC2lpaVZ3Be67Th/dnZWoAJKsJC3XA8fPhxoamp6hMb+BaaMgWcUMGtszZjiFDNmvcDI91pzG0iY4ARwkwrxkcHBwUdgNrRMbnrqoRbkVzDcvn3bl5qaWsmcgFH4G8XdEGUWFhak51AuISFBnkoCTyFbyWKxCJwIxlC0fq2rq7tcVFRkRKskjh8/Lr0+kBjCCDV/knfdv3//WX19/R8IRRNemxlu4AXwKqM+EJwdj1HbPYSwh3sCPAJDABm2LLchCjS+5/kirKGhwWk0GrMuXrxYQuX9hm/XXTMXMY+srKwI5ApZrbYmZh7deEJhAUKjLe/pLTzSsCuHrK+1tbUJVe3P6upq87Vr174rKysrYHVj/uW+OH3IfEuw4F3ee/fuPQfAvwOs5yyE4CnlFOu7BWrTCWlreO6FACpBZGwUw4BvkANLobReHb3kGZYGsGzTq/zlO8AT1ru6uoZbWlqeA6gINJAfnz59OlVLoX8Jtebm5raampqfcMvQYgTknz9//sKVK1c+y83NTdIEuCnaKMuNGzd+6+np6cCtSTkAw9D9X8Dyh+dbgaaAC1XAnUlPTy+qqqq6cPbs2UzkmWjNljiDJzpwHFnCkW2yo6NjCKW8H54wjlezKvRT09LSTsJrz5w6dSoN+Yp51ADAPUj8VoDbDq9pxrwuJcNIYQllJTIi/xopBw/VA7DJp0+f9hA78CgL5F5C8J2CpoCj8sfA6WCe/FPRhsRlZmbGIs8Y4FFO5CJgtrSsvrRVGW1V93b1myoGnKAKEcHgnwsWpg1lNI0fphwrmdqbckeU18WrnlOjqp5/j7W3BWvfQVPKa5SBkcrYCNVB65TRTlWZ1lXiXVU5xbtlDb2SPaLWYwrgHIcqPg6Vc7fbX69Yoyqfa7/AeiegbWOEVhmsVcWDwPn224iDJgla8Hd38Hd3ELQgaIeI/hZgAIPEp0vmQJdoAAAAAElFTkSuQmCC) no-repeat 50% 0; + background-size: 64px; + opacity: 1; + } + + .christmas&-primary&-lg:before { + background-size: 72px; + } + + .christmas&-primary&-sm:before { + background-size: 56px; + } + + // https://github.com/ant-design/ant-design/issues/12681 + &:empty { + vertical-align: top; + } } a.@{btn-prefix-cls} { diff --git a/components/button/style/mixin.less b/components/button/style/mixin.less index 699aa8646..138ca4903 100644 --- a/components/button/style/mixin.less +++ b/components/button/style/mixin.less @@ -16,12 +16,17 @@ &:active, &.active { .button-color(@btn-disable-color; @btn-disable-bg; @btn-disable-border); + text-shadow: none; + box-shadow: none; } } } .button-variant-primary(@color; @background) { .button-color(@color; @background; @background); + text-shadow: 0 -1px 0 rgba(0, 0, 0, .12); + box-shadow: 0 2px 0 rgba(0, 0, 0, .045); + &:hover, &:focus { .button-color(@color; ~`colorPalette("@{background}", 5)`; ~`colorPalette("@{background}", 5)`); @@ -72,6 +77,7 @@ .button-variant-ghost(@color) { .button-color(@color; transparent; @color); + text-shadow: none; &:hover, &:focus { @@ -95,7 +101,7 @@ > a:only-child { color: currentColor; &:after { - content: ''; + content: ""; position: absolute; top: 0; left: 0; @@ -112,7 +118,6 @@ > .@{btnClassName}, > span > .@{btnClassName} { position: relative; - line-height: @btn-height-base - 2px; &:hover, &:focus, @@ -158,6 +163,7 @@ user-select: none; transition: all .3s @ease-in-out; position: relative; + box-shadow: 0 2px 0 rgba(0, 0, 0, .015); > .@{iconfont-css-prefix} { line-height: 1; @@ -175,7 +181,7 @@ &:not([disabled]):active { outline: 0; - transition: none; + box-shadow: none; } &.disabled, diff --git a/components/calendar/Header.jsx b/components/calendar/Header.jsx index 43da7f308..bf0442c0b 100644 --- a/components/calendar/Header.jsx +++ b/components/calendar/Header.jsx @@ -84,7 +84,7 @@ export default { const currentYear = value.get('year') if (rangeEnd.get('year') === currentYear) { end = rangeEnd.get('month') + 1 - } else { + } else if (rangeStart.get('year') === currentYear) { start = rangeStart.get('month') } } diff --git a/components/calendar/__tests__/__snapshots__/demo.test.js.snap b/components/calendar/__tests__/__snapshots__/demo.test.js.snap index e48c4e450..00db10164 100644 --- a/components/calendar/__tests__/__snapshots__/demo.test.js.snap +++ b/components/calendar/__tests__/__snapshots__/demo.test.js.snap @@ -7,14 +7,14 @@ exports[`renders ./components/calendar/demo/basic.md correctly 1`] = `
2016
-
+
Nov
-
+
@@ -313,14 +313,14 @@ exports[`renders ./components/calendar/demo/card.md correctly 1`] = `
2016
-
+
Nov
-
+
@@ -619,14 +619,14 @@ exports[`renders ./components/calendar/demo/notice-calendar.md correctly 1`] = `
2016
-
+
Nov
-
+
@@ -1032,14 +1032,14 @@ exports[`renders ./components/calendar/demo/select.md correctly 1`] = `
2017
-
+
Jan
-
+
@@ -1336,14 +1336,14 @@ exports[`renders ./components/calendar/demo/select.md correctly 1`] = `
2017
-
+
Jan
-
+
diff --git a/components/calendar/__tests__/__snapshots__/index.test.js.snap b/components/calendar/__tests__/__snapshots__/index.test.js.snap new file mode 100644 index 000000000..6f1ea3eb1 --- /dev/null +++ b/components/calendar/__tests__/__snapshots__/index.test.js.snap @@ -0,0 +1,306 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Calendar Calendar should support locale 1`] = ` +
+
+
+
+
+
2018年
+
+
+
+
+
+
+
Oct
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SuMoTuWeThFrSa
+
+
30
+
+
+
+
+
01
+
+
+
+
+
02
+
+
+
+
+
03
+
+
+
+
+
04
+
+
+
+
+
05
+
+
+
+
+
06
+
+
+
+
+
07
+
+
+
+
+
08
+
+
+
+
+
09
+
+
+
+
+
10
+
+
+
+
+
11
+
+
+
+
+
12
+
+
+
+
+
13
+
+
+
+
+
14
+
+
+
+
+
15
+
+
+
+
+
16
+
+
+
+
+
17
+
+
+
+
+
18
+
+
+
+
+
19
+
+
+
+
+
20
+
+
+
+
+
21
+
+
+
+
+
22
+
+
+
+
+
23
+
+
+
+
+
24
+
+
+
+
+
25
+
+
+
+
+
26
+
+
+
+
+
27
+
+
+
+
+
28
+
+
+
+
+
29
+
+
+
+
+
30
+
+
+
+
+
31
+
+
+
+
+
01
+
+
+
+
+
02
+
+
+
+
+
03
+
+
+
+
+
04
+
+
+
+
+
05
+
+
+
+
+
06
+
+
+
+
+
07
+
+
+
+
+
08
+
+
+
+
+
09
+
+
+
+
+
10
+
+
+
+
+
+
+`; diff --git a/components/calendar/__tests__/index.test.js b/components/calendar/__tests__/index.test.js index be4cee1ac..2ec381db6 100644 --- a/components/calendar/__tests__/index.test.js +++ b/components/calendar/__tests__/index.test.js @@ -1,14 +1,14 @@ import Moment from 'moment' import { mount } from '@vue/test-utils' import { asyncExpect } from '@/tests/utils' -import Vue from 'vue' +import MockDate from 'mockdate' import Calendar from '..' function $$ (className) { return document.body.querySelectorAll(className) } describe('Calendar', () => { - it('Calendar should be selectable', () => { + it('Calendar should be selectable', async () => { const onSelect = jest.fn() const wrapper = mount( { @@ -18,13 +18,17 @@ describe('Calendar', () => { }, { sync: false } ) - wrapper.findAll('.ant-fullcalendar-cell').at(0).trigger('click') - expect(onSelect).toBeCalledWith(expect.anything()) - const value = onSelect.mock.calls[0][0] - expect(Moment.isMoment(value)).toBe(true) + await asyncExpect(() => { + wrapper.findAll('.ant-fullcalendar-cell').at(0).trigger('click') + }) + await asyncExpect(() => { + expect(onSelect).toBeCalledWith(expect.anything()) + const value = onSelect.mock.calls[0][0] + expect(Moment.isMoment(value)).toBe(true) + }) }) - it('only Valid range should be selectable', () => { + it('only Valid range should be selectable', async () => { const onSelect = jest.fn() const validRange = [Moment('2018-02-02'), Moment('2018-02-18')] const wrapper = mount( @@ -35,12 +39,14 @@ describe('Calendar', () => { }, { sync: false } ) - wrapper.findAll('[title="February 1, 2018"]').at(0).trigger('click') - wrapper.findAll('[title="February 2, 2018"]').at(0).trigger('click') - expect(onSelect.mock.calls.length).toBe(1) + await asyncExpect(() => { + wrapper.findAll('[title="February 1, 2018"]').at(0).trigger('click') + wrapper.findAll('[title="February 2, 2018"]').at(0).trigger('click') + expect(onSelect.mock.calls.length).toBe(1) + }) }) - it('dates other than in valid range should be disabled', () => { + it('dates other than in valid range should be disabled', async () => { const onSelect = jest.fn() const validRange = [Moment('2018-02-02'), Moment('2018-02-18')] const wrapper = mount( @@ -51,12 +57,14 @@ describe('Calendar', () => { }, { sync: false } ) - wrapper.findAll('[title="February 20, 2018"]').at(0).trigger('click') - expect(wrapper.find('[title="February 20, 2018"]').classes()).toContain('ant-fullcalendar-disabled-cell') - expect(onSelect.mock.calls.length).toBe(0) + await asyncExpect(() => { + wrapper.findAll('[title="February 20, 2018"]').at(0).trigger('click') + expect(wrapper.find('[title="February 20, 2018"]').classes()).toContain('ant-fullcalendar-disabled-cell') + expect(onSelect.mock.calls.length).toBe(0) + }) }) - it('months other than in valid range should be disabled', () => { + it('months other than in valid range should be disabled', async () => { const onSelect = jest.fn() const validRange = [Moment('2018-02-02'), Moment('2018-05-18')] const wrapper = mount( @@ -67,12 +75,14 @@ describe('Calendar', () => { }, { sync: false } ) - expect(wrapper.findAll('[title="Jan"]').at(0).classes()).toContain('ant-fullcalendar-month-panel-cell-disabled') - expect(wrapper.findAll('[title="Feb"]').at(0).classes()).not.toContain('ant-fullcalendar-month-panel-cell-disabled') - expect(wrapper.findAll('[title="Jun"]').at(0).classes()).toContain('ant-fullcalendar-month-panel-cell-disabled') - wrapper.findAll('[title="Jan"]').at(0).trigger('click') - wrapper.findAll('[title="Mar"]').at(0).trigger('click') - expect(onSelect.mock.calls.length).toBe(1) + await asyncExpect(() => { + expect(wrapper.findAll('[title="Jan"]').at(0).classes()).toContain('ant-fullcalendar-month-panel-cell-disabled') + expect(wrapper.findAll('[title="Feb"]').at(0).classes()).not.toContain('ant-fullcalendar-month-panel-cell-disabled') + expect(wrapper.findAll('[title="Jun"]').at(0).classes()).toContain('ant-fullcalendar-month-panel-cell-disabled') + wrapper.findAll('[title="Jan"]').at(0).trigger('click') + wrapper.findAll('[title="Mar"]').at(0).trigger('click') + expect(onSelect.mock.calls.length).toBe(1) + }) }) it('months other than in valid range should not be shown in header', async () => { @@ -92,15 +102,9 @@ describe('Calendar', () => { await asyncExpect(() => { $$('.ant-select-dropdown-menu-item')[0].click() }, 0) - // await asyncExpect(() => { - // wrapper.find('.ant-fullcalendar-month-select').trigger('click') - // }) - // await asyncExpect(() => { - // expect($$('.ant-select-dropdown-menu-item').length).toBe(13) - // }) }) - it('getDateRange should returns a disabledDate function', () => { + it('getDateRange should returns a disabledDate function', async () => { const validRange = [Moment('2018-02-02'), Moment('2018-05-18')] const wrapper = mount( Calendar, { @@ -111,25 +115,28 @@ describe('Calendar', () => { sync: false, } ) - const instance = wrapper.vm - const disabledDate = instance.getDateRange(validRange) - expect(disabledDate(Moment('2018-06-02'))).toBe(true) - expect(disabledDate(Moment('2018-04-02'))).toBe(false) - }) - - it('Calendar should change mode by prop', (done) => { - const monthMode = 'month' - const yearMode = 'year' - const wrapper = mount(Calendar, { sync: false }) - expect(wrapper.vm.sMode).toEqual(monthMode) - wrapper.setProps({ mode: 'year' }) - Vue.nextTick(() => { - expect(wrapper.vm.sMode).toEqual(yearMode) - done() + await asyncExpect(() => { + const instance = wrapper.vm + const disabledDate = instance.getDateRange(validRange) + expect(disabledDate(Moment('2018-06-02'))).toBe(true) + expect(disabledDate(Moment('2018-04-02'))).toBe(false) }) }) - it('Calendar should switch mode', (done) => { + it('Calendar should change mode by prop', async () => { + const monthMode = 'month' + const yearMode = 'year' + const wrapper = mount(Calendar, { sync: false }) + await asyncExpect(() => { + expect(wrapper.vm.sMode).toEqual(monthMode) + wrapper.setProps({ mode: 'year' }) + }) + await asyncExpect(() => { + expect(wrapper.vm.sMode).toEqual(yearMode) + }) + }) + + it('Calendar should switch mode', async () => { const monthMode = 'month' const yearMode = 'year' const onPanelChangeStub = jest.fn() @@ -144,12 +151,26 @@ describe('Calendar', () => { sync: false, } ) - expect(wrapper.vm.sMode).toEqual(yearMode) - wrapper.vm.setType('date') - Vue.nextTick(() => { + await asyncExpect(() => { + expect(wrapper.vm.sMode).toEqual(yearMode) + wrapper.vm.setType('date') + }) + await asyncExpect(() => { expect(wrapper.vm.sMode).toEqual(monthMode) expect(onPanelChangeStub).toHaveBeenCalledTimes(1) - done() + }) + }) + + it('Calendar should support locale', async () => { + MockDate.set(Moment('2018-10-19')) + // eslint-disable-next-line + const zhCN = require('../locale/zh_CN').default; + const wrapper = mount(Calendar, { propsData: { + locale: zhCN, + }, sync: false }) + await asyncExpect(() => { + expect(wrapper.html()).toMatchSnapshot() + MockDate.reset() }) }) }) diff --git a/components/calendar/demo/index.vue b/components/calendar/demo/index.vue index 9be0423c9..f81368e01 100644 --- a/components/calendar/demo/index.vue +++ b/components/calendar/demo/index.vue @@ -28,6 +28,7 @@ When data is in the form of dates, such as schedules, timetables, prices calenda export default { category: 'Components', type: 'Data Display', + zhType: '数据展示', subtitle: '日历', cols: 1, title: 'Calendar', diff --git a/components/calendar/index.en-US.md b/components/calendar/index.en-US.md index c248c7404..7763009f8 100644 --- a/components/calendar/index.en-US.md +++ b/components/calendar/index.en-US.md @@ -4,7 +4,7 @@ **Note:** Part of the Calendar's locale is read from `value`. So, please set the locale of `moment` correctly. ````html -// The default locale is en-US, if you want to use other locale, just set locale in entry file globaly. +// The default locale is en-US, if you want to use other locale, just set locale in entry file globally. // import moment from 'moment'; // import 'moment/locale/zh-cn'; // moment.locale('zh-cn'); diff --git a/components/calendar/index.jsx b/components/calendar/index.jsx index f21e0f257..891df3a1e 100644 --- a/components/calendar/index.jsx +++ b/components/calendar/index.jsx @@ -41,7 +41,7 @@ export const CalendarProps = () => ({ // monthCellRender: PropTypes.func, // dateFullCellRender: PropTypes.func, // monthFullCellRender: PropTypes.func, - locale: PropTypes.any, + locale: PropTypes.object, // onPanelChange?: (date?: moment.Moment, mode?: CalendarMode) => void; // onSelect?: (date?: moment.Moment) => void; disabledDate: PropTypes.func, @@ -226,13 +226,24 @@ const Calendar = { ) }, + getDefaultLocale () { + const result = { + ...enUS, + ...this.$props.locale, + } + result.lang = { + ...result.lang, + ...(this.$props.locale || {}).lang, + } + return result + }, }, render () { return ( `; -exports[`renders ./components/card/demo/concise.md correctly 1`] = ` -
-
-

Card content

-

Card content

-

Card content

-
-
-`; - exports[`renders ./components/card/demo/flexible-content.md correctly 1`] = `
example
@@ -236,9 +226,9 @@ exports[`renders ./components/card/demo/meta.md correctly 1`] = `
    -
  • -
  • -
  • +
  • +
  • +
`; @@ -262,13 +252,13 @@ exports[`renders ./components/card/demo/tabs.md correctly 1`] = `
-
-
+
+
- @@ -278,10 +268,15 @@ exports[`renders ./components/card/demo/tabs.md correctly 1`] = `
-
-
+
+
+
+
+
+
+
@@ -292,8 +287,8 @@ exports[`renders ./components/card/demo/tabs.md correctly 1`] = `
-
-
+
+
@@ -308,11 +303,16 @@ exports[`renders ./components/card/demo/tabs.md correctly 1`] = `
-
+
+
-
+
+
+
+
+
diff --git a/components/card/__tests__/__snapshots__/index.test.js.snap b/components/card/__tests__/__snapshots__/index.test.js.snap index 3bf46ab53..7c2b47bf3 100644 --- a/components/card/__tests__/__snapshots__/index.test.js.snap +++ b/components/card/__tests__/__snapshots__/index.test.js.snap @@ -59,3 +59,17 @@ exports[`Card should still have padding when card which set padding to 0 is load
`; + +exports[`Card title should be vertically aligned 1`] = ` +
+
+
+
Card title
+
+
+
+
+

Card content

+
+
+`; diff --git a/components/card/__tests__/index.test.js b/components/card/__tests__/index.test.js index b1acbcc03..01a6ad63c 100644 --- a/components/card/__tests__/index.test.js +++ b/components/card/__tests__/index.test.js @@ -1,5 +1,6 @@ import { mount } from '@vue/test-utils' import Card from '../index' +import Button from '../../button/index' const testMethod = typeof window !== 'undefined' ? it : xit @@ -46,4 +47,15 @@ describe('Card', () => { }) expect(wrapper.html()).toMatchSnapshot() }) + + it('title should be vertically aligned', () => { + const wrapper = mount({ + render () { + return Button} style={{ width: '300px' }}> +

Card content

+
+ }, + }) + expect(wrapper.html()).toMatchSnapshot() + }) }) diff --git a/components/card/demo/concise.md b/components/card/demo/concise.md deleted file mode 100644 index 0ee3c79d7..000000000 --- a/components/card/demo/concise.md +++ /dev/null @@ -1,19 +0,0 @@ - - #### 简洁卡片 - 只包含内容区域 - - - - #### Simple card - A simple card only containing a content area. - - -```html - -``` diff --git a/components/card/demo/index.vue b/components/card/demo/index.vue index 541dcb930..41d6f55ea 100644 --- a/components/card/demo/index.vue +++ b/components/card/demo/index.vue @@ -1,7 +1,6 @@ +``` + diff --git a/components/cascader/index.en-US.md b/components/cascader/index.en-US.md index a8376f6d0..386438080 100644 --- a/components/cascader/index.en-US.md +++ b/components/cascader/index.en-US.md @@ -26,6 +26,7 @@ | popupVisible | set visible of cascader popup | boolean | - | | showSearch | Whether show search input in single mode. | boolean\|object | false | | size | input size, one of `large` `default` `small` | string | `default` | +| suffixIcon | The custom suffix icon | string \| VNode \| slot | - | | value(v-model) | selected value | string\[] | - | Fields in `showSearch`: diff --git a/components/cascader/index.jsx b/components/cascader/index.jsx index 691a31913..9c7620c40 100644 --- a/components/cascader/index.jsx +++ b/components/cascader/index.jsx @@ -7,8 +7,9 @@ import omit from 'omit.js' import KeyCode from '../_util/KeyCode' import Input from '../input' import Icon from '../icon' -import { hasProp, filterEmpty, getOptionProps, getStyle, getClass, getAttrs } from '../_util/props-util' +import { hasProp, filterEmpty, getOptionProps, getStyle, getClass, getAttrs, getComponentFromProp, isValidElement } from '../_util/props-util' import BaseMixin from '../_util/BaseMixin' +import { cloneElement } from '../_util/vnode' const CascaderOptionType = PropTypes.shape({ value: PropTypes.string, @@ -74,6 +75,7 @@ const CascaderProps = { popupVisible: PropTypes.bool, fieldNames: FieldNamesType, autoFocus: PropTypes.bool, + suffixIcon: PropTypes.any, } function defaultFilterOption (inputValue, path, names) { @@ -88,7 +90,7 @@ function defaultSortFilteredOption (a, b, inputValue, names) { return a.findIndex(callback) - b.findIndex(callback) } -function getFilledFieldNames (fieldNames = {}) { +function getFilledFieldNames ({ fieldNames = {}}) { const names = { children: fieldNames.children || 'children', label: fieldNames.label || 'label', @@ -110,13 +112,13 @@ const Cascader = { }, data () { this.cachedOptions = [] - const { value, defaultValue, popupVisible, showSearch, options, changeOnSelect, flattenTree, fieldNames } = this + const { value, defaultValue, popupVisible, showSearch, options, flattenTree } = this return { sValue: value || defaultValue || [], inputValue: '', inputFocused: false, sPopupVisible: popupVisible, - flattenOptions: showSearch ? flattenTree(options, changeOnSelect, fieldNames) : undefined, + flattenOptions: showSearch ? flattenTree(options, this.$props) : undefined, } }, mounted () { @@ -135,7 +137,7 @@ const Cascader = { }, options (val) { if (this.showSearch) { - this.setState({ flattenOptions: this.flattenTree(this.options, this.changeOnSelect, this.fieldNames) }) + this.setState({ flattenOptions: this.flattenTree(this.options, this.$props) }) } }, }, @@ -218,8 +220,8 @@ const Cascader = { }, getLabel () { - const { options, $scopedSlots, fieldNames } = this - const names = getFilledFieldNames(fieldNames) + const { options, $scopedSlots } = this + const names = getFilledFieldNames(this.$props) const displayRender = this.displayRender || $scopedSlots.displayRender || defaultDisplayRender const value = this.sValue const unwrappedValue = Array.isArray(value[0]) ? value[0] : value @@ -242,18 +244,18 @@ const Cascader = { } }, - flattenTree (options, changeOnSelect, fieldNames, ancestor = []) { - const names = getFilledFieldNames(fieldNames) + flattenTree (options, props, ancestor = []) { + const names = getFilledFieldNames(props) let flattenOptions = [] const childrenName = names.children options.forEach((option) => { const path = ancestor.concat(option) - if (changeOnSelect || !option[childrenName] || !option[childrenName].length) { + if (props.changeOnSelect || !option[childrenName] || !option[childrenName].length) { flattenOptions.push(path) } if (option[childrenName]) { flattenOptions = flattenOptions.concat( - this.flattenTree(option[childrenName], changeOnSelect, fieldNames, path) + this.flattenTree(option[childrenName], props, path) ) } }) @@ -261,8 +263,8 @@ const Cascader = { }, generateFilteredOptions (prefixCls) { - const { showSearch, notFoundContent, $scopedSlots, fieldNames } = this - const names = getFilledFieldNames(fieldNames) + const { showSearch, notFoundContent, $scopedSlots } = this + const names = getFilledFieldNames(this.$props) const { filter = defaultFilterOption, // render = this.defaultRenderFilteredOption, @@ -308,6 +310,8 @@ const Cascader = { const { $slots, sPopupVisible, inputValue, $listeners } = this const { sValue: value, inputFocused } = this.$data const props = getOptionProps(this) + let suffixIcon = getComponentFromProp(this, 'suffixIcon') + suffixIcon = Array.isArray(suffixIcon) ? suffixIcon[0] : suffixIcon const { prefixCls, inputPrefixCls, placeholder, size, disabled, allowClear, showSearch = false, ...otherProps } = props @@ -318,7 +322,8 @@ const Cascader = { }) const clearIcon = (allowClear && !disabled && value.length > 0) || inputValue ? ( {suffixIcon}) || ( + + ) + const input = children.length ? children : ( : null} {clearIcon} - + {inputIcon} + + ) + + const expandIcon = ( + + ) + + const loadingIcon = ( + + ) const cascaderProps = { @@ -427,6 +455,8 @@ const Cascader = { value: value, popupVisible: sPopupVisible, dropdownMenuColumnStyle: dropdownMenuColumnStyle, + expandIcon, + loadingIcon, }, on: { ...$listeners, diff --git a/components/cascader/index.zh-CN.md b/components/cascader/index.zh-CN.md index aad91e236..1a0d58d17 100644 --- a/components/cascader/index.zh-CN.md +++ b/components/cascader/index.zh-CN.md @@ -26,6 +26,7 @@ | popupVisible | 控制浮层显隐 | boolean | - | | showSearch | 在选择框中显示搜索框 | boolean | false | | size | 输入框大小,可选 `large` `default` `small` | string | `default` | +| suffixIcon | 自定义的选择框后缀图标 | string \| VNode \| slot | - | | value(v-model) | 指定选中项 | string\[] | - | `showSearch` 为对象时,其中的字段: diff --git a/components/cascader/style/index.less b/components/cascader/style/index.less index 29a33593a..5de52826e 100644 --- a/components/cascader/style/index.less +++ b/components/cascader/style/index.less @@ -13,7 +13,7 @@ background-color: transparent !important; cursor: pointer; width: 100%; - position: static; + position: relative; } &-picker-show-search &-input.@{ant-prefix}-input { @@ -101,13 +101,9 @@ margin-top: -6px; line-height: 12px; color: @disabled-color; - &:before { - transition: transform .2s; - } + transition: transform .2s; &&-expand { - &:before { - transform: rotate(180deg); - } + transform: rotate(180deg); } } } @@ -205,17 +201,14 @@ &-expand { position: relative; padding-right: 24px; - &:after { - .iconfont-font("\e61f"); - .iconfont-size-under-12px(8px); - color: @text-color-secondary; - position: absolute; - right: @control-padding-horizontal; - } } - &-loading:after { - .iconfont-font("\e64d"); - animation: loadingCircle 1s infinite linear; + + &-expand &-expand-icon, + &-expand &-loading-icon { + .iconfont-size-under-12px(10px); + color: @text-color-secondary; + position: absolute; + right: @control-padding-horizontal; } & &-keyword { diff --git a/components/checkbox/Checkbox.jsx b/components/checkbox/Checkbox.jsx index 763d3c32b..ba69b9616 100644 --- a/components/checkbox/Checkbox.jsx +++ b/components/checkbox/Checkbox.jsx @@ -55,7 +55,10 @@ export default { } = props const checkboxProps = { props: { ...restProps, prefixCls }, on: restListeners, attrs: getAttrs(this) } if (checkboxGroup) { - checkboxProps.on.change = () => checkboxGroup.toggleOption({ label: children, value: props.value }) + checkboxProps.on.change = (...args) => { + this.$emit('change', ...args) + checkboxGroup.toggleOption({ label: children, value: props.value }) + } checkboxProps.props.checked = checkboxGroup.sValue.indexOf(props.value) !== -1 checkboxProps.props.disabled = props.disabled || checkboxGroup.disabled } else { @@ -63,6 +66,8 @@ export default { } const classString = classNames({ [`${prefixCls}-wrapper`]: true, + [`${prefixCls}-wrapper-checked`]: checkboxProps.props.checked, + [`${prefixCls}-wrapper-disabled`]: checkboxProps.props.disabled, }) const checkboxClass = classNames({ [`${prefixCls}-indeterminate`]: indeterminate, @@ -78,7 +83,7 @@ export default { class={checkboxClass} ref='vcCheckbox' /> - {children !== undefined ? {children} : null} + {children !== undefined && {children}} ) }, diff --git a/components/checkbox/Group.jsx b/components/checkbox/Group.jsx index 2ddc14655..4e2e8c989 100644 --- a/components/checkbox/Group.jsx +++ b/components/checkbox/Group.jsx @@ -1,6 +1,7 @@ import Checkbox from './Checkbox' import hasProp from '../_util/props-util' +function noop () {} export default { name: 'ACheckboxGroup', props: { @@ -82,7 +83,7 @@ export default { disabled={'disabled' in option ? option.disabled : props.disabled} value={option.value} checked={state.sValue.indexOf(option.value) !== -1} - onChange={() => this.toggleOption(option)} + onChange={option.onChange || noop} class={`${groupPrefixCls}-item`} > {option.label} diff --git a/components/checkbox/__tests__/__snapshots__/demo.test.js.snap b/components/checkbox/__tests__/__snapshots__/demo.test.js.snap index e1b7c71a6..1a8b85542 100644 --- a/components/checkbox/__tests__/__snapshots__/demo.test.js.snap +++ b/components/checkbox/__tests__/__snapshots__/demo.test.js.snap @@ -7,27 +7,27 @@ exports[`renders ./components/checkbox/demo/check-all.md correctly 1`] = `

-
+
`; exports[`renders ./components/checkbox/demo/controller.md correctly 1`] = `
-

`; -exports[`renders ./components/checkbox/demo/disabled.md correctly 1`] = `

`; +exports[`renders ./components/checkbox/demo/disabled.md correctly 1`] = `

`; exports[`renders ./components/checkbox/demo/group.md correctly 1`] = `

-

-

-
+

+

+
`; diff --git a/components/checkbox/__tests__/group.test.js b/components/checkbox/__tests__/group.test.js index 1fd53a51f..d4db61bde 100644 --- a/components/checkbox/__tests__/group.test.js +++ b/components/checkbox/__tests__/group.test.js @@ -103,4 +103,21 @@ describe('CheckboxGroup', () => { expect(wrapper.vm.sValue).toEqual(['Apple']) }) }) + + // https://github.com/ant-design/ant-design/issues/12642 + it('should trigger onChange in sub Checkbox', () => { + const onChange = jest.fn() + const wrapper = mount({ + render () { + return ( + + + + ) + }, + }) + wrapper.findAll('.ant-checkbox-input').at(0).trigger('change') + expect(onChange).toBeCalled() + expect(onChange.mock.calls[0][0].target.value).toEqual('my') + }) }) diff --git a/components/checkbox/demo/index.vue b/components/checkbox/demo/index.vue index ca86cf930..8fd01a22f 100644 --- a/components/checkbox/demo/index.vue +++ b/components/checkbox/demo/index.vue @@ -29,6 +29,7 @@ category: 'Components', subtitle: '多选框', type: 'Data Entry', + zhType: '数据录入', title: 'Checkbox', render () { return ( diff --git a/components/checkbox/index.en-US.md b/components/checkbox/index.en-US.md index 592ea8f00..5471f0b9a 100644 --- a/components/checkbox/index.en-US.md +++ b/components/checkbox/index.en-US.md @@ -23,7 +23,7 @@ | -------- | ----------- | ---- | ------- | | defaultValue | Default selected value | string\[] | \[] | | disabled | Disable all checkboxes | boolean | false | -| options | Specifies options | string\[] \| Array<{ label: string value: string disabled?: boolean }> | \[] | +| options | Specifies options | string\[] \| Array<{ label: string value: string disabled?: boolean, onChange?: function }> | \[] | | value | Used for setting the currently selected value. | string\[] | \[] | #### events diff --git a/components/checkbox/index.zh-CN.md b/components/checkbox/index.zh-CN.md index aba61cbe2..ff59f2d50 100644 --- a/components/checkbox/index.zh-CN.md +++ b/components/checkbox/index.zh-CN.md @@ -25,9 +25,8 @@ | --- | --- | --- | --- | | defaultValue | 默认选中的选项 | string\[] | \[] | | disabled | 整组失效 | boolean | false | -| options | 指定可选项 | string\[] \| Array<{ label: string value: string disabled?: boolean }> | \[] | +| options | 指定可选项 | string\[] \| Array<{ label: string value: string disabled?: boolean, onChange?: function }> | \[] | | value | 指定选中的选项 | string\[] | \[] | -| onChange | 变化时回调函数 | Function(checkedValue) | - | #### 事件 | 事件名称 | 说明 | 回调参数 | diff --git a/components/checkbox/style/mixin.less b/components/checkbox/style/mixin.less index f32d8dc12..81fc57010 100644 --- a/components/checkbox/style/mixin.less +++ b/components/checkbox/style/mixin.less @@ -28,7 +28,7 @@ height: 100%; border-radius: @border-radius-sm; border: 1px solid @checkbox-color; - content: ''; + content: ""; animation: antCheckboxEffect 0.36s ease-in-out; animation-fill-mode: both; visibility: hidden; @@ -46,10 +46,13 @@ display: block; width: @checkbox-size; height: @checkbox-size; - border: @border-width-base @border-style-base @border-color-base; + border: @checkbox-border-width @border-style-base @border-color-base; border-radius: @border-radius-sm; background-color: @checkbox-check-color; transition: all .3s; + // Fix IE checked style + // https://github.com/ant-design/ant-design/issues/12597 + border-collapse: separate; &:after { @check-width: (@checkbox-size / 14) * 5px; @@ -84,25 +87,6 @@ } } - // 半选状态 - .@{checkbox-prefix-cls}-indeterminate .@{checkbox-inner-prefix-cls}:after { - @indeterminate-width: @checkbox-size - 8px; - @indeterminate-height: @checkbox-size - 8px; - content: ' '; - transform: translate(-50%, -50%) scale(1); - border: 0; - left: 50%; - top: 50%; - width: @indeterminate-width; - height: @indeterminate-height; - background-color: @checkbox-color; - opacity: 1; - } - - .@{checkbox-prefix-cls}-indeterminate.@{checkbox-prefix-cls}-disabled .@{checkbox-inner-prefix-cls}:after { - border-color: @disabled-color; - } - // 选中状态 .@{checkbox-prefix-cls}-checked .@{checkbox-inner-prefix-cls}:after { transform: rotate(45deg) scale(1); @@ -143,6 +127,7 @@ &:after { animation-name: none; border-color: @input-disabled-bg; + border-collapse: separate; } } @@ -182,6 +167,32 @@ margin-left: 0; } } + + // 半选状态 + .@{checkbox-prefix-cls}-indeterminate { + .@{checkbox-inner-prefix-cls} { + background-color: #fff; + border-color: @border-color-base; + } + .@{checkbox-inner-prefix-cls}:after { + @indeterminate-width: @checkbox-size - 8px; + @indeterminate-height: @checkbox-size - 8px; + content: ' '; + transform: translate(-50%, -50%) scale(1); + border: 0; + left: 50%; + top: 50%; + width: @indeterminate-width; + height: @indeterminate-height; + background-color: @checkbox-color; + opacity: 1; + } + + &.@{checkbox-prefix-cls}-disabled .@{checkbox-inner-prefix-cls}:after { + border-color: @disabled-color; + background-color: @disabled-color; + } + } } @keyframes antCheckboxEffect { diff --git a/components/collapse/Collapse.jsx b/components/collapse/Collapse.jsx index c0a850bea..05e9f4c57 100644 --- a/components/collapse/Collapse.jsx +++ b/components/collapse/Collapse.jsx @@ -1,9 +1,7 @@ - -import PropTypes from '../_util/vue-types' import animation from '../_util/openAnimation' import { getOptionProps, initDefaultProps } from '../_util/props-util' import VcCollapse, { collapseProps } from '../vc-collapse' - +import Icon from '../icon' export default { name: 'ACollapse', model: { @@ -15,6 +13,13 @@ export default { bordered: true, openAnimation: animation, }), + methods: { + renderExpandIcon () { + return ( + + ) + }, + }, render () { const { prefixCls, bordered, $listeners } = this const collapseClassName = { @@ -23,6 +28,7 @@ export default { const rcCollapeProps = { props: { ...getOptionProps(this), + expandIcon: this.renderExpandIcon, }, class: collapseClassName, on: $listeners, diff --git a/components/collapse/__tests__/__snapshots__/demo.test.js.snap b/components/collapse/__tests__/__snapshots__/demo.test.js.snap index 07d713e20..64c2ddc04 100644 --- a/components/collapse/__tests__/__snapshots__/demo.test.js.snap +++ b/components/collapse/__tests__/__snapshots__/demo.test.js.snap @@ -4,15 +4,21 @@ exports[`renders ./components/collapse/demo/accordion.md correctly 1`] = `
- +
- +
- +
@@ -23,7 +29,9 @@ exports[`renders ./components/collapse/demo/basic.md correctly 1`] = `
-
This is panel header 1
+
This is panel header 1

A dog is a type of domesticated animal.Known for its loyalty and faithfulness,it can be found as a welcome guest in many households across the world.

@@ -31,11 +39,15 @@ exports[`renders ./components/collapse/demo/basic.md correctly 1`] = `
-
This is panel header 2
+
This is panel header 2
-
This is panel header 3
+
This is panel header 3
@@ -46,7 +58,9 @@ exports[`renders ./components/collapse/demo/borderless.md correctly 1`] = `
-
This is panel header 1
+
This is panel header 1

A dog is a type of domesticated animal.Known for its loyalty and faithfulness,it can be found as a welcome guest in many households across the world.

@@ -54,11 +68,15 @@ exports[`renders ./components/collapse/demo/borderless.md correctly 1`] = `
-
This is panel header 2
+
This is panel header 2
-
This is panel header 3
+
This is panel header 3
@@ -69,8 +87,13 @@ exports[`renders ./components/collapse/demo/custom.md correctly 1`] = `
-
- This is panel header 1
+
+ This is panel header 1

A dog is a type of domesticated animal.Known for its loyalty and faithfulness,it can be found as a welcome guest in many households across the world.

@@ -78,11 +101,15 @@ exports[`renders ./components/collapse/demo/custom.md correctly 1`] = `
-
This is panel header 2
+
This is panel header 2
-
This is panel header 3
+
This is panel header 3
@@ -93,15 +120,21 @@ exports[`renders ./components/collapse/demo/mix.md correctly 1`] = `
-
This is panel header 1
+
This is panel header 1
-
This is panel header 2
+
This is panel header 2
-
This is panel header 3
+
This is panel header 3
@@ -112,7 +145,9 @@ exports[`renders ./components/collapse/demo/noarrow.md correctly 1`] = `
-
This is panel header with arrow icon
+
This is panel header with arrow icon

A dog is a type of domesticated animal.Known for its loyalty and faithfulness,it can be found as a welcome guest in many households across the world.

diff --git a/components/collapse/demo/index.vue b/components/collapse/demo/index.vue index 3a671fb60..3536dd1e5 100644 --- a/components/collapse/demo/index.vue +++ b/components/collapse/demo/index.vue @@ -29,6 +29,7 @@ const md = { export default { category: 'Components', type: 'Data Display', + zhType: '数据展示', title: 'Collapse', subtitle: '折叠面板', cols: 1, diff --git a/components/collapse/style/index.less b/components/collapse/style/index.less index 622f48d32..74447fb11 100644 --- a/components/collapse/style/index.less +++ b/components/collapse/style/index.less @@ -37,17 +37,17 @@ .arrow { .iconfont-mixin(); - .collapse-close(); font-size: @font-size-sm; position: absolute; display: inline-block; line-height: 46px; vertical-align: top; - transition: transform 0.24s; - top: 0; + top: 50%; + transform: translateY(-50%); left: @padding-md; - &:before { - content: "\E61F"; + & svg { + .collapse-close(); + transition: transform 0.24s; } } @@ -89,7 +89,7 @@ } & > &-item > &-header[aria-expanded="true"] { - .arrow { + .anticon-right svg { .collapse-open(); } } diff --git a/components/date-picker/RangePicker.jsx b/components/date-picker/RangePicker.jsx index f65d572f6..e62240197 100644 --- a/components/date-picker/RangePicker.jsx +++ b/components/date-picker/RangePicker.jsx @@ -8,8 +8,9 @@ import Icon from '../icon' import Tag from '../tag' import interopDefault from '../_util/interopDefault' import { RangePickerProps } from './interface' -import { hasProp, getOptionProps, initDefaultProps, mergeProps } from '../_util/props-util' +import { hasProp, getOptionProps, initDefaultProps, mergeProps, getComponentFromProp, isValidElement } from '../_util/props-util' import BaseMixin from '../_util/BaseMixin' +import { cloneElement } from '../_util/vnode' function noop () {} function getShowDateFromValue (value) { const [start, end] = value @@ -130,6 +131,7 @@ export default { formatValue(value[0], this.format), formatValue(value[1], this.format), ]) + this.focus() }, handleOpenChange (open) { @@ -174,6 +176,7 @@ export default { this.setValue(value, true) this.$emit('ok', value) + this.$emit('openChange', false) }, setValue (value, hidePanel) { @@ -224,17 +227,18 @@ export default { ) }) - const rangeNode = ( + const rangeNode = (operations && operations.length > 0) ? ( - ) +
) : null return [rangeNode, customFooter] }, }, render () { const props = getOptionProps(this) + let suffixIcon = getComponentFromProp(this, 'suffixIcon') + suffixIcon = Array.isArray(suffixIcon) ? suffixIcon[0] : suffixIcon const { sValue: value, sShowDate: showDate, sHoverValue: hoverValue, sOpen: open, $listeners, $scopedSlots } = this const { calendarChange = noop, ok = noop, focus = noop, blur = noop, panelChange = noop } = $listeners const { @@ -296,7 +300,7 @@ export default { ok: ok, valueChange: this.handleShowDateChange, hoverChange: this.handleHoverChange, - panelChange: panelChange, + panelChange, inputSelect: this.handleCalendarInputSelect, }, class: calendarClassName, @@ -316,12 +320,24 @@ export default { const clearIcon = (!props.disabled && props.allowClear && value && (value[0] || value[1])) ? ( ) : null + const inputIcon = suffixIcon && ( + isValidElement(suffixIcon) + ? cloneElement( + suffixIcon, + { + class: `${prefixCls}-picker-icon`, + }, + ) : {suffixIcon}) || ( + + ) + const input = ({ value: inputValue }) => { const start = inputValue[0] const end = inputValue[1] @@ -345,7 +361,7 @@ export default { tabIndex={-1} /> {clearIcon} - + {inputIcon} ) } diff --git a/components/date-picker/WeekPicker.jsx b/components/date-picker/WeekPicker.jsx index bd3fa6bf6..d7526e6bf 100644 --- a/components/date-picker/WeekPicker.jsx +++ b/components/date-picker/WeekPicker.jsx @@ -3,10 +3,11 @@ import * as moment from 'moment' import Calendar from '../vc-calendar' import VcDatePicker from '../vc-calendar/src/Picker' import Icon from '../icon' -import { hasProp, getOptionProps, initDefaultProps } from '../_util/props-util' +import { hasProp, getOptionProps, initDefaultProps, getComponentFromProp, isValidElement } from '../_util/props-util' import BaseMixin from '../_util/BaseMixin' import { WeekPickerProps } from './interface' import interopDefault from '../_util/interopDefault' +import { cloneElement } from '../_util/vnode' function formatValue (value, format) { return (value && value.format(format)) || '' @@ -74,6 +75,7 @@ export default { this.setState({ sValue: value }) } this.$emit('change', value, formatValue(value, this.format)) + this.focus() }, clearSelection (e) { e.preventDefault() @@ -92,6 +94,8 @@ export default { render () { const props = getOptionProps(this) + let suffixIcon = getComponentFromProp(this, 'suffixIcon') + suffixIcon = Array.isArray(suffixIcon) ? suffixIcon[0] : suffixIcon const { prefixCls, disabled, pickerClass, popupStyle, pickerInputClass, format, allowClear, locale, localeCode, disabledDate, @@ -119,11 +123,24 @@ export default { ) const clearIcon = (!disabled && allowClear && this.sValue) ? ( ) : null + + const inputIcon = suffixIcon && ( + isValidElement(suffixIcon) + ? cloneElement( + suffixIcon, + { + class: `${prefixCls}-picker-icon`, + }, + ) : {suffixIcon}) || ( + + ) + const input = ({ value }) => { return ( @@ -138,7 +155,7 @@ export default { onBlur={blur} /> {clearIcon} - + {inputIcon} ) } diff --git a/components/date-picker/__tests__/DatePicker.test.js b/components/date-picker/__tests__/DatePicker.test.js index 26b9c4b57..6eed25bf0 100644 --- a/components/date-picker/__tests__/DatePicker.test.js +++ b/components/date-picker/__tests__/DatePicker.test.js @@ -146,41 +146,38 @@ describe('DatePicker', () => { }, { sync: false, attachToDocument: true }) await asyncExpect(() => { openPanel(wrapper) - }) + }, 0) await asyncExpect(() => { nextYear() + }, 1000) + await asyncExpect(() => { + expect(handleChange).not.toBeCalled() + }, 0) + await asyncExpect(() => { + nextMonth() }, 0) await asyncExpect(() => { expect(handleChange).not.toBeCalled() }) - await asyncExpect(() => { - nextMonth() - }) - await asyncExpect(() => { - expect(handleChange).not.toBeCalled() - }) await asyncExpect(() => { selectDateFromBody(moment('2017-12-22')) - }) + }, 1000) await asyncExpect(() => { expect(handleChange).toBeCalled() - }) - await asyncExpect(() => { - - }) + }, 0) }) it('clear input', async () => { const wrapper = mount(DatePicker, { sync: false, attachToDocument: true }) await asyncExpect(() => { openPanel(wrapper) - }) - await asyncExpect(() => { - selectDateFromBody(moment('2016-11-23')) }, 0) + await asyncExpect(() => { + selectDateFromBody(moment('2016-11-23')) + }, 100) await asyncExpect(() => { clearInput(wrapper) - }, 300) + }, 1000) await asyncExpect(() => { openPanel(wrapper) }, 0) diff --git a/components/date-picker/__tests__/MonthPicker.test.js b/components/date-picker/__tests__/MonthPicker.test.js index 2e115e9af..f0cf2e6c5 100644 --- a/components/date-picker/__tests__/MonthPicker.test.js +++ b/components/date-picker/__tests__/MonthPicker.test.js @@ -1,8 +1,26 @@ +import { mount } from '@vue/test-utils' +import { asyncExpect } from '@/tests/utils' +import moment from 'moment' import DatePicker from '..' import focusTest from '../../../tests/shared/focusTest' +import { openPanel, $$ } from './utils' const { MonthPicker } = DatePicker describe('MonthPicker', () => { focusTest(MonthPicker) + it('reset select item when popup close', async () => { + const wrapper = mount(MonthPicker, { + propsData: { value: moment('2018-07-01') }, + sync: false, + attachToDocument: true, + }) + await asyncExpect(() => { + openPanel(wrapper) + }) + await asyncExpect(() => { + $$('.ant-calendar-month-panel-month')[0].click() + $$('.ant-calendar-month-panel-cell')[6].getAttribute('class').split(' ').includes('ant-calendar-month-panel-selected-cell') + }, 0) + }) }) diff --git a/components/date-picker/__tests__/RangePicker.test.js b/components/date-picker/__tests__/RangePicker.test.js index 84abd5dde..6d0854b21 100644 --- a/components/date-picker/__tests__/RangePicker.test.js +++ b/components/date-picker/__tests__/RangePicker.test.js @@ -325,4 +325,26 @@ describe('RangePicker', () => { }).not.toThrow() }) }) + // https://github.com/ant-design/ant-design/issues/11631 + it('triggers onOpenChange when click on preset range', async () => { + const handleOpenChange = jest.fn() + const range = [moment().subtract(2, 'd'), moment()] + const wrapper = mount({ + render () { + return + }, + }, { + sync: false, + attachToDocument: true, + }) + await asyncExpect(() => { + wrapper.find('.ant-calendar-picker-input').trigger('click') + }) + await asyncExpect(() => { + $$('.ant-calendar-range-quick-selector .ant-tag')[0].click() + }, 0) + await asyncExpect(() => { + expect(handleOpenChange).toBeCalledWith(false) + }) + }) }) diff --git a/components/date-picker/__tests__/__snapshots__/DatePicker.test.js.snap b/components/date-picker/__tests__/__snapshots__/DatePicker.test.js.snap index 0fa634fcb..1a318dbe4 100644 --- a/components/date-picker/__tests__/__snapshots__/DatePicker.test.js.snap +++ b/components/date-picker/__tests__/__snapshots__/DatePicker.test.js.snap @@ -1,3 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`DatePicker prop locale should works 1`] = `
`; +exports[`DatePicker prop locale should works 1`] = `
`; diff --git a/components/date-picker/__tests__/__snapshots__/RangePicker.test.js.snap b/components/date-picker/__tests__/__snapshots__/RangePicker.test.js.snap index bba446b62..fd891fc39 100644 --- a/components/date-picker/__tests__/__snapshots__/RangePicker.test.js.snap +++ b/components/date-picker/__tests__/__snapshots__/RangePicker.test.js.snap @@ -687,7 +687,11 @@ exports[`RangePicker switch to corresponding month panel when click presetted ra - +
diff --git a/components/date-picker/__tests__/__snapshots__/WeekPicker.test.js.snap b/components/date-picker/__tests__/__snapshots__/WeekPicker.test.js.snap index 1ced1846f..e77c56ad6 100644 --- a/components/date-picker/__tests__/__snapshots__/WeekPicker.test.js.snap +++ b/components/date-picker/__tests__/__snapshots__/WeekPicker.test.js.snap @@ -1,3 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`WeekPicker should support style prop 1`] = ``; +exports[`WeekPicker should support style prop 1`] = ``; diff --git a/components/date-picker/__tests__/__snapshots__/demo.test.js.snap b/components/date-picker/__tests__/__snapshots__/demo.test.js.snap index 04454334c..6c1e5f529 100644 --- a/components/date-picker/__tests__/__snapshots__/demo.test.js.snap +++ b/components/date-picker/__tests__/__snapshots__/demo.test.js.snap @@ -1,27 +1,77 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`renders ./components/date-picker/demo/basic.md correctly 1`] = `


~
`; +exports[`renders ./components/date-picker/demo/basic.md correctly 1`] = ` +


~
+`; -exports[`renders ./components/date-picker/demo/date-render.md correctly 1`] = `
~
`; +exports[`renders ./components/date-picker/demo/date-render.md correctly 1`] = ` +
~
+`; -exports[`renders ./components/date-picker/demo/disabled.md correctly 1`] = `


~
`; +exports[`renders ./components/date-picker/demo/disabled.md correctly 1`] = ` +


~
+`; -exports[`renders ./components/date-picker/demo/disabled-date.md correctly 1`] = `


~
`; +exports[`renders ./components/date-picker/demo/disabled-date.md correctly 1`] = ` +


~
+`; -exports[`renders ./components/date-picker/demo/extra-footer.md correctly 1`] = `
~ ~
`; +exports[`renders ./components/date-picker/demo/extra-footer.md correctly 1`] = ` +
~ ~
+`; -exports[`renders ./components/date-picker/demo/format.md correctly 1`] = `


~
`; +exports[`renders ./components/date-picker/demo/format.md correctly 1`] = ` +


~
+`; -exports[`renders ./components/date-picker/demo/mode.md correctly 1`] = `

~
`; +exports[`renders ./components/date-picker/demo/mode.md correctly 1`] = ` +

~
+`; -exports[`renders ./components/date-picker/demo/presetted-ranges.md correctly 1`] = `
~
~
`; +exports[`renders ./components/date-picker/demo/presetted-ranges.md correctly 1`] = ` +
~
~
+`; exports[`renders ./components/date-picker/demo/size.md correctly 1`] = `
-


-


~
+




~
+
`; -exports[`renders ./components/date-picker/demo/start-end.md correctly 1`] = `
`; +exports[`renders ./components/date-picker/demo/start-end.md correctly 1`] = `
`; -exports[`renders ./components/date-picker/demo/time.md correctly 1`] = `

~
`; +exports[`renders ./components/date-picker/demo/suffix.md correctly 1`] = ` +


~

ab

ab

~ ab
ab
+`; + +exports[`renders ./components/date-picker/demo/time.md correctly 1`] = ` +

~
+`; diff --git a/components/date-picker/createPicker.js b/components/date-picker/createPicker.js index 14ba81bb9..d74f49fd2 100644 --- a/components/date-picker/createPicker.js +++ b/components/date-picker/createPicker.js @@ -7,7 +7,8 @@ import classNames from 'classnames' import Icon from '../icon' import interopDefault from '../_util/interopDefault' import BaseMixin from '../_util/BaseMixin' -import { hasProp, getOptionProps, initDefaultProps, mergeProps } from '../_util/props-util' +import { hasProp, getOptionProps, initDefaultProps, mergeProps, getComponentFromProp, isValidElement } from '../_util/props-util' +import { cloneElement } from '../_util/vnode' // export const PickerProps = { // value?: moment.Moment; @@ -44,14 +45,26 @@ export default function createPicker (TheCalendar, props) { return { sValue: value, showDate: value, + _open: !!this.open, } }, watch: { + open (val) { + const props = getOptionProps(this) + const state = {} + state._open = val + if ('value' in props && !val && props.value !== this.showDate) { + state.showDate = props.value + } + this.setState(state) + }, value (val) { - this.setState({ - sValue: val, - showDate: val, - }) + const state = {} + state.sValue = val + if (val !== this.sValue) { + state.showDate = val + } + this.setState(state) }, }, methods: { @@ -79,12 +92,19 @@ export default function createPicker (TheCalendar, props) { }) } this.$emit('change', value, (value && value.format(this.format)) || '') + this.focus() }, handleCalendarChange (value) { this.setState({ showDate: value }) }, - + handleOpenChange (open) { + const props = getOptionProps(this) + if (!('open' in props)) { + this.setState({ _open: open }) + } + this.$emit('openChange', open) + }, focus () { this.$refs.input.focus() }, @@ -101,7 +121,10 @@ export default function createPicker (TheCalendar, props) { }, render () { - const { sValue: value, showDate, $listeners, $scopedSlots } = this + const { $listeners, $scopedSlots } = this + const { sValue: value, showDate, _open: open } = this.$data + let suffixIcon = getComponentFromProp(this, 'suffixIcon') + suffixIcon = Array.isArray(suffixIcon) ? suffixIcon[0] : suffixIcon const { panelChange = noop, focus = noop, blur = noop, ok = noop } = $listeners const props = getOptionProps(this) const { prefixCls, locale, localeCode } = props @@ -123,9 +146,11 @@ export default function createPicker (TheCalendar, props) { const pickerProps = { props: {}, on: {}} const calendarProps = { props: {}, on: {}} + const pickerStyle = {} if (props.showTime) { // fix https://github.com/ant-design/ant-design/issues/1902 calendarProps.on.select = this.handleChange + pickerStyle.width = '195px' } else { pickerProps.on.change = this.handleChange } @@ -150,7 +175,7 @@ export default function createPicker (TheCalendar, props) { }, on: { ok: ok, - panelChange: panelChange, + panelChange, change: this.handleCalendarChange, }, class: calendarClassName, @@ -164,12 +189,24 @@ export default function createPicker (TheCalendar, props) { const clearIcon = (!props.disabled && props.allowClear && value) ? ( ) : null + const inputIcon = suffixIcon && ( + isValidElement(suffixIcon) + ? cloneElement( + suffixIcon, + { + class: `${prefixCls}-picker-icon`, + }, + ) : {suffixIcon}) || ( + + ) + const input = ({ value: inputValue }) => (
{clearIcon} - + {inputIcon}
) const vcDatePickerProps = { @@ -197,12 +235,15 @@ export default function createPicker (TheCalendar, props) { on: { ...omit($listeners, 'change'), ...pickerProps.on, + open, + onOpenChange: this.handleOpenChange, }, style: props.popupStyle, } return (
+`; + +exports[`Drawer render top drawer 1`] = ` +
+
+
+
+
+
Here is content of Drawer
diff --git a/components/drawer/__tests__/__snapshots__/DrawerEvent.test.js.snap b/components/drawer/__tests__/__snapshots__/DrawerEvent.test.js.snap index d63d6cd14..efd6a7c4e 100644 --- a/components/drawer/__tests__/__snapshots__/DrawerEvent.test.js.snap +++ b/components/drawer/__tests__/__snapshots__/DrawerEvent.test.js.snap @@ -7,7 +7,7 @@ exports[`Drawer render correctly 1`] = `
-
+
Here is content of Drawer
diff --git a/components/drawer/__tests__/__snapshots__/demo.test.js.snap b/components/drawer/__tests__/__snapshots__/demo.test.js.snap index ecadb932f..5723e2095 100644 --- a/components/drawer/__tests__/__snapshots__/demo.test.js.snap +++ b/components/drawer/__tests__/__snapshots__/demo.test.js.snap @@ -1,11 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`renders ./components/drawer/demo/basic-left.md correctly 1`] = ` -
- -
-`; - exports[`renders ./components/drawer/demo/basic-right.md correctly 1`] = `
@@ -18,6 +12,13 @@ exports[`renders ./components/drawer/demo/multi-level-drawer.md correctly 1`] =
`; +exports[`renders ./components/drawer/demo/placement.md correctly 1`] = ` +
+
+ +
+`; + exports[`renders ./components/drawer/demo/user-profile.md correctly 1`] = `
diff --git a/components/drawer/demo/basic-left.md b/components/drawer/demo/basic-left.md deleted file mode 100644 index 57b192f6a..000000000 --- a/components/drawer/demo/basic-left.md +++ /dev/null @@ -1,47 +0,0 @@ - -#### 左侧滑出 -基础抽屉,点击触发按钮抽屉从左滑出,点击遮罩区关闭 - - - -#### Left Slider -Basic drawer. - - -```html - - -``` diff --git a/components/drawer/demo/index.vue b/components/drawer/demo/index.vue index c726fb74a..15c24c5ea 100644 --- a/components/drawer/demo/index.vue +++ b/components/drawer/demo/index.vue @@ -1,6 +1,6 @@ +``` diff --git a/components/drawer/index.en-US.md b/components/drawer/index.en-US.md index 47c1944dd..3cc960dda 100644 --- a/components/drawer/index.en-US.md +++ b/components/drawer/index.en-US.md @@ -12,10 +12,12 @@ | maskStyle | Style for Drawer's mask element. | object | {} | | title | The title for Drawer. | string\|slot | - | | visible | Whether the Drawer dialog is visible or not. | boolean | false | -| width | Width of the Drawer dialog. | string\|number | 256 | | wrapClassName | The class name of the container of the Drawer dialog. | string | - | +| width | Width of the Drawer dialog. | string\|number | 256 | +| height | placement is `top` or `bottom`, height of the Drawer dialog. | string\|number | - | +| className | The class name of the container of the Drawer dialog. | string | - | | zIndex | The `z-index` of the Drawer. | Number | 1000 | -| placement | The placement of the Drawer. | 'left' \| 'right' | 'right' +| placement | The placement of the Drawer. | 'top' \| 'right' \| 'bottom' \| 'left' | 'right' | ## Methods diff --git a/components/drawer/index.jsx b/components/drawer/index.jsx index 5b53c97bf..c422588a7 100644 --- a/components/drawer/index.jsx +++ b/components/drawer/index.jsx @@ -1,6 +1,8 @@ +import classNames from 'classnames' import VcDrawer from '../vc-drawer/src' import PropTypes from '../_util/vue-types' import BaseMixin from '../_util/BaseMixin' +import Icon from '../icon' import { getComponentFromProp, getOptionProps } from '../_util/props-util' const Drawer = { @@ -10,14 +12,15 @@ const Drawer = { destroyOnClose: PropTypes.bool, getContainer: PropTypes.any, maskClosable: PropTypes.bool.def(true), - mask: PropTypes.bool, + mask: PropTypes.bool.def(true), maskStyle: PropTypes.object, title: PropTypes.any, visible: PropTypes.bool, width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).def(256), + height: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).def(256), zIndex: PropTypes.number, prefixCls: PropTypes.string.def('ant-drawer'), - placement: PropTypes.string.def('right'), + placement: PropTypes.oneOf(['top', 'right', 'bottom', 'left']).def('right'), level: PropTypes.any.def(null), wrapClassName: PropTypes.string, }, @@ -88,7 +91,16 @@ const Drawer = { getDestoryOnClose () { return this.destroyOnClose && !this.visible }, - + // get drawar push width or height + getPushTransform (placement) { + if (placement === 'left' || placement === 'right') { + return `translateX(${placement === 'left' ? 180 : -180}px)` + } + if (placement === 'top' || placement === 'bottom') { + return `translateY(${placement === 'top' ? 180 : -180}px)` + } + }, + // render drawer body dom renderBody () { if (this.destoryClose && !this.visible) { return null @@ -110,6 +122,7 @@ const Drawer = { } const { prefixCls, closable } = this.$props const title = getComponentFromProp(this, 'title') + // is have header dom let header if (title) { header = ( @@ -118,6 +131,7 @@ const Drawer = {
) } + // is have closer button let closer if (closable) { closer = ( @@ -127,7 +141,9 @@ const Drawer = { aria-label='Close' class={`${prefixCls}-close`} > - + + + ) } @@ -146,31 +162,49 @@ const Drawer = {
) }, + getRcDrawerStyle () { + const { zIndex, placement, maskStyle } = this.$props + return this.$data._push + ? { + ...maskStyle, + zIndex, + transform: this.getPushTransform(placement), + } + : { + ...maskStyle, + zIndex, + } + }, + }, render () { const props = getOptionProps(this) - const { zIndex, visible, placement, mask, wrapClassName, ...rest } = props - const vcDrawerStyle = this.$data._push - ? { - zIndex, - transform: `translateX(${placement === 'left' ? 180 : -180}px)`, - } - : { zIndex } + const { width, height, visible, placement, wrapClassName, ...rest } = props + const haveMask = rest.mask ? '' : 'no-mask' + const offsetStyle = {} + if (placement === 'left' || placement === 'right') { + offsetStyle.width = typeof width === 'number' ? `${width}px` : width + } else { + offsetStyle.height = typeof height === 'number' ? `${height}px` : height + } const vcDrawerProps = { props: { handler: false, - open: visible, - showMask: mask, - placement, - wrapClassName, ...rest, + ...offsetStyle, + open: visible, + showMask: props.mask, + placement, + wrapClassName: classNames({ + [wrapClassName]: !!wrapClassName, + [haveMask]: !!haveMask, + }), }, on: { maskClick: this.onMaskClick, ...this.$listeners, }, - style: vcDrawerStyle, - + style: this.getRcDrawerStyle(), } return ( * { transition: transform @animation-duration-slow @ease-base-in; @@ -28,6 +29,9 @@ &.@{dawer-prefix-cls}-open { width: 100%; } + &.@{dawer-prefix-cls}-open.no-mask { + width: 0%; + } } &-left { &.@{dawer-prefix-cls}-open { @@ -51,10 +55,17 @@ &-top, &-bottom { - .@{dawer-prefix-cls}-content-wrapper, - .@{dawer-prefix-cls}-content { + width: 100%; + height: 0%; + .@{dawer-prefix-cls}-content-wrapper { width: 100%; } + &.@{dawer-prefix-cls}-open { + height: 100%; + } + &.@{dawer-prefix-cls}-open.no-mask { + height: 0%; + } } &-top { &.@{dawer-prefix-cls}-open { @@ -75,13 +86,12 @@ } } } - &.@{dawer-prefix-cls}-open { .@{dawer-prefix-cls} { &-mask { opacity: 0.3; height: 100%; - animation: antdDrawerFadeIn @animation-duration-slow @ease-base-out; + animation: antdDrawerFadeIn @animation-duration-slow @ease-base-out; transition: none; } } @@ -129,12 +139,6 @@ height: 56px; line-height: 56px; font-size: @font-size-lg; - - &:before { - content: "\e633"; - display: block; - font-family: "anticon" !important; - } } &:focus, @@ -170,9 +174,6 @@ } &-open { transition: transform @animation-duration-slow @ease-base-out; - > * { - transition: transform @animation-duration-slow @ease-base-out; - } &-content { box-shadow: @shadow-2; } diff --git a/components/drawer/style/index.less b/components/drawer/style/index.less index 303b8ee83..40a49a53b 100644 --- a/components/drawer/style/index.less +++ b/components/drawer/style/index.less @@ -1,4 +1,3 @@ @import "../../style/themes/default"; @import "../../style/mixins/index"; @import "./drawer"; - diff --git a/components/dropdown/__tests__/__snapshots__/demo.test.js.snap b/components/dropdown/__tests__/__snapshots__/demo.test.js.snap index ed84b871e..e1a1adcbb 100644 --- a/components/dropdown/__tests__/__snapshots__/demo.test.js.snap +++ b/components/dropdown/__tests__/__snapshots__/demo.test.js.snap @@ -2,32 +2,46 @@ exports[`renders ./components/dropdown/demo/basic.md correctly 1`] = ` - Hover me + Hover me `; exports[`renders ./components/dropdown/demo/context-menu.md correctly 1`] = `Right Click on Me`; exports[`renders ./components/dropdown/demo/dropdown-button.md correctly 1`] = `
-
-
+
+
`; exports[`renders ./components/dropdown/demo/event.md correctly 1`] = ` - Hover me, Click menu item + Hover me, Click menu item `; exports[`renders ./components/dropdown/demo/item.md correctly 1`] = ` - Hover me + Hover me `; exports[`renders ./components/dropdown/demo/overlay-visible.md correctly 1`] = ` - Hover me + Hover me `; exports[`renders ./components/dropdown/demo/placement.md correctly 1`] = ` @@ -42,10 +56,14 @@ exports[`renders ./components/dropdown/demo/placement.md correctly 1`] = ` exports[`renders ./components/dropdown/demo/sub-menu.md correctly 1`] = ` - Cascading menu + Cascading menu `; exports[`renders ./components/dropdown/demo/trigger.md correctly 1`] = ` - Click me + Click me `; diff --git a/components/dropdown/demo/context-menu.md b/components/dropdown/demo/context-menu.md index 68268dc84..f605f852c 100644 --- a/components/dropdown/demo/context-menu.md +++ b/components/dropdown/demo/context-menu.md @@ -1,11 +1,11 @@ #### 右键菜单 -默认是移入触发菜单,可以鼠标右键触发。 +默认是移入触发菜单,可以点击鼠标右键触发。 #### Context Menu -The default trigger mode is `hover`, you can change it to `right click`. +The default trigger mode is `hover`, you can change it to `contextMenu`. ```html diff --git a/components/dropdown/demo/index.vue b/components/dropdown/demo/index.vue index 6b581f941..7a7c62e45 100644 --- a/components/dropdown/demo/index.vue +++ b/components/dropdown/demo/index.vue @@ -27,6 +27,7 @@ export default { category: 'Components', subtitle: '下拉菜单', type: 'Navigation', + zhType: '导航', title: 'Dropdown', render () { return ( diff --git a/components/dropdown/dropdown-button.jsx b/components/dropdown/dropdown-button.jsx index 4a0756293..85aa140d6 100644 --- a/components/dropdown/dropdown-button.jsx +++ b/components/dropdown/dropdown-button.jsx @@ -1,17 +1,19 @@ import Button from '../button' +import buttonTypes from '../button/buttonTypes' import { ButtonGroupProps } from '../button/button-group' -import Icon from '../icon' import Dropdown from './dropdown' import PropTypes from '../_util/vue-types' import { hasProp, getComponentFromProp } from '../_util/props-util' import getDropdownProps from './getDropdownProps' +const ButtonTypesProps = buttonTypes() const DropdownProps = getDropdownProps() const ButtonGroup = Button.Group const DropdownButtonProps = { ...ButtonGroupProps, ...DropdownProps, type: PropTypes.oneOf(['primary', 'ghost', 'dashed', 'danger', 'default']).def('default'), + htmlType: ButtonTypesProps.htmlType, disabled: PropTypes.bool, prefixCls: PropTypes.string.def('ant-dropdown-button'), placement: DropdownProps.placement.def('bottomRight'), @@ -34,7 +36,7 @@ export default { }, render () { const { - type, disabled, + type, disabled, htmlType, prefixCls, trigger, align, visible, placement, getPopupContainer, ...restProps @@ -65,6 +67,7 @@ export default { type={type} disabled={disabled} onClick={this.onClick} + htmlType={htmlType} > {this.$slots.default} diff --git a/components/dropdown/dropdown.jsx b/components/dropdown/dropdown.jsx index ad1dee203..84b012d3b 100644 --- a/components/dropdown/dropdown.jsx +++ b/components/dropdown/dropdown.jsx @@ -1,11 +1,12 @@ -import RcDropdown from './src/index' +import RcDropdown from '../vc-dropdown/src/index' import DropdownButton from './dropdown-button' -// import warning from '../_util/warning' import PropTypes from '../_util/vue-types' import { cloneElement } from '../_util/vnode' import { getOptionProps, getPropsData } from '../_util/props-util' import getDropdownProps from './getDropdownProps' +import Icon from '../icon' + const DropdownProps = getDropdownProps() const Dropdown = { name: 'ADropdown', @@ -44,11 +45,19 @@ const Dropdown = { // menu should be focusable in dropdown defaultly const overlayProps = overlay && getPropsData(overlay) const { selectable = false, focusable = true } = overlayProps || {} + + const expandIcon = ( + + + + ) + const fixedModeOverlay = overlay && overlay.componentOptions ? cloneElement(overlay, { props: { mode: 'vertical', selectable, focusable, + expandIcon, }, }) : overlay const triggerActions = disabled ? [] : trigger diff --git a/components/dropdown/style/index.less b/components/dropdown/style/index.less index f4967b69f..ec0e955b0 100644 --- a/components/dropdown/style/index.less +++ b/components/dropdown/style/index.less @@ -55,6 +55,10 @@ &-submenu-popup { position: absolute; z-index: @zindex-dropdown; + + > .@{dropdown-prefix-cls}-menu { + transform-origin: 0 0; + } } &-item, @@ -117,10 +121,8 @@ .@{dropdown-prefix-cls}-menu-submenu-arrow { position: absolute; right: @padding-xs; - &:after { - font-family: "anticon" !important; + &-icon { font-style: normal; - content: "\e61f"; color: @text-color-secondary; .iconfont-size-under-12px(10px); } @@ -146,7 +148,7 @@ &-submenu&-submenu-disabled .@{dropdown-prefix-cls}-menu-submenu-title { &, - .@{dropdown-prefix-cls}-menu-submenu-arrow:after { + .@{dropdown-prefix-cls}-menu-submenu-arrow-icon { color: @disabled-color; } } @@ -185,12 +187,9 @@ .@{dropdown-prefix-cls}-trigger, .@{dropdown-prefix-cls}-link { - .@{iconfont-css-prefix}-down { + > .@{iconfont-css-prefix}:not(.@{iconfont-css-prefix}-ellipsis) { .iconfont-size-under-12px(10px); } - .@{iconfont-css-prefix}-ellipsis { - text-shadow: 0 0 currentColor; - } } .@{dropdown-prefix-cls}-button { @@ -200,7 +199,7 @@ padding-left: @padding-xs; padding-right: @padding-xs; } - .@{iconfont-css-prefix}-down { + .@{iconfont-css-prefix}:not(.@{iconfont-css-prefix}-ellipsis) { .iconfont-size-under-12px(10px); } } diff --git a/components/form/Form.jsx b/components/form/Form.jsx index bf27ecc96..1406fb800 100755 --- a/components/form/Form.jsx +++ b/components/form/Form.jsx @@ -100,6 +100,8 @@ export const ValidationRule = { // trigger?: string; // /** 可以把 onChange 的参数转化为控件的值,例如 DatePicker 可设为:(date, dateString) => dateString */ // getValueFromEvent?: (...args: any[]) => any; +// /** Get the component props according to field value. */ +// getValueProps?: (value: any) => any; // /** 校验子节点值的时机 */ // validateTrigger?: string | string[]; // /** 校验规则,参见 [async-validator](https://github.com/yiminghe/async-validator) */ diff --git a/components/form/FormItem.jsx b/components/form/FormItem.jsx index dd7e1bca8..fcc13d0d0 100644 --- a/components/form/FormItem.jsx +++ b/components/form/FormItem.jsx @@ -6,7 +6,7 @@ import Row from '../grid/Row' import Col, { ColProps } from '../grid/Col' import warning from '../_util/warning' import { FIELD_META_PROP, FIELD_DATA_PROP } from './constants' -import { initDefaultProps, getComponentFromProp, filterEmpty, getSlotOptions, isValidElement, getSlots, getAllChildren } from '../_util/props-util' +import { initDefaultProps, getComponentFromProp, filterEmpty, getSlotOptions, isValidElement, getAllChildren } from '../_util/props-util' import getTransitionProps from '../_util/getTransitionProps' import BaseMixin from '../_util/BaseMixin' import { cloneElement, cloneVNodes } from '../_util/vnode' diff --git a/components/form/__tests__/__snapshots__/demo.test.js.snap b/components/form/__tests__/__snapshots__/demo.test.js.snap index ffac3693e..dd62b1ad7 100644 --- a/components/form/__tests__/__snapshots__/demo.test.js.snap +++ b/components/form/__tests__/__snapshots__/demo.test.js.snap @@ -4,7 +4,7 @@ exports[`renders ./components/form/demo/advanced-search.vue correctly 1`] = `