refactor: remove rc-menu

This commit is contained in:
tanjinzhou 2021-05-24 14:38:43 +08:00
parent 8179361803
commit 3d0edbc292
18 changed files with 4 additions and 2500 deletions

View File

@ -134,8 +134,8 @@ export default defineComponent({
watch(
() => props.selectedKeys,
(selectedKeys = mergedSelectedKeys.value) => {
mergedSelectedKeys.value = selectedKeys;
selectedKeys => {
mergedSelectedKeys.value = selectedKeys || mergedSelectedKeys.value;
},
{ immediate: true },
);

View File

@ -1,5 +1,5 @@
import classNames from '../../_util/classNames';
import { FunctionalComponent, provide } from 'vue';
import { FunctionalComponent } from 'vue';
import { useInjectMenu } from './hooks/useMenuContext';
const InternalSubMenuList: FunctionalComponent<any> = (_props, { slots, attrs }) => {
const { prefixCls, mode } = useInjectMenu();

View File

@ -1,6 +1,6 @@
import { reactive, defineComponent, nextTick, computed, watch } from 'vue';
import FilterFilled from '@ant-design/icons-vue/FilterFilled';
import Menu, { SubMenu, Item as MenuItem } from '../vc-menu';
import Menu, { SubMenu, MenuItem } from '../menu';
import closest from '../_util/dom-closest';
import classNames from '../_util/classNames';
import shallowequal from '../_util/shallowequal';

View File

@ -1,310 +0,0 @@
import PropTypes from '../_util/vue-types';
import SubMenu from './SubMenu';
import BaseMixin from '../_util/BaseMixin';
import { getWidth, setStyle, menuAllProps } from './util';
import { cloneElement } from '../_util/vnode';
import { getAllProps, getSlot, findDOMNode } from '../_util/props-util';
const MENUITEM_OVERFLOWED_CLASSNAME = 'menuitem-overflowed';
const FLOAT_PRECISION_ADJUST = 0.5;
const MENUITEM_OVERFLOWED_UNI_KEY = 'MENUITEM_OVERFLOWED_UNI_KEY';
const MENUITEM_OVERFLOWED_UNI_KEYS = [MENUITEM_OVERFLOWED_UNI_KEY];
const DOMWrap = {
name: 'DOMWrap',
mixins: [BaseMixin],
data() {
this.resizeObserver = null;
this.mutationObserver = null;
// original scroll size of the list
this.originalTotalWidth = 0;
// copy of overflowed items
this.overflowedItems = [];
// cache item of the original items (so we can track the size and order)
this.menuItemSizes = [];
return {
lastVisibleIndex: undefined,
};
},
mounted() {
this.$nextTick(() => {
this.setChildrenWidthAndResize();
if (this.level === 1 && this.mode === 'horizontal') {
const menuUl = findDOMNode(this);
if (!menuUl) {
return;
}
this.resizeObserver = new ResizeObserver(entries => {
entries.forEach(this.setChildrenWidthAndResize);
});
[].slice
.call(menuUl.children)
.concat(menuUl)
.forEach(el => {
this.resizeObserver.observe(el);
});
if (typeof MutationObserver !== 'undefined') {
this.mutationObserver = new MutationObserver(() => {
this.resizeObserver.disconnect();
[].slice
.call(menuUl.children)
.concat(menuUl)
.forEach(el => {
this.resizeObserver.observe(el);
});
this.setChildrenWidthAndResize();
});
this.mutationObserver.observe(menuUl, {
attributes: false,
childList: true,
subTree: false,
});
}
}
});
},
beforeUnmount() {
if (this.resizeObserver) {
this.resizeObserver.disconnect();
}
if (this.mutationObserver) {
this.mutationObserver.disconnect();
}
},
methods: {
// get all valid menuItem nodes
getMenuItemNodes() {
const { prefixCls } = this.$props;
const ul = findDOMNode(this);
if (!ul) {
return [];
}
// filter out all overflowed indicator placeholder
return [].slice
.call(ul.children)
.filter(node => node.className.split(' ').indexOf(`${prefixCls}-overflowed-submenu`) < 0);
},
getOverflowedSubMenuItem(keyPrefix, overflowedItems, renderPlaceholder) {
const { overflowedIndicator, level, mode, prefixCls, theme } = this.$props;
if (level !== 1 || mode !== 'horizontal') {
return null;
}
// put all the overflowed item inside a submenu
// with a title of overflow indicator ('...')
const copy = getSlot(this)[0];
const allProps = getAllProps(copy) || {};
const { title, extraProps, ...rest } = { ...allProps, ...allProps.extraProps }; // eslint-disable-line no-unused-vars
let style = {};
let key = `${keyPrefix}-overflowed-indicator`;
let eventKey = `${keyPrefix}-overflowed-indicator`;
if (overflowedItems.length === 0 && renderPlaceholder !== true) {
style = {
display: 'none',
};
} else if (renderPlaceholder) {
style = {
visibility: 'hidden',
// prevent from taking normal dom space
position: 'absolute',
};
key = `${key}-placeholder`;
eventKey = `${eventKey}-placeholder`;
}
const popupClassName = theme ? `${prefixCls}-${theme}` : '';
const props = {};
menuAllProps.forEach(k => {
if (rest[k] !== undefined) {
props[k] = rest[k];
}
});
const subMenuProps = {
title: overflowedIndicator,
popupClassName,
...props,
eventKey,
disabled: false,
class: `${prefixCls}-overflowed-submenu`,
key,
style,
isOverflowedSubMenu: true,
};
return <SubMenu {...subMenuProps}>{overflowedItems}</SubMenu>;
},
// memorize rendered menuSize
setChildrenWidthAndResize() {
if (this.mode !== 'horizontal') {
return;
}
const ul = findDOMNode(this);
if (!ul) {
return;
}
const ulChildrenNodes = ul.children;
if (!ulChildrenNodes || ulChildrenNodes.length === 0) {
return;
}
const lastOverflowedIndicatorPlaceholder = ul.children[ulChildrenNodes.length - 1];
// need last overflowed indicator for calculating length;
setStyle(lastOverflowedIndicatorPlaceholder, 'display', 'inline-block');
const menuItemNodes = this.getMenuItemNodes();
// reset display attribute for all hidden elements caused by overflow to calculate updated width
// and then reset to original state after width calculation
const overflowedItems = menuItemNodes.filter(
c => c.className.split(' ').indexOf(MENUITEM_OVERFLOWED_CLASSNAME) >= 0,
);
overflowedItems.forEach(c => {
setStyle(c, 'display', 'inline-block');
});
this.menuItemSizes = menuItemNodes.map(c => getWidth(c));
overflowedItems.forEach(c => {
setStyle(c, 'display', 'none');
});
this.overflowedIndicatorWidth = getWidth(ul.children[ul.children.length - 1]);
this.originalTotalWidth = this.menuItemSizes.reduce((acc, cur) => acc + cur, 0);
this.handleResize();
// prevent the overflowed indicator from taking space;
setStyle(lastOverflowedIndicatorPlaceholder, 'display', 'none');
},
handleResize() {
if (this.mode !== 'horizontal') {
return;
}
const ul = findDOMNode(this);
if (!ul) {
return;
}
const width = getWidth(ul);
this.overflowedItems = [];
let currentSumWidth = 0;
// index for last visible child in horizontal mode
let lastVisibleIndex;
// float number comparison could be problematic
// e.g. 0.1 + 0.2 > 0.3 =====> true
// thus using FLOAT_PRECISION_ADJUST as buffer to help the situation
if (this.originalTotalWidth > width + FLOAT_PRECISION_ADJUST) {
lastVisibleIndex = -1;
this.menuItemSizes.forEach(liWidth => {
currentSumWidth += liWidth;
if (currentSumWidth + this.overflowedIndicatorWidth <= width) {
lastVisibleIndex += 1;
}
});
}
this.setState({ lastVisibleIndex });
},
renderChildren(children) {
// need to take care of overflowed items in horizontal mode
const { lastVisibleIndex } = this.$data;
const className = this.$attrs.class || '';
return (children || []).reduce((acc, childNode, index) => {
let item = childNode;
const { extraProps = {} } = item.props || {};
const { eventKey } = extraProps;
if (this.mode === 'horizontal') {
let overflowed = this.getOverflowedSubMenuItem(eventKey, []);
if (
lastVisibleIndex !== undefined &&
className.indexOf(`${this.prefixCls}-root`) !== -1
) {
if (index > lastVisibleIndex) {
item = cloneElement(
childNode,
// eventKey openkeys
{
extraProps: {
...extraProps,
style: { display: 'none' },
eventKey: `${eventKey}-hidden`,
class: MENUITEM_OVERFLOWED_CLASSNAME,
parentUniKey: MENUITEM_OVERFLOWED_UNI_KEY,
parentUniKeys: MENUITEM_OVERFLOWED_UNI_KEYS,
},
},
);
}
if (index === lastVisibleIndex + 1) {
this.overflowedItems = children.slice(lastVisibleIndex + 1).map(c => {
const { extraProps = {} } = c.props || {};
const { eventKey } = extraProps;
return cloneElement(
c,
// children[index].key will become '.$key' in clone by default,
// we have to overwrite with the correct key explicitly
{
extraProps: {
...extraProps,
key: eventKey,
mode: 'vertical-left',
parentUniKey: MENUITEM_OVERFLOWED_UNI_KEY,
parentUniKeys: MENUITEM_OVERFLOWED_UNI_KEYS,
},
},
);
});
overflowed = this.getOverflowedSubMenuItem(eventKey, this.overflowedItems);
}
}
const ret = [...acc, overflowed, item];
if (index === children.length - 1) {
// need a placeholder for calculating overflowed indicator width
ret.push(this.getOverflowedSubMenuItem(eventKey, [], true));
}
return ret;
}
return [...acc, item];
}, []);
},
},
render() {
const Tag = this.$props.tag;
return <Tag>{this.renderChildren(getSlot(this))}</Tag>;
},
};
DOMWrap.props = {
mode: PropTypes.oneOf(['horizontal', 'vertical', 'vertical-left', 'vertical-right', 'inline']),
prefixCls: PropTypes.string,
level: PropTypes.number,
theme: PropTypes.string,
overflowedIndicator: PropTypes.any,
visible: PropTypes.looseBool,
hiddenClassName: PropTypes.string,
tag: PropTypes.string.def('div'),
};
export default DOMWrap;

View File

@ -1,23 +0,0 @@
import { inject } from 'vue';
import { injectExtraPropsKey } from './FunctionProvider';
export default {
name: 'MenuDivider',
inheritAttrs: false,
props: {
disabled: {
type: Boolean,
default: true,
},
rootPrefixCls: String,
},
setup() {
return {
injectExtraProps: inject(injectExtraPropsKey, () => ({})),
};
},
render() {
const { rootPrefixCls } = { ...this.$props, ...this.injectExtraProps };
const { class: className = '', style } = this.$attrs;
return <li class={[className, `${rootPrefixCls}-item-divider`]} style={style} />;
},
};

View File

@ -1,20 +0,0 @@
// import PropTypes from '../_util/vue-types';
import { computed, provide } from 'vue';
import { propTypes } from '../vc-progress/src/types';
export const injectExtraPropsKey = Symbol();
const FunctionProvider = {
inheritAttrs: false,
isMenuProvider: true,
props: {
extraProps: propTypes.object,
},
setup(props, { slots }) {
provide(
injectExtraPropsKey,
computed(() => props.extraProps),
);
return () => slots.default?.();
},
};
export default FunctionProvider;

View File

@ -1,46 +0,0 @@
import { createVNode, defineComponent, inject, provide, watch } from 'vue';
import { injectExtraPropsKey } from './FunctionProvider';
export default function wrapWithConnect(WrappedComponent) {
const tempProps = WrappedComponent.props || {};
const props = {};
Object.keys(tempProps).forEach(k => {
props[k] = { ...tempProps[k], required: false };
});
const Connect = {
name: `Connect_${WrappedComponent.name}`,
inheritAttrs: false,
props,
setup(props) {
provide(injectExtraPropsKey, undefined); // 断掉 injectExtraPropsKey 的依赖
const injectExtraProps = injectExtraPropsKey ? inject(injectExtraPropsKey, () => ({})) : {};
watch(injectExtraProps, () => {
// 神奇的问题vue 3.0.3 之后,没能正确响应式,暂时加个 watch hack 一下
});
return {
props,
injectExtraProps,
};
},
methods: {
getWrappedInstance() {
return this.$refs.wrappedInstance;
},
},
render() {
const { $slots = {}, $attrs } = this;
const props = { ...this.props, ...this.injectExtraProps };
const wrapProps = {
...$attrs,
...props,
ref: 'wrappedInstance',
};
// const slots = {};
// for (let [key, value] of Object.entries($slots)) {
// slots[key] = () => value();
// }
return createVNode(WrappedComponent, wrapProps, $slots);
},
};
return defineComponent(Connect);
}

View File

@ -1,217 +0,0 @@
import PropTypes from '../_util/vue-types';
import { default as SubPopupMenu } from './SubPopupMenu';
import BaseMixin from '../_util/BaseMixin';
import hasProp, { getOptionProps, getComponent } from '../_util/props-util';
import commonPropsType from './commonPropsType';
import {
computed,
defineComponent,
getCurrentInstance,
provide,
reactive,
ref,
toRaw,
watch,
} from 'vue';
import isEqual from 'lodash-es/isEqual';
const Menu = {
name: 'Menu',
inheritAttrs: false,
props: {
...commonPropsType,
onClick: PropTypes.func,
selectable: PropTypes.looseBool.def(true),
},
mixins: [BaseMixin],
setup(props) {
const menuChildrenInfo = reactive({});
const selectedKeys = ref(props.selectedKeys || props.defaultSelectedKeys || []);
const openKeys = ref(props.openKeys || props.defaultOpenKeys || []);
// computed(() => {
// return props.openKeys || props.defaultOpenKeys || [];
// });
watch(
() => props.selectedKeys,
() => {
selectedKeys.value = props.selectedKeys || [];
},
);
watch(
() => props.openKeys,
() => {
openKeys.value = props.openKeys || [];
},
);
const activeKey = reactive({
'0-menu-': props.activeKey,
});
const defaultActiveFirst = reactive({});
const addChildrenInfo = (key, info) => {
menuChildrenInfo[key] = info;
};
const removeChildrenInfo = key => {
delete menuChildrenInfo[key];
};
const getActiveKey = key => {
return key;
}; // TODO
const selectedParentUniKeys = ref([]);
watch(menuChildrenInfo, () => {
const keys = Object.values(menuChildrenInfo)
.filter(info => info.isSelected)
.reduce((allKeys, { parentUniKeys = [] }) => {
return [...allKeys, ...toRaw(parentUniKeys)];
}, []);
if (!isEqual(selectedParentUniKeys.value, keys)) {
selectedParentUniKeys.value = keys || [];
}
});
const store = reactive({
selectedKeys,
openKeys,
activeKey,
defaultActiveFirst,
menuChildrenInfo,
selectedParentUniKeys,
addChildrenInfo,
removeChildrenInfo,
getActiveKey,
});
const ins = getCurrentInstance();
const getEl = () => {
return ins.vnode.el;
};
provide('menuStore', store);
provide(
'parentMenu',
reactive({
isRootMenu: computed(() => props.isRootMenu),
getPopupContainer: computed(() => props.getPopupContainer),
getEl,
}),
);
return {
store,
};
},
methods: {
handleSelect(selectInfo) {
const props = this.$props;
if (props.selectable) {
// root menu
let selectedKeys = this.store.selectedKeys;
const selectedKey = selectInfo.key;
if (props.multiple) {
selectedKeys = selectedKeys.concat([selectedKey]);
} else {
selectedKeys = [selectedKey];
}
if (!hasProp(this, 'selectedKeys')) {
this.store.selectedKeys = selectedKeys;
}
this.__emit('select', {
...selectInfo,
selectedKeys,
});
}
},
handleClick(e) {
this.__emit('click', e);
},
// onKeyDown needs to be exposed as a instance method
// e.g., in rc-select, we need to navigate menu item while
// current active item is rc-select input box rather than the menu itself
onKeyDown(e, callback) {
this.innerMenu.getWrappedInstance().onKeyDown(e, callback);
},
onOpenChange(event) {
const openKeys = this.store.openKeys.concat();
let changed = false;
const processSingle = e => {
let oneChanged = false;
if (e.open) {
oneChanged = openKeys.indexOf(e.key) === -1;
if (oneChanged) {
openKeys.push(e.key);
}
} else {
const index = openKeys.indexOf(e.key);
oneChanged = index !== -1;
if (oneChanged) {
openKeys.splice(index, 1);
}
}
changed = changed || oneChanged;
};
if (Array.isArray(event)) {
// batch change call
event.forEach(processSingle);
} else {
processSingle(event);
}
if (changed) {
if (!hasProp(this, 'openKeys')) {
this.store.openKeys = openKeys;
}
this.__emit('openChange', openKeys);
}
},
handleDeselect(selectInfo) {
const props = this.$props;
if (props.selectable) {
const selectedKeys = this.store.selectedKeys.concat();
const selectedKey = selectInfo.key;
const index = selectedKeys.indexOf(selectedKey);
if (index !== -1) {
selectedKeys.splice(index, 1);
}
if (!hasProp(this, 'selectedKeys')) {
this.store.selectedKeys = selectedKeys;
}
this.__emit('deselect', {
...selectInfo,
selectedKeys,
});
}
},
getOpenTransitionName() {
const props = this.$props;
let transitionName = props.openTransitionName;
const animationName = props.openAnimation;
if (!transitionName && typeof animationName === 'string') {
transitionName = `${props.prefixCls}-open-${animationName}`;
}
return transitionName;
},
saveInnerMenu(ref) {
this.innerMenu = ref;
},
},
render() {
const props = { ...getOptionProps(this), ...this.$attrs };
props.class = props.class
? `${props.class} ${props.prefixCls}-root`
: `${props.prefixCls}-root`;
const subPopupMenuProps = {
...props,
itemIcon: getComponent(this, 'itemIcon', props),
expandIcon: getComponent(this, 'expandIcon', props),
overflowedIndicator: getComponent(this, 'overflowedIndicator', props) || <span>···</span>,
openTransitionName: this.getOpenTransitionName(),
onClick: this.handleClick,
onOpenChange: this.onOpenChange,
onDeselect: this.handleDeselect,
onSelect: this.handleSelect,
ref: this.saveInnerMenu,
store: this.store,
};
return <SubPopupMenu {...subPopupMenuProps} v-slots={this.$slots} />;
},
};
export default defineComponent(Menu);

View File

@ -1,224 +0,0 @@
import PropTypes from '../_util/vue-types';
import KeyCode from '../_util/KeyCode';
import BaseMixin from '../_util/BaseMixin';
import scrollIntoView from 'dom-scroll-into-view';
import { noop, menuAllProps } from './util';
import { getComponent, getSlot, findDOMNode } from '../_util/props-util';
import { computed, defineComponent, inject, onBeforeUnmount, onMounted, toRaw } from 'vue';
import InjectExtraProps from './InjectExtraProps';
const props = {
attribute: PropTypes.object,
rootPrefixCls: PropTypes.string,
eventKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
active: PropTypes.looseBool,
selectedKeys: PropTypes.array,
disabled: PropTypes.looseBool,
title: PropTypes.any,
index: PropTypes.number,
inlineIndent: PropTypes.number.def(24),
level: PropTypes.number.def(1),
mode: PropTypes.oneOf(['horizontal', 'vertical', 'vertical-left', 'vertical-right', 'inline']),
multiple: PropTypes.looseBool,
value: PropTypes.any,
manualRef: PropTypes.func.def(noop),
role: PropTypes.any,
subMenuKey: PropTypes.string,
itemIcon: PropTypes.any,
parentUniKeys: PropTypes.array.def(() => []),
parentUniKey: PropTypes.string,
// clearSubMenuTimers: PropTypes.func.def(noop),
};
let indexGuid = 0;
const MenuItem = defineComponent({
name: 'AMenuItem',
mixins: [BaseMixin],
inheritAttrs: false,
props,
isMenuItem: true,
setup(props) {
const uniKey = `menu_item_${++indexGuid}`;
const store = inject('menuStore', () => ({}));
const isSelected = computed(
() => store.selectedKeys && store.selectedKeys.indexOf(props.eventKey) !== -1,
);
onMounted(() => {
store.addChildrenInfo(
uniKey,
computed(() => ({
parentUniKeys: props.parentUniKeys,
parentUniKey: props.parentUniKey,
eventKey: props.eventKey,
isSelected: isSelected.value,
disabled: props.disabled,
})),
);
});
onBeforeUnmount(() => {
store.removeChildrenInfo(uniKey);
});
return {
parentMenu: inject('parentMenu', undefined),
isSelected,
};
},
created() {
this.prevActive = this.active;
// invoke customized ref to expose component to mixin
this.callRef();
},
updated() {
this.$nextTick(() => {
const { active, parentMenu, eventKey } = this;
if (!this.prevActive && active && (!parentMenu || !parentMenu[`scrolled-${eventKey}`])) {
scrollIntoView(findDOMNode(this.node), parentMenu.getEl(), {
onlyScrollIfNeeded: true,
});
parentMenu[`scrolled-${eventKey}`] = true;
} else if (parentMenu && parentMenu[`scrolled-${eventKey}`]) {
delete parentMenu[`scrolled-${eventKey}`];
}
this.prevActive = active;
});
this.callRef();
},
methods: {
onKeyDown(e) {
const keyCode = e.keyCode;
if (keyCode === KeyCode.ENTER) {
this.onClick(e);
return true;
}
},
onMouseLeave(e) {
const { eventKey } = this.$props;
this.__emit('itemHover', {
key: eventKey,
hover: false,
});
this.__emit('mouseleave', {
key: eventKey,
domEvent: e,
});
},
onMouseEnter(e) {
const { eventKey } = this;
this.__emit('itemHover', {
key: eventKey,
hover: true,
});
this.__emit('mouseenter', {
key: eventKey,
domEvent: e,
});
},
onClick(e) {
const { eventKey, multiple } = this.$props;
const { isSelected } = this;
const info = {
key: eventKey,
keyPath: [eventKey],
item: { ...toRaw(this.$props), isSelected },
domEvent: e,
};
this.__emit('click', info);
if (multiple) {
if (isSelected) {
this.__emit('deselect', info);
} else {
this.__emit('select', info);
}
} else if (!isSelected) {
this.__emit('select', info);
}
},
getPrefixCls() {
return `${this.$props.rootPrefixCls}-item`;
},
getActiveClassName() {
return `${this.getPrefixCls()}-active`;
},
getSelectedClassName() {
return `${this.getPrefixCls()}-selected`;
},
getDisabledClassName() {
return `${this.getPrefixCls()}-disabled`;
},
saveNode(node) {
this.node = node;
},
callRef() {
if (this.manualRef) {
this.manualRef(this);
}
},
},
render() {
const { class: cls, style, ...props } = { ...this.$props, ...this.$attrs };
const className = {
[cls]: !!cls,
[this.getPrefixCls()]: true,
[this.getActiveClassName()]: !props.disabled && this.active,
[this.getSelectedClassName()]: this.isSelected,
[this.getDisabledClassName()]: props.disabled,
};
let attrs = {
...props.attribute,
title: props.title,
role: props.role || 'menuitem',
'aria-disabled': props.disabled,
};
if (props.role === 'option') {
// overwrite to option
attrs = {
...attrs,
role: 'option',
'aria-selected': this.isSelected,
};
} else if (props.role === null || props.role === 'none') {
// sometimes we want to specify role inside <li/> element
// <li><a role='menuitem'>Link</a></li> would be a good example
// in this case the role on <li/> should be "none" to
// remove the implied listitem role.
// https://www.w3.org/TR/wai-aria-practices-1.1/examples/menubar/menubar-1/menubar-1.html
attrs.role = 'none';
}
// In case that onClick/onMouseLeave/onMouseEnter is passed down from owner
const mouseEvent = {
onClick: props.disabled ? noop : this.onClick,
onMouseleave: props.disabled ? noop : this.onMouseLeave,
onMouseenter: props.disabled ? noop : this.onMouseEnter,
};
const styles = { ...(style || {}) };
if (props.mode === 'inline') {
styles.paddingLeft = `${props.inlineIndent * props.level}px`;
}
menuAllProps.forEach(key => delete props[key]);
const liProps = {
...props,
...attrs,
...mouseEvent,
ref: this.saveNode,
};
return (
<li {...liProps} style={styles} class={className}>
{getSlot(this)}
{getComponent(this, 'itemIcon', props)}
</li>
);
},
});
export default InjectExtraProps(MenuItem);
export { props as menuItemProps };

View File

@ -1,51 +0,0 @@
import PropTypes from '../_util/vue-types';
import { getComponent, getSlot } from '../_util/props-util';
import { menuAllProps } from './util';
import { defineComponent, inject } from 'vue';
import classNames from '../_util/classNames';
import { injectExtraPropsKey } from './FunctionProvider';
const MenuItemGroup = {
name: 'MenuItemGroup',
inheritAttrs: false,
setup() {
return {
injectExtraProps: inject(injectExtraPropsKey, () => ({})),
};
},
props: {
renderMenuItem: PropTypes.func,
index: PropTypes.number,
className: PropTypes.string,
subMenuKey: PropTypes.string,
rootPrefixCls: PropTypes.string,
disabled: PropTypes.looseBool.def(true),
title: PropTypes.any,
},
isMenuItemGroup: true,
methods: {
renderInnerMenuItem(item) {
const { renderMenuItem, index, subMenuKey } = { ...this.$props, ...this.injectExtraProps };
return renderMenuItem(item, index, subMenuKey);
},
},
render() {
const props = { ...this.$props, ...this.injectExtraProps, ...this.$attrs };
const { class: cls = '', rootPrefixCls, title } = props;
const titleClassName = `${rootPrefixCls}-item-group-title`;
const listClassName = `${rootPrefixCls}-item-group-list`;
menuAllProps.forEach(key => delete props[key]);
// Set onClick to null, to ignore propagated onClick event
delete props.onClick;
const children = getSlot(this);
return (
<li {...props} class={classNames(cls, `${rootPrefixCls}-item-group`)}>
<div class={titleClassName} title={typeof title === 'string' ? title : undefined}>
{getComponent(this, 'title')}
</div>
<ul class={listClassName}>{children && children.map(this.renderInnerMenuItem)}</ul>
</li>
);
},
};
export default defineComponent(MenuItemGroup);

View File

@ -1,552 +0,0 @@
import {
computed,
defineComponent,
getCurrentInstance,
inject,
onBeforeUnmount,
onMounted,
provide,
reactive,
} from 'vue';
import omit from 'omit.js';
import PropTypes from '../_util/vue-types';
import Trigger from '../vc-trigger';
import KeyCode from '../_util/KeyCode';
import SubPopupMenu from './SubPopupMenu';
import placements from './placements';
import BaseMixin from '../_util/BaseMixin';
import { getComponent, splitAttrs, findDOMNode, getSlot } from '../_util/props-util';
import { requestAnimationTimeout, cancelAnimationTimeout } from '../_util/requestAnimationTimeout';
import { noop, getMenuIdFromSubMenuEventKey, loopMenuItemRecursively } from './util';
import { getTransitionProps, Transition } from '../_util/transition';
import InjectExtraProps from './InjectExtraProps';
let guid = 0;
const popupPlacementMap = {
horizontal: 'bottomLeft',
vertical: 'rightTop',
'vertical-left': 'rightTop',
'vertical-right': 'leftTop',
};
const updateDefaultActiveFirst = (store, eventKey, defaultActiveFirst) => {
const menuId = getMenuIdFromSubMenuEventKey(eventKey);
store.defaultActiveFirst[menuId] = defaultActiveFirst;
};
let indexGuid = 0;
const SubMenu = defineComponent({
name: 'SubMenu',
mixins: [BaseMixin],
inheritAttrs: false,
isSubMenu: true,
props: {
title: PropTypes.any,
openKeys: PropTypes.array.def([]),
openChange: PropTypes.func.def(noop),
rootPrefixCls: PropTypes.string,
eventKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
multiple: PropTypes.looseBool,
isRootMenu: PropTypes.looseBool.def(false),
index: PropTypes.number,
triggerSubMenuAction: PropTypes.string,
popupClassName: PropTypes.string,
getPopupContainer: PropTypes.func,
forceSubMenuRender: PropTypes.looseBool.def(false),
openAnimation: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
disabled: PropTypes.looseBool,
subMenuOpenDelay: PropTypes.number.def(0.1),
subMenuCloseDelay: PropTypes.number.def(0.1),
level: PropTypes.number.def(1),
inlineIndent: PropTypes.number.def(24),
openTransitionName: PropTypes.string,
popupOffset: PropTypes.array,
mode: PropTypes.oneOf([
'horizontal',
'vertical',
'vertical-left',
'vertical-right',
'inline',
]).def('vertical'),
manualRef: PropTypes.func.def(noop),
builtinPlacements: PropTypes.object.def(() => ({})),
itemIcon: PropTypes.any,
expandIcon: PropTypes.any,
subMenuKey: PropTypes.string,
theme: PropTypes.string,
parentUniKeys: PropTypes.array.def(() => []),
parentUniKey: PropTypes.string,
isOverflowedSubMenu: PropTypes.looseBool.def(false),
},
isSubMenu: true,
setup(props) {
const uniKey = props.isOverflowedSubMenu
? 'MENUITEM_OVERFLOWED_UNI_KEY'
: `sub_menu_${++indexGuid}`;
const store = inject('menuStore', () => ({}));
onMounted(() => {
store.addChildrenInfo(
uniKey,
computed(() => ({
parentUniKeys: props.parentUniKeys,
parentUniKey: props.parentUniKey,
eventKey: props.eventKey,
disabled: props.disabled,
})),
);
});
onBeforeUnmount(() => {
store.removeChildrenInfo(uniKey);
});
const isChildrenSelected = computed(() => {
return store.selectedParentUniKeys.indexOf(uniKey) !== -1;
});
const ins = getCurrentInstance();
const getEl = () => {
return ins.vnode.el;
};
provide(
'parentMenu',
reactive({
isRootMenu: computed(() => props.isRootMenu),
getPopupContainer: props.getPopupContainer,
getEl,
}),
);
return {
parentMenu: inject('parentMenu', undefined),
store,
isChildrenSelected,
childrenUniKeys: [...props.parentUniKeys, uniKey],
uniKey,
isOpen: computed(() => store.openKeys.indexOf(props.eventKey) > -1),
active: computed(() => store.activeKey[props.subMenuKey] === props.eventKey),
};
},
data() {
const props = this.$props;
const store = this.store;
const eventKey = props.eventKey;
const defaultActiveFirst = store.defaultActiveFirst;
let value = false;
if (defaultActiveFirst) {
value = defaultActiveFirst[eventKey];
}
updateDefaultActiveFirst(store, eventKey, value);
this.internalMenuId = undefined;
this.haveRendered = undefined;
this.haveOpened = undefined;
this.subMenuTitle = undefined;
return {};
},
mounted() {
this.$nextTick(() => {
this.handleUpdated();
});
},
updated() {
this.$nextTick(() => {
this.handleUpdated();
});
},
beforeUnmount() {
const { eventKey } = this;
this.__emit('destroy', eventKey);
/* istanbul ignore if */
if (this.minWidthTimeout) {
cancelAnimationTimeout(this.minWidthTimeout);
this.minWidthTimeout = null;
}
/* istanbul ignore if */
if (this.mouseenterTimeout) {
cancelAnimationTimeout(this.mouseenterTimeout);
this.mouseenterTimeout = null;
}
},
methods: {
isChildrenSelected2() {
if (this.haveOpened) return this.isChildrenSelected;
const ret = { find: false };
loopMenuItemRecursively(getSlot(this), this.store.selectedKeys, ret);
return ret.find;
},
handleUpdated() {
const { mode, manualRef } = this.$props;
// invoke customized ref to expose component to mixin
if (manualRef) {
manualRef(this);
}
if (mode !== 'horizontal' || !this.parentMenu.isRootMenu || !this.isOpen) {
return;
}
this.minWidthTimeout = requestAnimationTimeout(() => this.adjustWidth(), 0);
},
onKeyDown(e) {
const keyCode = e.keyCode;
const menu = this.menuInstance;
const { isOpen } = this;
if (keyCode === KeyCode.ENTER) {
this.onTitleClick(e);
updateDefaultActiveFirst(this.store, this.$props.eventKey, true);
return true;
}
if (keyCode === KeyCode.RIGHT) {
if (isOpen) {
menu.onKeyDown(e);
} else {
this.triggerOpenChange(true);
// need to update current menu's defaultActiveFirst value
updateDefaultActiveFirst(this.store, this.$props.eventKey, true);
}
return true;
}
if (keyCode === KeyCode.LEFT) {
let handled;
if (isOpen) {
handled = menu.onKeyDown(e);
} else {
return undefined;
}
if (!handled) {
this.triggerOpenChange(false);
handled = true;
}
return handled;
}
if (isOpen && (keyCode === KeyCode.UP || keyCode === KeyCode.DOWN)) {
return menu.onKeyDown(e);
}
return undefined;
},
onPopupVisibleChange(visible) {
this.triggerOpenChange(visible, visible ? 'mouseenter' : 'mouseleave');
},
onMouseEnter(e) {
const { eventKey: key } = this.$props;
updateDefaultActiveFirst(this.store, key, false);
this.__emit('mouseenter', {
key,
domEvent: e,
});
},
onMouseLeave(e) {
const { eventKey } = this.$props;
this.__emit('mouseleave', {
key: eventKey,
domEvent: e,
});
},
onTitleMouseEnter(domEvent) {
const { eventKey: key } = this.$props;
this.__emit('itemHover', {
key,
hover: true,
});
this.__emit('titleMouseenter', {
key,
domEvent,
});
},
onTitleMouseLeave(e) {
const { eventKey } = this.$props;
this.__emit('itemHover', {
key: eventKey,
hover: false,
});
this.__emit('titleMouseleave', {
key: eventKey,
domEvent: e,
});
},
onTitleClick(e) {
const { triggerSubMenuAction, eventKey } = this.$props;
this.__emit('titleClick', {
key: eventKey,
domEvent: e,
});
if (triggerSubMenuAction === 'hover') {
return;
}
this.triggerOpenChange(!this.isOpen, 'click');
updateDefaultActiveFirst(this.store, eventKey, false);
},
onSubMenuClick(info) {
this.__emit('click', this.addKeyPath(info));
},
getPrefixCls() {
return `${this.$props.rootPrefixCls}-submenu`;
},
getActiveClassName() {
return `${this.getPrefixCls()}-active`;
},
getDisabledClassName() {
return `${this.getPrefixCls()}-disabled`;
},
getSelectedClassName() {
return `${this.getPrefixCls()}-selected`;
},
getOpenClassName() {
return `${this.$props.rootPrefixCls}-submenu-open`;
},
saveMenuInstance(c) {
// children menu instance
this.menuInstance = c;
},
addKeyPath(info) {
return {
...info,
keyPath: (info.keyPath || []).concat(this.$props.eventKey),
};
},
triggerOpenChange(open, type) {
const key = this.$props.eventKey;
const openChange = () => {
this.__emit('openChange', {
key,
item: this.$props,
trigger: type,
open,
});
};
if (type === 'mouseenter') {
// make sure mouseenter happen after other menu item's mouseleave
this.mouseenterTimeout = requestAnimationTimeout(() => {
openChange();
}, 0);
} else {
openChange();
}
},
adjustWidth() {
/* istanbul ignore if */
if (!this.subMenuTitle || !this.menuInstance) {
return;
}
const popupMenu = findDOMNode(this.menuInstance);
if (popupMenu.offsetWidth >= this.subMenuTitle.offsetWidth) {
return;
}
/* istanbul ignore next */
popupMenu.style.minWidth = `${this.subMenuTitle.offsetWidth}px`;
},
saveSubMenuTitle(subMenuTitle) {
this.subMenuTitle = subMenuTitle;
},
renderChildren() {
const props = { ...this.$props, ...this.$attrs };
const subPopupMenuProps = {
mode: props.mode === 'horizontal' ? 'vertical' : props.mode,
visible: this.isOpen,
level: props.level + 1,
inlineIndent: props.inlineIndent,
focusable: false,
eventKey: `${props.eventKey}-menu-`,
openKeys: props.openKeys,
openTransitionName: props.openTransitionName,
openAnimation: props.openAnimation,
subMenuOpenDelay: props.subMenuOpenDelay,
subMenuCloseDelay: props.subMenuCloseDelay,
forceSubMenuRender: props.forceSubMenuRender,
triggerSubMenuAction: props.triggerSubMenuAction,
builtinPlacements: props.builtinPlacements,
multiple: props.multiple,
prefixCls: props.rootPrefixCls,
manualRef: this.saveMenuInstance,
itemIcon: getComponent(this, 'itemIcon'),
expandIcon: getComponent(this, 'expandIcon'),
onClick: this.onSubMenuClick,
onSelect: props.onSelect || noop,
onDeselect: props.onDeselect || noop,
onOpenChange: props.onOpenChange || noop,
id: this.internalMenuId,
parentUniKeys: this.childrenUniKeys,
parentUniKey: this.uniKey,
};
const haveRendered = this.haveRendered;
this.haveRendered = true;
this.haveOpened =
this.haveOpened || subPopupMenuProps.visible || subPopupMenuProps.forceSubMenuRender;
// never rendered not planning to, don't render
if (!this.haveOpened) {
return <div />;
}
// don't show transition on first rendering (no animation for opened menu)
// show appear transition if it's not visible (not sure why)
// show appear transition if it's not inline mode
const transitionAppear =
haveRendered || !subPopupMenuProps.visible || !subPopupMenuProps.mode === 'inline';
subPopupMenuProps.class = ` ${subPopupMenuProps.prefixCls}-sub`;
let transitionProps = { appear: transitionAppear, css: false };
if (subPopupMenuProps.openTransitionName) {
transitionProps = getTransitionProps(subPopupMenuProps.openTransitionName, {
appear: transitionAppear,
});
} else if (typeof subPopupMenuProps.openAnimation === 'object') {
transitionProps = { ...transitionProps, ...(subPopupMenuProps.openAnimation || {}) };
if (!transitionAppear) {
transitionProps.appear = false;
}
} else if (typeof subPopupMenuProps.openAnimation === 'string') {
transitionProps = getTransitionProps(subPopupMenuProps.openAnimation, {
appear: transitionAppear,
});
}
return (
<Transition {...transitionProps}>
<SubPopupMenu v-show={this.isOpen} {...subPopupMenuProps} v-slots={this.$slots} />
</Transition>
);
},
},
render() {
const props = { ...this.$props, ...this.$attrs };
const { onEvents } = splitAttrs(props);
const isOpen = this.isOpen;
const prefixCls = this.getPrefixCls();
const isInlineMode = props.mode === 'inline';
if (!this.internalMenuId) {
if (props.eventKey) {
this.internalMenuId = `${props.eventKey}$Menu`;
} else {
this.internalMenuId = `$__$${++guid}$Menu`;
}
}
const children = this.renderChildren();
const className = {
[prefixCls]: true,
[`${prefixCls}-${props.mode}`]: true,
[props.class]: !!props.class,
[this.getOpenClassName()]: isOpen,
[this.getActiveClassName()]: this.active || (isOpen && !isInlineMode),
[this.getDisabledClassName()]: props.disabled,
[this.getSelectedClassName()]: this.isChildrenSelected || this.isChildrenSelected2(),
};
let mouseEvents = {};
let titleClickEvents = {};
let titleMouseEvents = {};
if (!props.disabled) {
mouseEvents = {
onMouseleave: this.onMouseLeave,
onMouseenter: this.onMouseEnter,
};
// only works in title, not outer li
titleClickEvents = {
onClick: this.onTitleClick,
};
titleMouseEvents = {
onMouseenter: this.onTitleMouseEnter,
onMouseleave: this.onTitleMouseLeave,
};
}
const style = {};
if (isInlineMode) {
style.paddingLeft = `${props.inlineIndent * props.level}px`;
}
let ariaOwns = {};
// only set aria-owns when menu is open
// otherwise it would be an invalid aria-owns value
// since corresponding node cannot be found
if (isOpen) {
ariaOwns = {
'aria-owns': this.internalMenuId,
};
}
const titleProps = {
'aria-expanded': isOpen,
...ariaOwns,
'aria-haspopup': 'true',
title: typeof props.title === 'string' ? props.title : undefined,
...titleMouseEvents,
...titleClickEvents,
style,
class: `${prefixCls}-title`,
ref: this.saveSubMenuTitle,
};
// expand custom icon should NOT be displayed in menu with horizontal mode.
let icon = null;
if (props.mode !== 'horizontal') {
icon = getComponent(this, 'expandIcon', props);
}
const title = (
<div {...titleProps}>
{getComponent(this, 'title')}
{icon || <i class={`${prefixCls}-arrow`} />}
</div>
);
const getPopupContainer = this.parentMenu.isRootMenu
? this.parentMenu.getPopupContainer
: triggerNode => triggerNode.parentNode;
const popupPlacement = popupPlacementMap[props.mode];
const popupAlign = props.popupOffset ? { offset: props.popupOffset } : {};
let popupClassName = props.mode === 'inline' ? '' : props.popupClassName || '';
popupClassName = `${prefixCls}-popup ${popupClassName}`;
const liProps = {
...omit(onEvents, ['onClick']),
...mouseEvents,
class: className,
style: props.style,
};
return (
<li {...liProps} role="menuitem">
{isInlineMode && title}
{isInlineMode && children}
{!isInlineMode && (
<Trigger
prefixCls={prefixCls}
popupClassName={popupClassName}
getPopupContainer={getPopupContainer}
builtinPlacements={placements}
builtinPlacements={Object.assign({}, placements, props.builtinPlacements)}
popupPlacement={popupPlacement}
popupVisible={isOpen}
popupAlign={popupAlign}
action={props.disabled ? [] : [props.triggerSubMenuAction]}
mouseEnterDelay={props.subMenuOpenDelay}
mouseLeaveDelay={props.subMenuCloseDelay}
onPopupVisibleChange={this.onPopupVisibleChange}
forceRender={props.forceSubMenuRender}
// popupTransitionName='rc-menu-open-slide-up'
// popupAnimation={transitionProps}
popup={children}
>
{title}
</Trigger>
)}
</li>
);
},
});
export default InjectExtraProps(SubMenu);

View File

@ -1,389 +0,0 @@
import { Comment, inject } from 'vue';
import PropTypes from '../_util/vue-types';
import BaseMixin from '../_util/BaseMixin';
import KeyCode from '../_util/KeyCode';
import classNames from '../_util/classNames';
import { getKeyFromChildrenIndex, noop, isMobileDevice, menuAllProps } from './util';
import DOMWrap from './DOMWrap';
import {
initDefaultProps,
getOptionProps,
getComponent,
splitAttrs,
getSlot,
} from '../_util/props-util';
import FunctionProvider from './FunctionProvider';
// import { getActiveKey } from '../vc-tabs/src/utils';
function allDisabled(arr) {
if (!arr.length) {
return true;
}
return arr.every(c => {
return !!c.disabled;
});
}
function updateActiveKey(store, menuId, activeKey) {
store.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 = getPropsData(c);
// 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 = getPropsData(c);
// const noActiveKey = activeKey === null || activeKey === undefined;
// if (noActiveKey && c && !propsData.disabled) {
// activeKey = getKeyFromChildrenIndex(c, eventKey, i);
// }
// });
// return activeKey;
// }
// return activeKey;
// }
const SubPopupMenu = {
name: 'SubPopupMenu',
inheritAttrs: false,
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.looseBool,
eventKey: PropTypes.string,
// adding in refactor
focusable: PropTypes.looseBool,
multiple: PropTypes.looseBool,
defaultActiveFirst: PropTypes.looseBool,
activeKey: 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([]),
subMenuOpenDelay: PropTypes.number.def(0.1),
subMenuCloseDelay: PropTypes.number.def(0.1),
forceSubMenuRender: PropTypes.looseBool.def(false),
parentUniKeys: PropTypes.array.def(() => []),
parentUniKey: PropTypes.string,
theme: PropTypes.string,
},
{
prefixCls: 'rc-menu',
mode: 'vertical',
level: 1,
inlineIndent: 24,
visible: true,
focusable: true,
manualRef: noop,
},
),
mixins: [BaseMixin],
setup() {
const store = inject('menuStore', () => ({}));
return { store };
},
created() {
const props = getOptionProps(this);
this.prevProps = { ...props };
this.store.activeKey[props.eventKey] = this.store.getActiveKey(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 : this.store.activeKey[getEventKey(props)];
const activeKey = this.store.getActiveKey(originalActiveKey);
if (activeKey !== originalActiveKey) {
updateActiveKey(this.store, getEventKey(props), activeKey);
} else if ('activeKey' in prevProps) {
// If prev activeKey is not same as current activeKey,
// we should set it.
const prevActiveKey = this.store.getActiveKey(prevProps.activeKey);
if (activeKey !== prevActiveKey) {
updateActiveKey(this.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.store, getEventKey(this.$props), activeItem.eventKey);
if (typeof callback === 'function') {
callback(activeItem);
}
return 1;
}
return undefined;
},
onItemHover(e) {
const { key, hover } = e;
updateActiveKey(this.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.store.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) {
return getComponent(instance, name);
},
renderCommonMenuItem(child, i, extraProps) {
if (child.type === Comment) {
return child;
}
const state = this.store;
const props = this.$props;
const key = getKeyFromChildrenIndex(child, props.eventKey, i);
const childProps = child.props || {}; // child.props
const isActive = key === state.activeKey[getEventKey(this.$props)];
if (!childProps.disabled) {
// manualRef使keyrefthis.instanceArray
this.instanceArrayKeyIndexMap[key] = Object.keys(this.instanceArrayKeyIndexMap).length;
}
const newChildProps = {
mode: childProps.mode || props.mode,
level: props.level,
inlineIndent: props.inlineIndent,
renderMenuItem: this.renderMenuItem,
rootPrefixCls: props.prefixCls,
index: i,
// 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,
builtinPlacements: props.builtinPlacements,
itemIcon: this.getIcon(child, 'itemIcon') || this.getIcon(this, 'itemIcon'),
expandIcon: this.getIcon(child, 'expandIcon') || this.getIcon(this, 'expandIcon'),
...extraProps,
onClick: e => {
(childProps.onClick || noop)(e);
this.onClick(e);
},
onItemHover: this.onItemHover,
onOpenChange: this.onOpenChange,
onDeselect: this.onDeselect,
// destroy: this.onDestroy,
onSelect: this.onSelect,
parentUniKeys: this.parentUniKeys,
parentUniKey: this.parentUniKey,
};
if (props.forceSubMenuRender !== undefined) {
newChildProps.forceSubMenuRender = props.forceSubMenuRender;
}
// ref: https://github.com/ant-design/ant-design/issues/13943
if (props.mode === 'inline' || isMobileDevice()) {
newChildProps.triggerSubMenuAction = 'click';
}
return <FunctionProvider extraProps={newChildProps}>{child}</FunctionProvider>;
},
renderMenuItem(c, i, subMenuKey) {
if (!c) {
return null;
}
const state = this.store;
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 { onEvents, extraAttrs } = splitAttrs(this.$attrs);
const { eventKey, prefixCls, visible, level, mode, theme } = props;
this.instanceArray = [];
this.instanceArrayKeyIndexMap = {};
const className = classNames(
extraAttrs.class,
props.prefixCls,
`${props.prefixCls}-${props.mode}`,
);
menuAllProps.forEach(key => delete props[key]);
// Otherwise, the propagated click event will trigger another onClick
delete onEvents.onClick;
const domWrapProps = {
...props,
tag: 'ul',
// hiddenClassName: `${prefixCls}-hidden`,
visible,
prefixCls,
level,
mode,
theme,
overflowedIndicator: getComponent(this, 'overflowedIndicator'),
role: props.role || 'menu',
class: className,
style: extraAttrs.style,
...onEvents,
};
if (extraAttrs.id !== undefined) {
domWrapProps.id = extraAttrs.id;
}
if (props.focusable) {
domWrapProps.tabindex = '0';
domWrapProps.onKeydown = this.onKeyDown;
}
delete domWrapProps.children;
return (
// ESLint is not smart enough to know that the type of `children` was checked.
/* eslint-disable */
<DOMWrap {...domWrapProps}>
{getSlot(this).map((c, i) => this.renderMenuItem(c, i, eventKey || '0-menu-'))}
</DOMWrap>
/*eslint -enable */
);
},
};
export default SubPopupMenu;

View File

@ -1,318 +0,0 @@
@menuPrefixCls: rc-menu;
@font-face {
font-family: 'FontAwesome';
src: url('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.2.0/fonts/fontawesome-webfont.eot');
src: url('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.2.0/fonts/fontawesome-webfont.eot?#iefix')
format('embedded-opentype'),
url('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.2.0/fonts/fontawesome-webfont.woff')
format('woff'),
url('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.2.0/fonts/fontawesome-webfont.ttf')
format('truetype'),
url('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.2.0/fonts/fontawesome-webfont.svg?#fontawesomeregular')
format('svg');
font-weight: normal;
font-style: normal;
}
.@{menuPrefixCls} {
outline: none;
margin-bottom: 0;
padding-left: 0; // Override default ul/ol
list-style: none;
border: 1px solid #d9d9d9;
box-shadow: 0 0 4px #d9d9d9;
border-radius: 3px;
color: #666;
&-hidden {
display: none;
}
&-collapse {
overflow: hidden;
&-active {
transition: height 0.3s ease-out;
}
}
&-item-group-list {
margin: 0;
padding: 0;
}
&-item-group-title {
color: #999;
line-height: 1.5;
padding: 8px 10px;
border-bottom: 1px solid #dedede;
}
&-item-active,
&-submenu-active > &-submenu-title {
background-color: #eaf8fe;
}
&-item-selected {
background-color: #eaf8fe;
// fix chrome render bug
transform: translateZ(0);
}
&-submenu-selected {
background-color: #eaf8fe;
}
& > li&-submenu {
padding: 0;
}
&-horizontal&-sub,
&-vertical&-sub,
&-vertical-left&-sub,
&-vertical-right&-sub {
min-width: 160px;
margin-top: 0;
}
&-item,
&-submenu-title {
margin: 0;
position: relative;
display: block;
padding: 7px 7px 7px 16px;
white-space: nowrap;
// Disabled state sets text to gray and nukes hover/tab effects
&.@{menuPrefixCls}-item-disabled,
&.@{menuPrefixCls}-submenu-disabled {
color: #777 !important;
}
}
& > &-item-divider {
height: 1px;
margin: 1px 0;
overflow: hidden;
padding: 0;
line-height: 0;
background-color: #e5e5e5;
}
&-submenu {
&-popup {
position: absolute;
.submenu-title-wrapper {
padding-right: 20px;
}
}
> .@{menuPrefixCls} {
background-color: #fff;
}
}
.@{menuPrefixCls}-submenu-title,
.@{menuPrefixCls}-item {
.anticon {
width: 14px;
height: 14px;
margin-right: 8px;
top: -1px;
}
}
&-horizontal {
background-color: #f3f5f7;
border: none;
border-bottom: 1px solid #d9d9d9;
box-shadow: none;
white-space: nowrap;
overflow: hidden;
& > .@{menuPrefixCls}-item,
& > .@{menuPrefixCls}-submenu > .@{menuPrefixCls}-submenu-title {
padding: 15px 20px;
}
& > .@{menuPrefixCls}-submenu,
& > .@{menuPrefixCls}-item {
border-bottom: 2px solid transparent;
display: inline-block;
vertical-align: bottom;
&-active {
border-bottom: 2px solid #2db7f5;
background-color: #f3f5f7;
color: #2baee9;
}
}
&:after {
content: '\20';
display: block;
height: 0;
clear: both;
}
}
&-vertical,
&-vertical-left,
&-vertical-right,
&-inline {
padding: 12px 0;
& > .@{menuPrefixCls}-item,
& > .@{menuPrefixCls}-submenu > .@{menuPrefixCls}-submenu-title {
padding: 12px 8px 12px 24px;
}
.@{menuPrefixCls}-submenu-arrow {
display: inline-block;
font: normal normal normal 14px/1 FontAwesome;
font-size: inherit;
vertical-align: baseline;
text-align: center;
text-transform: none;
text-rendering: auto;
position: absolute;
right: 16px;
line-height: 1.5em;
&:before {
content: '\f0da';
}
}
}
&-inline {
.@{menuPrefixCls}-submenu-arrow {
transform: rotate(90deg);
transition: transform 0.3s;
}
& .@{menuPrefixCls}-submenu-open > .@{menuPrefixCls}-submenu-title {
.@{menuPrefixCls}-submenu-arrow {
transform: rotate(-90deg);
}
}
}
&-vertical&-sub,
&-vertical-left&-sub,
&-vertical-right&-sub {
padding: 0;
}
&-sub&-inline {
padding: 0;
border: none;
border-radius: 0;
box-shadow: none;
& > .@{menuPrefixCls}-item,
& > .@{menuPrefixCls}-submenu > .@{menuPrefixCls}-submenu-title {
padding-top: 8px;
padding-bottom: 8px;
padding-right: 0;
}
}
.effect() {
animation-duration: 0.3s;
animation-fill-mode: both;
transform-origin: 0 0;
}
&-open {
&-slide-up-enter,
&-slide-up-appear {
.effect();
opacity: 0;
animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1);
animation-play-state: paused;
}
&-slide-up-leave {
.effect();
opacity: 1;
animation-timing-function: cubic-bezier(0.6, 0.04, 0.98, 0.34);
animation-play-state: paused;
}
&-slide-up-enter&-slide-up-enter-active,
&-slide-up-appear&-slide-up-appear-active {
animation-name: rcMenuOpenSlideUpIn;
animation-play-state: running;
}
&-slide-up-leave&-slide-up-leave-active {
animation-name: rcMenuOpenSlideUpOut;
animation-play-state: running;
}
@keyframes rcMenuOpenSlideUpIn {
0% {
opacity: 0;
transform-origin: 0% 0%;
transform: scaleY(0);
}
100% {
opacity: 1;
transform-origin: 0% 0%;
transform: scaleY(1);
}
}
@keyframes rcMenuOpenSlideUpOut {
0% {
opacity: 1;
transform-origin: 0% 0%;
transform: scaleY(1);
}
100% {
opacity: 0;
transform-origin: 0% 0%;
transform: scaleY(0);
}
}
&-zoom-enter,
&-zoom-appear {
opacity: 0;
.effect();
animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1);
animation-play-state: paused;
}
&-zoom-leave {
.effect();
animation-timing-function: cubic-bezier(0.6, 0.04, 0.98, 0.34);
animation-play-state: paused;
}
&-zoom-enter&-zoom-enter-active,
&-zoom-appear&-zoom-appear-active {
animation-name: rcMenuOpenZoomIn;
animation-play-state: running;
}
&-zoom-leave&-zoom-leave-active {
animation-name: rcMenuOpenZoomOut;
animation-play-state: running;
}
@keyframes rcMenuOpenZoomIn {
0% {
opacity: 0;
transform: scale(0, 0);
}
100% {
opacity: 1;
transform: scale(1, 1);
}
}
@keyframes rcMenuOpenZoomOut {
0% {
transform: scale(1, 1);
}
100% {
opacity: 0;
transform: scale(0, 0);
}
}
}
}

View File

@ -1,42 +0,0 @@
import PropTypes from '../_util/vue-types';
export default {
prefixCls: PropTypes.string.def('rc-menu'),
focusable: PropTypes.looseBool.def(true),
multiple: PropTypes.looseBool,
visible: PropTypes.looseBool.def(true),
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]),
).def([]),
defaultOpenKeys: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])).def(
[],
),
openKeys: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])),
openAnimation: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
mode: PropTypes.oneOf([
'horizontal',
'vertical',
'vertical-left',
'vertical-right',
'inline',
]).def('vertical'),
triggerSubMenuAction: PropTypes.string.def('hover'),
subMenuOpenDelay: PropTypes.number.def(0.1),
subMenuCloseDelay: PropTypes.number.def(0.1),
level: PropTypes.number.def(1),
inlineIndent: PropTypes.number.def(24),
theme: PropTypes.oneOf(['light', 'dark']).def('light'),
getPopupContainer: PropTypes.func,
openTransitionName: PropTypes.string,
forceSubMenuRender: PropTypes.looseBool.def(false),
selectable: PropTypes.looseBool,
isRootMenu: PropTypes.looseBool.def(true),
builtinPlacements: PropTypes.object.def(() => ({})),
itemIcon: PropTypes.any,
expandIcon: PropTypes.any,
overflowedIndicator: PropTypes.any,
onClick: PropTypes.func,
onSelect: PropTypes.func,
onDeselect: PropTypes.func,
};

