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 classNames from '../_util/classNames';
|
||||
import isMobile from '../vc-menu/utils/isMobile';
|
||||
import isMobile from '../_util/isMobile';
|
||||
import Input from './Input';
|
||||
import LoadingOutlined from '@ant-design/icons-vue/LoadingOutlined';
|
||||
import SearchOutlined from '@ant-design/icons-vue/SearchOutlined';
|
||||
|
|
|
@ -14,6 +14,11 @@ Menu.install = function(app: App) {
|
|||
return app;
|
||||
};
|
||||
|
||||
Menu.Item = MenuItem;
|
||||
Menu.Divider = Divider;
|
||||
Menu.SubMenu = SubMenu;
|
||||
Menu.ItemGroup = ItemGroup;
|
||||
|
||||
export default Menu as typeof Menu &
|
||||
Plugin & {
|
||||
readonly Item: typeof MenuItem;
|
||||
|
|
|
@ -146,10 +146,10 @@ export default defineComponent({
|
|||
...info,
|
||||
selectedKeys: newSelectedKeys,
|
||||
};
|
||||
if (!('selectedKeys' in props)) {
|
||||
mergedSelectedKeys.value = newSelectedKeys;
|
||||
}
|
||||
if (!shallowEqual(newSelectedKeys, mergedSelectedKeys.value)) {
|
||||
if (!('selectedKeys' in props)) {
|
||||
mergedSelectedKeys.value = newSelectedKeys;
|
||||
}
|
||||
emit('update:selectedKeys', newSelectedKeys);
|
||||
if (exist && props.multiple) {
|
||||
emit('deselect', selectInfo);
|
||||
|
@ -266,6 +266,10 @@ export default defineComponent({
|
|||
triggerSelection(info);
|
||||
};
|
||||
|
||||
const onInternalKeyDown = (e: KeyboardEvent) => {
|
||||
console.log('onInternalKeyDown', e);
|
||||
};
|
||||
|
||||
const onInternalOpenChange = (eventKey: Key, open: boolean) => {
|
||||
const { key, childrenEventKeys } = store[eventKey];
|
||||
let newOpenKeys = mergedOpenKeys.value.filter(k => k !== key);
|
||||
|
@ -322,7 +326,11 @@ export default defineComponent({
|
|||
isRootMenu: true,
|
||||
});
|
||||
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 Tooltip from '../../tooltip';
|
||||
import { MenuInfo } from './interface';
|
||||
import KeyCode from 'ant-design-vue/es/_util/KeyCode';
|
||||
|
||||
let indexGuid = 0;
|
||||
|
||||
|
@ -18,7 +19,7 @@ export default defineComponent({
|
|||
title: { type: [String, Boolean], default: undefined },
|
||||
icon: PropTypes.VNodeChild,
|
||||
},
|
||||
emits: ['mouseenter', 'mouseleave', 'click'],
|
||||
emits: ['mouseenter', 'mouseleave', 'click', 'keydown', 'focus'],
|
||||
slots: ['icon'],
|
||||
inheritAttrs: false,
|
||||
setup(props, { slots, emit, attrs }) {
|
||||
|
@ -80,7 +81,7 @@ export default defineComponent({
|
|||
};
|
||||
});
|
||||
|
||||
const getEventInfo = (e: MouseEvent): MenuInfo => {
|
||||
const getEventInfo = (e: MouseEvent | KeyboardEvent): MenuInfo => {
|
||||
return {
|
||||
key: key,
|
||||
eventKey: eventKey,
|
||||
|
@ -104,7 +105,6 @@ export default defineComponent({
|
|||
const onMouseEnter = (event: MouseEvent) => {
|
||||
if (!mergedDisabled.value) {
|
||||
changeActiveKeys(keysPath.value);
|
||||
console.log('item mouseenter', keysPath.value);
|
||||
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) => {
|
||||
// inline-collapsed.md demo 依赖 span 来隐藏文字,有 icon 属性,则内部包裹一个 span
|
||||
// ref: https://github.com/ant-design/ant-design/pull/23456
|
||||
|
@ -182,6 +203,8 @@ export default defineComponent({
|
|||
onMouseenter={onMouseEnter}
|
||||
onMouseleave={onMouseLeave}
|
||||
onClick={onInternalClick}
|
||||
onKeydown={onInternalKeyDown}
|
||||
onFocus={onInternalFocus}
|
||||
title={typeof title === 'string' ? title : undefined}
|
||||
>
|
||||
{cloneElement(icon, {
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import Menu, { MenuItem } from '../../vc-menu';
|
||||
import Menu from '../../menu';
|
||||
import PropTypes from '../../_util/vue-types';
|
||||
import { OptionProps } from './Option';
|
||||
import { inject } from 'vue';
|
||||
|
||||
const MenuItem = Menu.Item;
|
||||
|
||||
function noop() {}
|
||||
export default {
|
||||
name: 'DropdownMenu',
|
||||
|
|
124
examples/App.vue
124
examples/App.vue
|
@ -1,107 +1,39 @@
|
|||
<template>
|
||||
<div style="width: 256px">
|
||||
<a-button type="primary" style="margin-bottom: 16px" @click="toggleCollapsed">
|
||||
<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>
|
||||
<demo />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent, reactive, toRefs, watch } from 'vue';
|
||||
import {
|
||||
MenuFoldOutlined,
|
||||
MenuUnfoldOutlined,
|
||||
PieChartOutlined,
|
||||
MailOutlined,
|
||||
DesktopOutlined,
|
||||
InboxOutlined,
|
||||
AppstoreOutlined,
|
||||
} from '@ant-design/icons-vue';
|
||||
<script>
|
||||
import { defineComponent } from 'vue';
|
||||
import demo from '../v2-doc/src/docs/mentions/demo/index.vue';
|
||||
// import Affix from '../components/affix';
|
||||
export default defineComponent({
|
||||
components: {
|
||||
MenuFoldOutlined,
|
||||
MenuUnfoldOutlined,
|
||||
PieChartOutlined,
|
||||
MailOutlined,
|
||||
DesktopOutlined,
|
||||
InboxOutlined,
|
||||
AppstoreOutlined,
|
||||
demo,
|
||||
// Affix,
|
||||
},
|
||||
setup() {
|
||||
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;
|
||||
};
|
||||
|
||||
data() {
|
||||
return {
|
||||
...toRefs(state),
|
||||
toggleCollapsed,
|
||||
visible: false,
|
||||
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>
|
||||
|
|
Loading…
Reference in New Issue