feat: update step (#2379)

* feat: update step

* fix: step use v-model can't click

* fix: avoid step auto mount attrs

* chore: use props to transfer event

* docs: add steps breakchange
pull/2386/head^2
zkwolf 4 years ago committed by GitHub
parent 36bec7eb5a
commit 65f49b97d7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -47,3 +47,7 @@ v-model -> v-model:selectedKeys :openKeys.sync -> v-mdoel:openKeys
## dropdown ## dropdown
v-model -> v-model:visible v-model -> v-model:visible
## Steps
v-model -> v-model:current

@ -302,6 +302,10 @@ export function getComponentName(opts) {
return opts && (opts.Ctor.options.name || opts.tag); return opts && (opts.Ctor.options.name || opts.tag);
} }
export function isFragment(c) {
return c.length === 1 && c[0].type === Fragment;
}
export function isEmptyElement(c) { export function isEmptyElement(c) {
return c.type === Comment || (c.type === Text && c.children.trim() === ''); return c.type === Comment || (c.type === Text && c.children.trim() === '');
} }
@ -311,6 +315,9 @@ export function isStringElement(c) {
} }
export function filterEmpty(children = []) { export function filterEmpty(children = []) {
if (isFragment(children)) {
return children[0].children.filter(c => !isEmptyElement(c));
}
return children.filter(c => !isEmptyElement(c)); return children.filter(c => !isEmptyElement(c));
} }
const initDefaultProps = (propTypes, defaultProps) => { const initDefaultProps = (propTypes, defaultProps) => {

@ -1,10 +1,10 @@
import { inject } from 'vue';
import CloseOutlined from '@ant-design/icons-vue/CloseOutlined'; import CloseOutlined from '@ant-design/icons-vue/CloseOutlined';
import CheckOutlined from '@ant-design/icons-vue/CheckOutlined'; import CheckOutlined from '@ant-design/icons-vue/CheckOutlined';
import PropTypes from '../_util/vue-types'; import PropTypes from '../_util/vue-types';
import { initDefaultProps, getOptionProps, getListeners } from '../_util/props-util'; import { initDefaultProps, getOptionProps, getComponent } from '../_util/props-util';
import VcSteps from '../vc-steps'; import VcSteps from '../vc-steps';
import { ConfigConsumerProps } from '../config-provider'; import { ConfigConsumerProps } from '../config-provider';
import Base from '../base';
const getStepsProps = (defaultProps = {}) => { const getStepsProps = (defaultProps = {}) => {
const props = { const props = {
@ -27,12 +27,10 @@ const Steps = {
props: getStepsProps({ props: getStepsProps({
current: 0, current: 0,
}), }),
inject: { setup() {
configProvider: { default: () => ConfigConsumerProps }, return {
}, configProvider: inject('configProvider', ConfigConsumerProps),
model: { };
prop: 'current',
event: 'change',
}, },
Step: { ...VcSteps.Step, name: 'AStep' }, Step: { ...VcSteps.Step, name: 'AStep' },
render() { render() {
@ -41,30 +39,27 @@ const Steps = {
const getPrefixCls = this.configProvider.getPrefixCls; const getPrefixCls = this.configProvider.getPrefixCls;
const prefixCls = getPrefixCls('steps', customizePrefixCls); const prefixCls = getPrefixCls('steps', customizePrefixCls);
const iconPrefix = getPrefixCls('', customizeIconPrefixCls); const iconPrefix = getPrefixCls('', customizeIconPrefixCls);
const progressDot = getComponent(this, 'progressDot', this, false);
const icons = { const icons = {
finish: <CheckOutlined class={`${prefixCls}-finish-icon`} />, finish: <CheckOutlined class={`${prefixCls}-finish-icon`} />,
error: <CloseOutlined class={`${prefixCls}-error-icon`} />, error: <CloseOutlined class={`${prefixCls}-error-icon`} />,
}; };
const stepsProps = { const stepsProps = {
props: {
icons, icons,
iconPrefix, iconPrefix,
prefixCls, prefixCls,
progressDot,
...props, ...props,
},
on: getListeners(this),
scopedSlots: this.$scopedSlots,
}; };
return <VcSteps {...stepsProps}>{this.$slots.default}</VcSteps>; return <VcSteps {...stepsProps}>{this.$slots.default && this.$slots.default()}</VcSteps>;
}, },
}; };
/* istanbul ignore next */ /* istanbul ignore next */
Steps.install = function(Vue) { Steps.install = function(app) {
Vue.use(Base); app.component(Steps.name, Steps);
Vue.component(Steps.name, Steps); app.component(Steps.Step.name, Steps.Step);
Vue.component(Steps.Step.name, Steps.Step);
}; };
export default Steps; export default Steps;

@ -1,5 +1,5 @@
import PropTypes from '../_util/vue-types'; import PropTypes from '../_util/vue-types';
import { getOptionProps, getComponentFromProp, getListeners } from '../_util/props-util'; import { getOptionProps, getComponent } from '../_util/props-util';
function isString(str) { function isString(str) {
return typeof str === 'string'; return typeof str === 'string';
@ -28,21 +28,26 @@ export default {
finish: PropTypes.any, finish: PropTypes.any,
error: PropTypes.any, error: PropTypes.any,
}).loose, }).loose,
onClick: PropTypes.func,
onStepClick: PropTypes.func,
}, },
methods: { methods: {
onClick(...args) { onItemClick(...args) {
const { onClick } = this.$props;
if (onClick) {
this.$emit('click', ...args); this.$emit('click', ...args);
}
this.$emit('stepClick', this.stepIndex); this.$emit('stepClick', this.stepIndex);
}, },
renderIconNode() { renderIconNode() {
const { prefixCls, stepNumber, status, iconPrefix, icons } = getOptionProps(this); const { prefixCls, stepNumber, status, iconPrefix, icons, progressDot } = getOptionProps(
let progressDot = this.progressDot; this,
if (progressDot === undefined) { );
progressDot = this.$scopedSlots.progressDot; const icon = getComponent(this, 'icon');
} const title = getComponent(this, 'title');
const icon = getComponentFromProp(this, 'icon'); const description = getComponent(this, 'description');
const title = getComponentFromProp(this, 'title');
const description = getComponentFromProp(this, 'description');
let iconNode; let iconNode;
const iconClassName = { const iconClassName = {
[`${prefixCls}-icon`]: true, [`${prefixCls}-icon`]: true,
@ -86,22 +91,23 @@ export default {
tailContent, tailContent,
adjustMarginRight, adjustMarginRight,
disabled, disabled,
onClick,
onStepClick,
} = getOptionProps(this); } = getOptionProps(this);
const title = getComponentFromProp(this, 'title'); const title = getComponent(this, 'title');
const subTitle = getComponentFromProp(this, 'subTitle'); const subTitle = getComponent(this, 'subTitle');
const description = getComponentFromProp(this, 'description'); const description = getComponent(this, 'description');
const classString = { const classString = {
[`${prefixCls}-item`]: true, [`${prefixCls}-item`]: true,
[`${prefixCls}-item-${status}`]: true, [`${prefixCls}-item-${status}`]: true,
[`${prefixCls}-item-custom`]: getComponentFromProp(this, 'icon'), [`${prefixCls}-item-custom`]: getComponent(this, 'icon'),
[`${prefixCls}-item-active`]: active, [`${prefixCls}-item-active`]: active,
[`${prefixCls}-item-disabled`]: disabled === true, [`${prefixCls}-item-disabled`]: disabled === true,
}; };
const stepProps = { const stepProps = {
class: classString, class: classString,
on: getListeners(this),
}; };
const stepItemStyle = {}; const stepItemStyle = {};
if (itemWidth) { if (itemWidth) {
@ -110,17 +116,15 @@ export default {
if (adjustMarginRight) { if (adjustMarginRight) {
stepItemStyle.marginRight = adjustMarginRight; stepItemStyle.marginRight = adjustMarginRight;
} }
const listeners = getListeners(this);
const accessibilityProps = { const accessibilityProps = {
attrs: {}, onClick: onClick || noop,
on: {
click: listeners.click || noop,
},
}; };
if (listeners.stepClick && !disabled) {
accessibilityProps.attrs.role = 'button'; if (onStepClick && !disabled) {
accessibilityProps.attrs.tabIndex = 0; accessibilityProps.role = 'button';
accessibilityProps.on.click = this.onClick; accessibilityProps.tabIndex = 0;
accessibilityProps.onClick = this.onItemClick;
} }
return ( return (
<div {...stepProps} style={stepItemStyle}> <div {...stepProps} style={stepItemStyle}>

@ -2,7 +2,7 @@ import PropTypes from '../_util/vue-types';
import BaseMixin from '../_util/BaseMixin'; import BaseMixin from '../_util/BaseMixin';
import debounce from 'lodash/debounce'; import debounce from 'lodash/debounce';
import isFlexSupported from '../_util/isFlexSupported'; import isFlexSupported from '../_util/isFlexSupported';
import { filterEmpty, getEvents, getPropsData, getListeners } from '../_util/props-util'; import { filterEmpty } from '../_util/props-util';
import { cloneElement } from '../_util/vnode'; import { cloneElement } from '../_util/vnode';
export default { export default {
@ -59,6 +59,7 @@ export default {
const { current } = this.$props; const { current } = this.$props;
if (current !== next) { if (current !== next) {
this.$emit('change', next); this.$emit('change', next);
this.$emit('update:current', next);
} }
}, },
calcStepOffsetWidth() { calcStepOffsetWidth() {
@ -97,17 +98,14 @@ export default {
status, status,
size, size,
current, current,
$scopedSlots, $slots,
progressDot,
initial, initial,
icons, icons,
} = this; } = this;
const isNav = type === 'navigation'; const isNav = type === 'navigation';
let progressDot = this.progressDot;
if (progressDot === undefined) {
progressDot = $scopedSlots.progressDot;
}
const { lastStepOffsetWidth, flexSupported } = this; const { lastStepOffsetWidth, flexSupported } = this;
const filteredChildren = filterEmpty(this.$slots.default); const filteredChildren = filterEmpty($slots.default && $slots.default());
const lastIndex = filteredChildren.length - 1; const lastIndex = filteredChildren.length - 1;
const adjustedlabelPlacement = progressDot ? 'vertical' : labelPlacement; const adjustedlabelPlacement = progressDot ? 'vertical' : labelPlacement;
const classString = { const classString = {
@ -119,42 +117,36 @@ export default {
[`${prefixCls}-navigation`]: isNav, [`${prefixCls}-navigation`]: isNav,
[`${prefixCls}-flex-not-supported`]: !flexSupported, [`${prefixCls}-flex-not-supported`]: !flexSupported,
}; };
const listeners = getListeners(this);
const stepsProps = { const stepsProps = {
class: classString, class: classString,
ref: 'vcStepsRef', ref: 'vcStepsRef',
on: listeners,
}; };
return ( return (
<div {...stepsProps}> <div {...stepsProps}>
{filteredChildren.map((child, index) => { {filteredChildren.map((child, index) => {
const childProps = getPropsData(child); const childProps = child.props || {};
const stepNumber = initial + index; const stepNumber = initial + index;
const stepProps = { const stepProps = {
props: {
stepNumber: `${stepNumber + 1}`, stepNumber: `${stepNumber + 1}`,
stepIndex: stepNumber, stepIndex: stepNumber,
prefixCls, prefixCls,
iconPrefix, iconPrefix,
progressDot: this.progressDot, progressDot,
icons, icons,
...childProps, ...childProps,
},
on: getEvents(child),
scopedSlots: $scopedSlots,
}; };
if (listeners.change) {
stepProps.on.stepClick = this.onStepClick; const { onChange } = this.$attrs;
if (onChange || this.$attrs['onUpdate:current']) {
stepProps.onStepClick = this.onStepClick;
} }
if (!flexSupported && direction !== 'vertical') { if (!flexSupported && direction !== 'vertical') {
if (isNav) { if (isNav) {
stepProps.props.itemWidth = `${100 / (lastIndex + 1)}%`; stepProps.itemWidth = `${100 / (lastIndex + 1)}%`;
stepProps.props.adjustMarginRight = 0; stepProps.adjustMarginRight = 0;
} else if (index !== lastIndex) { } else if (index !== lastIndex) {
stepProps.props.itemWidth = `${100 / lastIndex}%`; stepProps.itemWidth = `${100 / lastIndex}%`;
stepProps.props.adjustMarginRight = `${-Math.round( stepProps.adjustMarginRight = `${-Math.round(lastStepOffsetWidth / lastIndex + 1)}px`;
lastStepOffsetWidth / lastIndex + 1,
)}px`;
} }
} }
// fix tail color // fix tail color
@ -163,14 +155,14 @@ export default {
} }
if (!childProps.status) { if (!childProps.status) {
if (stepNumber === current) { if (stepNumber === current) {
stepProps.props.status = status; stepProps.status = status;
} else if (stepNumber < current) { } else if (stepNumber < current) {
stepProps.props.status = 'finish'; stepProps.status = 'finish';
} else { } else {
stepProps.props.status = 'wait'; stepProps.status = 'wait';
} }
} }
stepProps.props.active = stepNumber === current; stepProps.active = stepNumber === current;
return cloneElement(child, stepProps); return cloneElement(child, stepProps);
})} })}
</div> </div>

@ -33,6 +33,7 @@ import Modal from 'ant-design-vue/modal';
import Menu from 'ant-design-vue/menu'; import Menu from 'ant-design-vue/menu';
import Mentions from 'ant-design-vue/mentions'; import Mentions from 'ant-design-vue/mentions';
import Dropdown from 'ant-design-vue/dropdown'; import Dropdown from 'ant-design-vue/dropdown';
import Steps from 'ant-design-vue/steps';
import 'ant-design-vue/style.js'; import 'ant-design-vue/style.js';
const basic = { const basic = {
@ -79,4 +80,5 @@ app
.use(Menu) .use(Menu)
.use(Mentions) .use(Mentions)
.use(Dropdown) .use(Dropdown)
.use(Steps)
.mount('#app'); .mount('#app');

Loading…
Cancel
Save