View File

@ -1,18 +0,0 @@
// based on rc-menu 7.5.5
import Menu from './Menu';
import SubMenu from './SubMenu';
import MenuItem, { menuItemProps } from './MenuItem';
import MenuItemGroup from './MenuItemGroup';
import Divider from './Divider';
export {
SubMenu,
MenuItem as Item,
menuItemProps as itemProps,
MenuItem,
MenuItemGroup,
MenuItemGroup as ItemGroup,
Divider,
};
export default Menu;

View File

@ -1,29 +0,0 @@
const autoAdjustOverflow = {
adjustX: 1,
adjustY: 1,
};
export const placements = {
topLeft: {
points: ['bl', 'tl'],
overflow: autoAdjustOverflow,
offset: [0, -7],
},
bottomLeft: {
points: ['tl', 'bl'],
overflow: autoAdjustOverflow,
offset: [0, 7],
},
leftTop: {
points: ['tr', 'tl'],
overflow: autoAdjustOverflow,
offset: [-4, 0],
},
rightTop: {
points: ['tl', 'tr'],
overflow: autoAdjustOverflow,
offset: [4, 0],
},
};
export default placements;

View File

@ -1,147 +0,0 @@
import isMobile from './utils/isMobile';
import isObject from 'lodash-es/isObject';
export function noop() {}
export function getKeyFromChildrenIndex(child, menuEventKey, index) {
const prefix = menuEventKey || '';
return child.key === null ? `${prefix}item_${index}` : child.key;
}
export function getMenuIdFromSubMenuEventKey(eventKey) {
return `${eventKey}-menu-`;
}
// export function loopMenuItem(children, cb) {
// let index = -1;
// children.forEach(c => {
// index++;
// if (c && c.type && c.type.isMenuItemGroup) {
// c.children.default &&
// c.children.default().forEach(c2 => {
// index++;
// cb(c2, index);
// });
// } else {
// cb(c, index);
// }
// });
// }
export function loopMenuItemRecursively(children, keys, ret) {
if (!children || ret.find) {
return;
}
children.forEach(c => {
if (ret.find) {
return;
}
const construct = c.type;
if (construct && isObject(construct)) {
if (
!construct ||
!(
construct.isSubMenu ||
construct.isMenuItem ||
construct.isMenuItemGroup ||
construct.isMenuProvider
)
) {
return;
}
if (keys.indexOf(c.key) !== -1) {
ret.find = true;
} else if (c.children && c.children.default) {
loopMenuItemRecursively(c.children.default(), keys, ret);
}
}
});
}
export const menuAllProps = [
'defaultSelectedKeys',
'selectedKeys',
'defaultOpenKeys',
'openKeys',
'mode',
'getPopupContainer',
'openTransitionName',
'openAnimation',
'subMenuOpenDelay',
'subMenuCloseDelay',
'forceSubMenuRender',
'triggerSubMenuAction',
'level',
'selectable',
'multiple',
'visible',
'focusable',
'defaultActiveFirst',
'prefixCls',
'inlineIndent',
'title',
'rootPrefixCls',
'eventKey',
'active',
'popupAlign',
'popupOffset',
'isOpen',
'renderMenuItem',
'manualRef',
'subMenuKey',
'disabled',
'index',
'isSelected',
'store',
'activeKey',
'builtinPlacements',
'overflowedIndicator',
// the following keys found need to be removed from test regression
'attribute',
'value',
'popupClassName',
'inlineCollapsed',
'menu',
'theme',
'itemIcon',
'expandIcon',
'onSelect',
'onDeselect',
'onDestroy',
'onOpenChange',
'onItemHover',
'onTitleMouseenter',
'onTitleMouseleave',
'onTitleClick',
'slots',
'ref',
'isRootMenu',
'parentUniKeys',
'parentUniKey',
];
// ref: https://github.com/ant-design/ant-design/issues/14007
// ref: https://bugs.chromium.org/p/chromium/issues/detail?id=360889
// getBoundingClientRect return the full precision value, which is
// not the same behavior as on chrome. Set the precision to 6 to
// unify their behavior
export const getWidth = elem => {
let width =
elem && typeof elem.getBoundingClientRect === 'function' && elem.getBoundingClientRect().width;
if (width) {
width = +width.toFixed(6);
}
return width || 0;
};
export const setStyle = (elem, styleProperty, value) => {
if (elem && typeof elem.style === 'object') {
elem.style[styleProperty] = value;
}
};
export const isMobileDevice = () => {
return isMobile.any;
};

