ant-design-vue/components/vc-menu/SubPopupMenu.jsx
tangjinzhou 73bef787cd
Feat 1.5.0 (#1853)
* feat: add Result component

* fix: update md template tag html>tpl
- fix `result` typo
- update jest `result` snapshots

* refactor: svg file to functional component icon
- update jest snapshot

* feat: add result

* Feat descriptions (#1251)

* feat: add descriptions

* fix: add descriptions types and fix docs

* fix: lint change code

* fix: demo warning

* fix: update demo, snapshot and remove classnames

* test: add descriptions test

* fix: descriptions demo (#1498)

* feat: add page header (#1250)

* feat: add page-header component

* update site: page-header

* ts definition update: page-header

* get page-header props with getComponentFromProp func

* optimize page-header

* doc: add page-header actions.md responsive.md

* breadcrumb itemRender add pure function support

* style: format code

* feat: update style to 3.23.6 from 2.13.6

* feat: update style to 3.26.8 from 3.23.6

* chore: update util

* chore: update util

* feat: update affix

* feat: update alert

* feat: update anchor

* feat: update auto-complete

* feat: update avatar

* feat: update back-top

* feat: update badge

* feat: update button

* feat: update breadcrumb

* feat: update ts

* docs: update doc

* feat: update calendat

* feat: update card

* feat: update carousel

* feat: update carousel

* feat: update checkbox

* feat: update comment

* feat: update config-provider

* docs: update doc

* feat: update collapse

* feat: update locale

* feat: update date-picker

* feat: update divider

* feat: update drawer

* feat: update dropdown

* feat: update rc-trigger

* feat: update dropdown

* feat: update empty

* test: add empty test

* feat: update form

* feat: update form

* feat: update spin

* feat: update grid

* docs: update grid doc

* feat: update icon

* feat: update slider

* feat: update textarea

* feat: update input-number

* feat: update layout

* feat: update list

* feat: update menu

* feat: meaage add key for update content

* feat: modal add closeIcon support

* feat: update notification

* feat: add pagination disabled support

* feat: popconfirm add disabled support

* test: update popover

* feat: progress support custom line-gradiend

* feat: update radio

* test: update radio test

* docs: update rate demo

* feat: skeleton add avatar support number type

* test: add switch test

* test: update statistic test

* fix: input clear icon event

* feat: steps add type、 v-model、subTitle

* feat: delete typography component

* feat: delete Typography style

* perf: update select

* feat: add download transformFile previewFile actio

* docs: update upload

* feat: update  tree-select

* docs: update tree-select

* feat: tree add blockNode selectable

* docs: add tree demo

* test: update snap

* docs: updatedoc

* feat: update tag

* docs: update ad doc

* feat: update tooltip

* feat: update timeline

* feat: time-picker add clearIcon

* docs: update tabs

* feat: transfer support custom children

* test: update transfer test

* feat: update table

* test: update table test

* test: update test

* feat: calendar update locale

* test: update test snap

* feat: add mentions (#1790)

* feat: mentions style

* feat: theme default

* feat: add mentions component

* feat: mentions API

* feat: add unit test for mentions

* feat: update mentions demo

* perf: model and inheritAttrs for mentions

* perf: use getComponentFromProp instead of this.$props

* perf: mentions rm defaultProps

* feat: rm rows in mentionsProps

* fix: mentions keyDown didn't work

* docs: update mentions api

* perf: mentions code

* feat: update mentions

* bump 1.5.0-alpha.1

* feat: pageheader add ghost prop

* docs: update descriptions demo

* chore: page-header add ghost type

* fix: color error

* feat: update to 3.26.12

* fix: some prop default value

* fix(typo): form, carousel, upload. duplicate identifier (#1848)

* Add Mentions Type (#1845)

* feat: add mentions type

* feat: add mentions in ant-design-vue.d.ts

* docs: update doc

* docs: add changelog

* fix: mentions getPopupCotainer value (#1850)

* docs: update doc

* docs: uptate demo

* docs: update demo

* docs: delete demo

* docs: delete doc

* test: update snapshots

* style: format code

* chore: update travis

* docs: update demo

Co-authored-by: Sendya <18x@loacg.com>
Co-authored-by: zkwolf <chenhao5866@gmail.com>
Co-authored-by: drafish <xwlyy1991@163.com>
Co-authored-by: Amour1688 <31695475+Amour1688@users.noreply.github.com>
2020-03-07 19:45:13 +08:00

416 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import omit from 'omit.js';
import PropTypes from '../_util/vue-types';
import { connect } from '../_util/store';
import BaseMixin from '../_util/BaseMixin';
import KeyCode from '../_util/KeyCode';
import classNames from 'classnames';
import { getKeyFromChildrenIndex, loopMenuItem, noop, isMobileDevice } from './util';
import DOMWrap from './DOMWrap';
import { cloneElement } from '../_util/vnode';
import {
initDefaultProps,
getOptionProps,
getPropsData,
getEvents,
getComponentFromProp,
getListeners,
} from '../_util/props-util';
function allDisabled(arr) {
if (!arr.length) {
return true;
}
return arr.every(c => {
return !!c.disabled;
});
}
function updateActiveKey(store, menuId, activeKey) {
const state = store.getState();
store.setState({
activeKey: {
...state.activeKey,
[menuId]: activeKey,
},
});
}
function getEventKey(props) {
// when eventKey not available ,it's menu and return menu id '0-menu-'
return props.eventKey || '0-menu-';
}
export function saveRef(key, c) {
if (c) {
const index = this.instanceArrayKeyIndexMap[key];
this.instanceArray[index] = c;
}
}
export function getActiveKey(props, originalActiveKey) {
let activeKey = originalActiveKey;
const { eventKey, defaultActiveFirst, children } = props;
if (activeKey !== undefined && activeKey !== null) {
let found;
loopMenuItem(children, (c, i) => {
const propsData = c.componentOptions.propsData || {};
if (c && !propsData.disabled && activeKey === getKeyFromChildrenIndex(c, eventKey, i)) {
found = true;
}
});
if (found) {
return activeKey;
}
}
activeKey = null;
if (defaultActiveFirst) {
loopMenuItem(children, (c, i) => {
const propsData = c.componentOptions.propsData || {};
if (!activeKey && c && !propsData.disabled) {
activeKey = getKeyFromChildrenIndex(c, eventKey, i);
}
});
return activeKey;
}
return activeKey;
}
const SubPopupMenu = {
name: 'SubPopupMenu',
props: initDefaultProps(
{
// onSelect: PropTypes.func,
// onClick: PropTypes.func,
// onDeselect: PropTypes.func,
// onOpenChange: PropTypes.func,
// onDestroy: PropTypes.func,
prefixCls: PropTypes.string,
openTransitionName: PropTypes.string,
openAnimation: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
openKeys: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])),
visible: PropTypes.bool,
parentMenu: PropTypes.object,
eventKey: PropTypes.string,
store: PropTypes.object,
// adding in refactor
focusable: PropTypes.bool,
multiple: PropTypes.bool,
defaultActiveFirst: PropTypes.bool,
activeKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
selectedKeys: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])),
defaultSelectedKeys: PropTypes.arrayOf(
PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
),
defaultOpenKeys: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])),
level: PropTypes.number,
mode: PropTypes.oneOf([
'horizontal',
'vertical',
'vertical-left',
'vertical-right',
'inline',
]),
triggerSubMenuAction: PropTypes.oneOf(['click', 'hover']),
inlineIndent: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
manualRef: PropTypes.func,
itemIcon: PropTypes.any,
expandIcon: PropTypes.any,
overflowedIndicator: PropTypes.any,
children: PropTypes.any.def([]),
__propsSymbol__: PropTypes.any, // mock componentWillReceiveProps
},
{
prefixCls: 'rc-menu',
mode: 'vertical',
level: 1,
inlineIndent: 24,
visible: true,
focusable: true,
manualRef: noop,
},
),
mixins: [BaseMixin],
created() {
const props = getOptionProps(this);
this.prevProps = { ...props };
props.store.setState({
activeKey: {
...props.store.getState().activeKey,
[props.eventKey]: getActiveKey(props, props.activeKey),
},
});
this.instanceArray = [];
},
mounted() {
// invoke customized ref to expose component to mixin
if (this.manualRef) {
this.manualRef(this);
}
},
updated() {
const props = getOptionProps(this);
const prevProps = this.prevProps;
const originalActiveKey =
'activeKey' in props ? props.activeKey : props.store.getState().activeKey[getEventKey(props)];
const activeKey = getActiveKey(props, originalActiveKey);
if (activeKey !== originalActiveKey) {
updateActiveKey(props.store, getEventKey(props), activeKey);
} else if ('activeKey' in prevProps) {
// If prev activeKey is not same as current activeKey,
// we should set it.
const prevActiveKey = getActiveKey(prevProps, prevProps.activeKey);
if (activeKey !== prevActiveKey) {
updateActiveKey(props.store, getEventKey(props), activeKey);
}
}
this.prevProps = { ...props };
},
methods: {
// all keyboard events callbacks run from here at first
onKeyDown(e, callback) {
const keyCode = e.keyCode;
let handled;
this.getFlatInstanceArray().forEach(obj => {
if (obj && obj.active && obj.onKeyDown) {
handled = obj.onKeyDown(e);
}
});
if (handled) {
return 1;
}
let activeItem = null;
if (keyCode === KeyCode.UP || keyCode === KeyCode.DOWN) {
activeItem = this.step(keyCode === KeyCode.UP ? -1 : 1);
}
if (activeItem) {
e.preventDefault();
updateActiveKey(this.$props.store, getEventKey(this.$props), activeItem.eventKey);
if (typeof callback === 'function') {
callback(activeItem);
}
return 1;
}
return undefined;
},
onItemHover(e) {
const { key, hover } = e;
updateActiveKey(this.$props.store, getEventKey(this.$props), hover ? key : null);
},
onDeselect(selectInfo) {
this.__emit('deselect', selectInfo);
},
onSelect(selectInfo) {
this.__emit('select', selectInfo);
},
onClick(e) {
this.__emit('click', e);
},
onOpenChange(e) {
this.__emit('openChange', e);
},
onDestroy(key) {
this.__emit('destroy', key);
},
getFlatInstanceArray() {
return this.instanceArray;
},
getOpenTransitionName() {
return this.$props.openTransitionName;
},
step(direction) {
let children = this.getFlatInstanceArray();
const activeKey = this.$props.store.getState().activeKey[getEventKey(this.$props)];
const len = children.length;
if (!len) {
return null;
}
if (direction < 0) {
children = children.concat().reverse();
}
// find current activeIndex
let activeIndex = -1;
children.every((c, ci) => {
if (c && c.eventKey === activeKey) {
activeIndex = ci;
return false;
}
return true;
});
if (
!this.defaultActiveFirst &&
activeIndex !== -1 &&
allDisabled(children.slice(activeIndex, len - 1))
) {
return undefined;
}
const start = (activeIndex + 1) % len;
let i = start;
do {
const child = children[i];
if (!child || child.disabled) {
i = (i + 1) % len;
} else {
return child;
}
} while (i !== start);
return null;
},
getIcon(instance, name) {
if (instance.$createElement) {
const temp = instance[name];
if (temp !== undefined) {
return temp;
}
return instance.$slots[name] || instance.$scopedSlots[name];
} else {
const temp = getPropsData(instance)[name];
if (temp !== undefined) {
return temp;
}
const slotsProp = [];
const componentOptions = instance.componentOptions || {};
(componentOptions.children || []).forEach(child => {
if (child.data && child.data.slot === name) {
if (child.tag === 'template') {
slotsProp.push(child.children);
} else {
slotsProp.push(child);
}
}
});
return slotsProp.length ? slotsProp : undefined;
}
},
renderCommonMenuItem(child, i, extraProps) {
if (child.tag === undefined) {
return child;
}
const state = this.$props.store.getState();
const props = this.$props;
const key = getKeyFromChildrenIndex(child, props.eventKey, i);
const childProps = child.componentOptions.propsData || {};
const isActive = key === state.activeKey[getEventKey(this.$props)];
if (!childProps.disabled) {
// manualRef的执行顺序不能保证使用key映射ref在this.instanceArray中的位置
this.instanceArrayKeyIndexMap[key] = Object.keys(this.instanceArrayKeyIndexMap).length;
}
const childListeners = getEvents(child);
const newChildProps = {
props: {
mode: childProps.mode || props.mode,
level: props.level,
inlineIndent: props.inlineIndent,
renderMenuItem: this.renderMenuItem,
rootPrefixCls: props.prefixCls,
index: i,
parentMenu: props.parentMenu,
// customized ref function, need to be invoked manually in child's componentDidMount
manualRef: childProps.disabled ? noop : saveRef.bind(this, key),
eventKey: key,
active: !childProps.disabled && isActive,
multiple: props.multiple,
openTransitionName: this.getOpenTransitionName(),
openAnimation: props.openAnimation,
subMenuOpenDelay: props.subMenuOpenDelay,
subMenuCloseDelay: props.subMenuCloseDelay,
forceSubMenuRender: props.forceSubMenuRender,
builtinPlacements: props.builtinPlacements,
itemIcon: this.getIcon(child, 'itemIcon') || this.getIcon(this, 'itemIcon'),
expandIcon: this.getIcon(child, 'expandIcon') || this.getIcon(this, 'expandIcon'),
...extraProps,
},
on: {
click: e => {
if ('keyPath' in e) {
(childListeners.click || noop)(e);
this.onClick(e);
}
},
itemHover: this.onItemHover,
openChange: this.onOpenChange,
deselect: this.onDeselect,
// destroy: this.onDestroy,
select: this.onSelect,
},
};
// ref: https://github.com/ant-design/ant-design/issues/13943
if (props.mode === 'inline' || isMobileDevice()) {
newChildProps.props.triggerSubMenuAction = 'click';
}
return cloneElement(child, newChildProps);
},
renderMenuItem(c, i, subMenuKey) {
if (!c) {
return null;
}
const state = this.$props.store.getState();
const extraProps = {
openKeys: state.openKeys,
selectedKeys: state.selectedKeys,
triggerSubMenuAction: this.triggerSubMenuAction,
isRootMenu: false,
subMenuKey,
};
return this.renderCommonMenuItem(c, i, extraProps);
},
},
render() {
const { ...props } = this.$props;
const { eventKey, prefixCls, visible, level, mode, theme } = props;
this.instanceArray = [];
this.instanceArrayKeyIndexMap = {};
const className = classNames(props.prefixCls, `${props.prefixCls}-${props.mode}`);
const domWrapProps = {
props: {
tag: 'ul',
// hiddenClassName: `${prefixCls}-hidden`,
visible,
prefixCls,
level,
mode,
theme,
overflowedIndicator: getComponentFromProp(this, 'overflowedIndicator'),
},
attrs: {
role: props.role || 'menu',
},
class: className,
// Otherwise, the propagated click event will trigger another onClick
on: omit(getListeners(this), ['click']),
};
// if (props.id) {
// domProps.id = props.id
// }
if (props.focusable) {
domWrapProps.attrs.tabIndex = '0';
domWrapProps.on.keydown = this.onKeyDown;
}
return (
// ESLint is not smart enough to know that the type of `children` was checked.
/* eslint-disable */
<DOMWrap {...domWrapProps}>
{props.children.map((c, i) => this.renderMenuItem(c, i, eventKey || '0-menu-'))}
</DOMWrap>
/*eslint -enable */
);
},
};
export default connect()(SubPopupMenu);