fix: menu
parent
67952ab5a6
commit
7b494fd445
|
@ -0,0 +1,110 @@
|
||||||
|
// 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;
|
|
@ -1,6 +1,6 @@
|
||||||
import { defineComponent, inject } from 'vue';
|
import { defineComponent, inject } from 'vue';
|
||||||
import classNames from '../_util/classNames';
|
import classNames from '../_util/classNames';
|
||||||
import isMobile from '../vc-menu/utils/isMobile';
|
import isMobile from '../_util/isMobile';
|
||||||
import Input from './Input';
|
import Input from './Input';
|
||||||
import LoadingOutlined from '@ant-design/icons-vue/LoadingOutlined';
|
import LoadingOutlined from '@ant-design/icons-vue/LoadingOutlined';
|
||||||
import SearchOutlined from '@ant-design/icons-vue/SearchOutlined';
|
import SearchOutlined from '@ant-design/icons-vue/SearchOutlined';
|
||||||
|
|
|
@ -14,6 +14,11 @@ Menu.install = function(app: App) {
|
||||||
return app;
|
return app;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Menu.Item = MenuItem;
|
||||||
|
Menu.Divider = Divider;
|
||||||
|
Menu.SubMenu = SubMenu;
|
||||||
|
Menu.ItemGroup = ItemGroup;
|
||||||
|
|
||||||
export default Menu as typeof Menu &
|
export default Menu as typeof Menu &
|
||||||
Plugin & {
|
Plugin & {
|
||||||
readonly Item: typeof MenuItem;
|
readonly Item: typeof MenuItem;
|
||||||
|
|
|
@ -146,10 +146,10 @@ export default defineComponent({
|
||||||
...info,
|
...info,
|
||||||
selectedKeys: newSelectedKeys,
|
selectedKeys: newSelectedKeys,
|
||||||
};
|
};
|
||||||
|
if (!shallowEqual(newSelectedKeys, mergedSelectedKeys.value)) {
|
||||||
if (!('selectedKeys' in props)) {
|
if (!('selectedKeys' in props)) {
|
||||||
mergedSelectedKeys.value = newSelectedKeys;
|
mergedSelectedKeys.value = newSelectedKeys;
|
||||||
}
|
}
|
||||||
if (!shallowEqual(newSelectedKeys, mergedSelectedKeys.value)) {
|
|
||||||
emit('update:selectedKeys', newSelectedKeys);
|
emit('update:selectedKeys', newSelectedKeys);
|
||||||
if (exist && props.multiple) {
|
if (exist && props.multiple) {
|
||||||
emit('deselect', selectInfo);
|
emit('deselect', selectInfo);
|
||||||
|
@ -266,6 +266,10 @@ export default defineComponent({
|
||||||
triggerSelection(info);
|
triggerSelection(info);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onInternalKeyDown = (e: KeyboardEvent) => {
|
||||||
|
console.log('onInternalKeyDown', e);
|
||||||
|
};
|
||||||
|
|
||||||
const onInternalOpenChange = (eventKey: Key, open: boolean) => {
|
const onInternalOpenChange = (eventKey: Key, open: boolean) => {
|
||||||
const { key, childrenEventKeys } = store[eventKey];
|
const { key, childrenEventKeys } = store[eventKey];
|
||||||
let newOpenKeys = mergedOpenKeys.value.filter(k => k !== key);
|
let newOpenKeys = mergedOpenKeys.value.filter(k => k !== key);
|
||||||
|
@ -322,7 +326,11 @@ export default defineComponent({
|
||||||
isRootMenu: true,
|
isRootMenu: true,
|
||||||
});
|
});
|
||||||
return () => {
|
return () => {
|
||||||
return <ul class={className.value}>{slots.default?.()}</ul>;
|
return (
|
||||||
|
<ul class={className.value} tabindex="0" onKeydown={onInternalKeyDown}>
|
||||||
|
{slots.default?.()}
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { useInjectFirstLevel, useInjectMenu } from './hooks/useMenuContext';
|
||||||
import { cloneElement } from '../../_util/vnode';
|
import { cloneElement } from '../../_util/vnode';
|
||||||
import Tooltip from '../../tooltip';
|
import Tooltip from '../../tooltip';
|
||||||
import { MenuInfo } from './interface';
|
import { MenuInfo } from './interface';
|
||||||
|
import KeyCode from 'ant-design-vue/es/_util/KeyCode';
|
||||||
|
|
||||||
let indexGuid = 0;
|
let indexGuid = 0;
|
||||||
|
|
||||||
|
@ -18,7 +19,7 @@ export default defineComponent({
|
||||||
title: { type: [String, Boolean], default: undefined },
|
title: { type: [String, Boolean], default: undefined },
|
||||||
icon: PropTypes.VNodeChild,
|
icon: PropTypes.VNodeChild,
|
||||||
},
|
},
|
||||||
emits: ['mouseenter', 'mouseleave', 'click'],
|
emits: ['mouseenter', 'mouseleave', 'click', 'keydown', 'focus'],
|
||||||
slots: ['icon'],
|
slots: ['icon'],
|
||||||
inheritAttrs: false,
|
inheritAttrs: false,
|
||||||
setup(props, { slots, emit, attrs }) {
|
setup(props, { slots, emit, attrs }) {
|
||||||
|
@ -80,7 +81,7 @@ export default defineComponent({
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const getEventInfo = (e: MouseEvent): MenuInfo => {
|
const getEventInfo = (e: MouseEvent | KeyboardEvent): MenuInfo => {
|
||||||
return {
|
return {
|
||||||
key: key,
|
key: key,
|
||||||
eventKey: eventKey,
|
eventKey: eventKey,
|
||||||
|
@ -104,7 +105,6 @@ export default defineComponent({
|
||||||
const onMouseEnter = (event: MouseEvent) => {
|
const onMouseEnter = (event: MouseEvent) => {
|
||||||
if (!mergedDisabled.value) {
|
if (!mergedDisabled.value) {
|
||||||
changeActiveKeys(keysPath.value);
|
changeActiveKeys(keysPath.value);
|
||||||
console.log('item mouseenter', keysPath.value);
|
|
||||||
emit('mouseenter', event);
|
emit('mouseenter', event);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -115,6 +115,27 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onInternalKeyDown = (e: KeyboardEvent) => {
|
||||||
|
emit('keydown', e);
|
||||||
|
|
||||||
|
if (e.which === KeyCode.ENTER) {
|
||||||
|
const info = getEventInfo(e);
|
||||||
|
|
||||||
|
// Legacy. Key will also trigger click event
|
||||||
|
emit('click', e);
|
||||||
|
onItemClick(info);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used for accessibility. Helper will focus element without key board.
|
||||||
|
* We should manually trigger an active
|
||||||
|
*/
|
||||||
|
const onInternalFocus = (e: FocusEvent) => {
|
||||||
|
changeActiveKeys(keysPath.value);
|
||||||
|
emit('focus', e);
|
||||||
|
};
|
||||||
|
|
||||||
const renderItemChildren = (icon: any, children: any) => {
|
const renderItemChildren = (icon: any, children: any) => {
|
||||||
// inline-collapsed.md demo 依赖 span 来隐藏文字,有 icon 属性,则内部包裹一个 span
|
// inline-collapsed.md demo 依赖 span 来隐藏文字,有 icon 属性,则内部包裹一个 span
|
||||||
// ref: https://github.com/ant-design/ant-design/pull/23456
|
// ref: https://github.com/ant-design/ant-design/pull/23456
|
||||||
|
@ -182,6 +203,8 @@ export default defineComponent({
|
||||||
onMouseenter={onMouseEnter}
|
onMouseenter={onMouseEnter}
|
||||||
onMouseleave={onMouseLeave}
|
onMouseleave={onMouseLeave}
|
||||||
onClick={onInternalClick}
|
onClick={onInternalClick}
|
||||||
|
onKeydown={onInternalKeyDown}
|
||||||
|
onFocus={onInternalFocus}
|
||||||
title={typeof title === 'string' ? title : undefined}
|
title={typeof title === 'string' ? title : undefined}
|
||||||
>
|
>
|
||||||
{cloneElement(icon, {
|
{cloneElement(icon, {
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
import Menu, { MenuItem } from '../../vc-menu';
|
import Menu from '../../menu';
|
||||||
import PropTypes from '../../_util/vue-types';
|
import PropTypes from '../../_util/vue-types';
|
||||||
import { OptionProps } from './Option';
|
import { OptionProps } from './Option';
|
||||||
import { inject } from 'vue';
|
import { inject } from 'vue';
|
||||||
|
|
||||||
|
const MenuItem = Menu.Item;
|
||||||
|
|
||||||
function noop() {}
|
function noop() {}
|
||||||
export default {
|
export default {
|
||||||
name: 'DropdownMenu',
|
name: 'DropdownMenu',
|
||||||
|
|
124
examples/App.vue
124
examples/App.vue
|
@ -1,107 +1,39 @@
|
||||||
<template>
|
<template>
|
||||||
<div style="width: 256px">
|
<div>
|
||||||
<a-button type="primary" style="margin-bottom: 16px" @click="toggleCollapsed">
|
<demo />
|
||||||
<MenuUnfoldOutlined v-if="collapsed" />
|
|
||||||
<MenuFoldOutlined v-else />
|
|
||||||
</a-button>
|
|
||||||
<a-menu
|
|
||||||
v-model:openKeys="openKeys"
|
|
||||||
v-model:selectedKeys="selectedKeys"
|
|
||||||
mode="inline"
|
|
||||||
theme="dark"
|
|
||||||
:inline-collapsed="collapsed"
|
|
||||||
>
|
|
||||||
<!-- <a-menu-item key="1">
|
|
||||||
<template #icon>
|
|
||||||
<PieChartOutlined />
|
|
||||||
</template>
|
|
||||||
<span>Option 1</span>
|
|
||||||
</a-menu-item>
|
|
||||||
<a-menu-item key="2">
|
|
||||||
<template #icon>
|
|
||||||
<DesktopOutlined />
|
|
||||||
</template>
|
|
||||||
<span>Option 2</span>
|
|
||||||
</a-menu-item>
|
|
||||||
<a-menu-item key="3">
|
|
||||||
<template #icon>
|
|
||||||
<InboxOutlined />
|
|
||||||
</template>
|
|
||||||
<span>Option 3</span>
|
|
||||||
</a-menu-item>
|
|
||||||
<a-sub-menu key="sub1">
|
|
||||||
<template #title>
|
|
||||||
<span>
|
|
||||||
<MailOutlined />
|
|
||||||
<span>Navigation One</span>
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
<a-menu-item key="5">Option 5</a-menu-item>
|
|
||||||
<a-menu-item key="6">Option 6</a-menu-item>
|
|
||||||
<a-menu-item key="7">Option 7</a-menu-item>
|
|
||||||
<a-menu-item key="8">Option 8</a-menu-item>
|
|
||||||
</a-sub-menu> -->
|
|
||||||
<a-sub-menu key="sub2">
|
|
||||||
<template #title>
|
|
||||||
<span>
|
|
||||||
<AppstoreOutlined />
|
|
||||||
<span>Navigation Two</span>
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
<a-menu-item key="9">Option 9</a-menu-item>
|
|
||||||
<a-menu-item key="10">Option 10</a-menu-item>
|
|
||||||
<a-sub-menu key="sub3" title="Submenu">
|
|
||||||
<a-menu-item key="11">Option 11</a-menu-item>
|
|
||||||
<a-menu-item key="12">Option 12</a-menu-item>
|
|
||||||
</a-sub-menu>
|
|
||||||
</a-sub-menu>
|
|
||||||
</a-menu>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script>
|
||||||
import { defineComponent, reactive, toRefs, watch } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import {
|
import demo from '../v2-doc/src/docs/mentions/demo/index.vue';
|
||||||
MenuFoldOutlined,
|
// import Affix from '../components/affix';
|
||||||
MenuUnfoldOutlined,
|
|
||||||
PieChartOutlined,
|
|
||||||
MailOutlined,
|
|
||||||
DesktopOutlined,
|
|
||||||
InboxOutlined,
|
|
||||||
AppstoreOutlined,
|
|
||||||
} from '@ant-design/icons-vue';
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
MenuFoldOutlined,
|
demo,
|
||||||
MenuUnfoldOutlined,
|
// Affix,
|
||||||
PieChartOutlined,
|
|
||||||
MailOutlined,
|
|
||||||
DesktopOutlined,
|
|
||||||
InboxOutlined,
|
|
||||||
AppstoreOutlined,
|
|
||||||
},
|
},
|
||||||
setup() {
|
data() {
|
||||||
const state = reactive({
|
|
||||||
collapsed: false,
|
|
||||||
selectedKeys: ['1'],
|
|
||||||
openKeys: ['sub1'],
|
|
||||||
preOpenKeys: ['sub1'],
|
|
||||||
});
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => state.openKeys,
|
|
||||||
(val, oldVal) => {
|
|
||||||
state.preOpenKeys = oldVal;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
const toggleCollapsed = () => {
|
|
||||||
state.collapsed = !state.collapsed;
|
|
||||||
// state.openKeys = state.collapsed ? [] : state.preOpenKeys;
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...toRefs(state),
|
visible: false,
|
||||||
toggleCollapsed,
|
pStyle: {
|
||||||
|
fontSize: '16px',
|
||||||
|
color: 'rgba(0,0,0,0.85)',
|
||||||
|
lineHeight: '24px',
|
||||||
|
display: 'block',
|
||||||
|
marginBottom: '16px',
|
||||||
|
},
|
||||||
|
pStyle2: {
|
||||||
|
marginBottom: '24px',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
methods: {
|
||||||
|
showDrawer() {
|
||||||
|
this.visible = true;
|
||||||
|
},
|
||||||
|
onClose() {
|
||||||
|
this.visible = false;
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
Loading…
Reference in New Issue