View File

@ -1,110 +0,0 @@
// MIT License from https://github.com/kaimallea/isMobile
const applePhone = /iPhone/i;
const appleIpod = /iPod/i;
const appleTablet = /iPad/i;
const androidPhone = /\bAndroid(?:.+)Mobile\b/i; // Match 'Android' AND 'Mobile'
const androidTablet = /Android/i;
const amazonPhone = /\bAndroid(?:.+)SD4930UR\b/i;
const amazonTablet = /\bAndroid(?:.+)(?:KF[A-Z]{2,4})\b/i;
const windowsPhone = /Windows Phone/i;
const windowsTablet = /\bWindows(?:.+)ARM\b/i; // Match 'Windows' AND 'ARM'
const otherBlackberry = /BlackBerry/i;
const otherBlackberry10 = /BB10/i;
const otherOpera = /Opera Mini/i;
const otherChrome = /\b(CriOS|Chrome)(?:.+)Mobile/i;
const otherFirefox = /Mobile(?:.+)Firefox\b/i; // Match 'Mobile' AND 'Firefox'
function match(regex, userAgent) {
return regex.test(userAgent);
}
function isMobile(userAgent) {
let ua = userAgent || (typeof navigator !== 'undefined' ? navigator.userAgent : '');
// Facebook mobile app's integrated browser adds a bunch of strings that
// match everything. Strip it out if it exists.
let tmp = ua.split('[FBAN');
if (typeof tmp[1] !== 'undefined') {
[ua] = tmp;
}
// Twitter mobile app's integrated browser on iPad adds a "Twitter for
// iPhone" string. Same probably happens on other tablet platforms.
// This will confuse detection so strip it out if it exists.
tmp = ua.split('Twitter');
if (typeof tmp[1] !== 'undefined') {
[ua] = tmp;
}
const result = {
apple: {
phone: match(applePhone, ua) && !match(windowsPhone, ua),
ipod: match(appleIpod, ua),
tablet: !match(applePhone, ua) && match(appleTablet, ua) && !match(windowsPhone, ua),
device:
(match(applePhone, ua) || match(appleIpod, ua) || match(appleTablet, ua)) &&
!match(windowsPhone, ua),
},
amazon: {
phone: match(amazonPhone, ua),
tablet: !match(amazonPhone, ua) && match(amazonTablet, ua),
device: match(amazonPhone, ua) || match(amazonTablet, ua),
},
android: {
phone:
(!match(windowsPhone, ua) && match(amazonPhone, ua)) ||
(!match(windowsPhone, ua) && match(androidPhone, ua)),
tablet:
!match(windowsPhone, ua) &&
!match(amazonPhone, ua) &&
!match(androidPhone, ua) &&
(match(amazonTablet, ua) || match(androidTablet, ua)),
device:
(!match(windowsPhone, ua) &&
(match(amazonPhone, ua) ||
match(amazonTablet, ua) ||
match(androidPhone, ua) ||
match(androidTablet, ua))) ||
match(/\bokhttp\b/i, ua),
},
windows: {
phone: match(windowsPhone, ua),
tablet: match(windowsTablet, ua),
device: match(windowsPhone, ua) || match(windowsTablet, ua),
},
other: {
blackberry: match(otherBlackberry, ua),
blackberry10: match(otherBlackberry10, ua),
opera: match(otherOpera, ua),
firefox: match(otherFirefox, ua),
chrome: match(otherChrome, ua),
device:
match(otherBlackberry, ua) ||
match(otherBlackberry10, ua) ||
match(otherOpera, ua) ||
match(otherFirefox, ua) ||
match(otherChrome, ua),
},
// Additional
any: null,
phone: null,
tablet: null,
};
result.any =
result.apple.device || result.android.device || result.windows.device || result.other.device;
// excludes 'other' devices and ipods, targeting touchscreen phones
result.phone = result.apple.phone || result.android.phone || result.windows.phone;
result.tablet = result.apple.tablet || result.android.tablet || result.windows.tablet;
return result;
}
const defaultResult = {
...isMobile(),
isMobile,
};
export default defaultResult;