From e6f3dd81ba4ad492158e278ecb0f9d9893bf2958 Mon Sep 17 00:00:00 2001 From: tamaina <tamaina@hotmail.co.jp> Date: Wed, 9 Aug 2023 09:08:47 +0900 Subject: [PATCH] =?UTF-8?q?fix(frontend):=20MkPopupMenu=E3=81=8C=E3=83=89?= =?UTF-8?q?=E3=83=AD=E3=83=AF=E3=83=BC=E3=81=A7=E5=AD=90=E3=83=A1=E3=83=8B?= =?UTF-8?q?=E3=83=A5=E3=83=BC=E3=81=AE=E5=87=BA=E7=8F=BE=E3=81=A8=E5=90=8C?= =?UTF-8?q?=E6=99=82=E3=81=ABpopup=E3=82=92resolve=E3=81=95=E3=81=9B?= =?UTF-8?q?=E3=82=8B=E3=81=AE=E3=82=92=E3=82=84=E3=82=81=E3=81=95=E3=81=9B?= =?UTF-8?q?=E3=82=8B=20(#11441)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(frontend): MkPopupMenuがドロワーで子メニューの出現と同時にpopupをresolveさせるのをやめさせる * fix * noCache * :v: * fix * ???? * a * a * :v: * fix emoji picker * ????? * close * 1 * fix2 * :v: * fix * :v: * :v: * :v: * preferClick * :v: * fix lint * a * rm nocache --- packages/frontend/src/components/MkMenu.vue | 64 +++++++++++-------- .../frontend/src/components/MkPopupMenu.vue | 46 ++++++++++++- .../frontend/src/scripts/get-note-menu.ts | 4 +- .../frontend/src/scripts/get-user-menu.ts | 4 +- packages/frontend/src/types/menu.ts | 2 +- 5 files changed, 86 insertions(+), 34 deletions(-) diff --git a/packages/frontend/src/components/MkMenu.vue b/packages/frontend/src/components/MkMenu.vue index 3d4e45b1f4..3b20856e12 100644 --- a/packages/frontend/src/components/MkMenu.vue +++ b/packages/frontend/src/components/MkMenu.vue @@ -39,11 +39,11 @@ SPDX-License-Identifier: AGPL-3.0-only <MkSwitchButton :class="$style.switchButton" :checked="item.ref" :disabled="item.disabled" @toggle="switchItem(item)" /> <span :class="$style.switchText">{{ item.text }}</span> </button> - <button v-else-if="item.type === 'parent'" role="menuitem" :tabindex="i" class="_button" :class="[$style.item, $style.parent, { [$style.childShowing]: childShowingItem === item }]" @mouseenter="showChildren(item, $event)"> + <div v-else-if="item.type === 'parent'" role="menuitem" :tabindex="i" :class="[$style.item, $style.parent, { [$style.childShowing]: childShowingItem === item }]" @mouseenter="preferClick ? null : showChildren(item, $event)" @click="!preferClick ? null : showChildren(item, $event)"> <i v-if="item.icon" class="ti-fw" :class="[$style.icon, item.icon]"></i> <span>{{ item.text }}</span> <span :class="$style.caret"><i class="ti ti-chevron-right ti-fw"></i></span> - </button> + </div> <button v-else :tabindex="i" class="_button" role="menuitem" :class="[$style.item, { [$style.danger]: item.danger, [$style.active]: item.active }]" :disabled="item.active" @click="clicked(item.action, $event)" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)"> <i v-if="item.icon" class="ti-fw" :class="[$style.icon, item.icon]"></i> <MkAvatar v-if="item.avatar" :user="item.avatar" :class="$style.avatar"/> @@ -56,19 +56,24 @@ SPDX-License-Identifier: AGPL-3.0-only </span> </div> <div v-if="childMenu"> - <XChild ref="child" :items="childMenu" :targetElement="childTarget" :rootElement="itemsEl" showing @actioned="childActioned"/> + <XChild ref="child" :items="childMenu" :targetElement="childTarget" :rootElement="itemsEl" showing @actioned="childActioned" @close="close(false)"/> </div> </div> </template> -<script lang="ts" setup> -import { defineAsyncComponent, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'; +<script lang="ts"> +import { Ref, defineAsyncComponent, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'; import { focusPrev, focusNext } from '@/scripts/focus'; import MkSwitchButton from '@/components/MkSwitch.button.vue'; -import { MenuItem, InnerMenuItem, OuterMenuItem, MenuPending, MenuAction, MenuSwitch, MenuParent } from '@/types/menu'; +import { MenuItem, InnerMenuItem, MenuPending, MenuAction, MenuSwitch, MenuParent } from '@/types/menu'; import * as os from '@/os'; import { i18n } from '@/i18n'; +import { isTouchUsing } from '@/scripts/touch'; +const childrenCache = new WeakMap<MenuParent, MenuItem[]>(); +</script> + +<script lang="ts" setup> const XChild = defineAsyncComponent(() => import('./MkMenu.child.vue')); const props = defineProps<{ @@ -82,6 +87,7 @@ const props = defineProps<{ const emit = defineEmits<{ (ev: 'close', actioned?: boolean): void; + (ev: 'hide'): void; }>(); let itemsEl = $shallowRef<HTMLDivElement>(); @@ -98,6 +104,8 @@ let keymap = $computed(() => ({ let childShowingItem = $ref<MenuItem | null>(); +let preferClick = isTouchUsing || props.asDrawer; + watch(() => props.items, () => { const items: (MenuItem | MenuPending)[] = [...props.items].filter(item => item !== undefined); @@ -117,7 +125,7 @@ watch(() => props.items, () => { immediate: true, }); -let childMenu = ref<MenuItem[] | null>(); +const childMenu = ref<MenuItem[] | null>(); let childTarget = $shallowRef<HTMLElement | null>(); function closeChild() { @@ -130,11 +138,11 @@ function childActioned() { close(true); } -function onGlobalMousedown(event: MouseEvent) { +const onGlobalMousedown = (event: MouseEvent) => { if (childTarget && (event.target === childTarget || childTarget.contains(event.target))) return; if (child && child.checkHit(event)) return; closeChild(); -} +}; let childCloseTimer: null | number = null; function onItemMouseEnter(item) { @@ -146,31 +154,30 @@ function onItemMouseLeave(item) { if (childCloseTimer) window.clearTimeout(childCloseTimer); } -let childrenCache = new WeakMap<MenuParent, OuterMenuItem[]>(); async function showChildren(item: MenuParent, ev: MouseEvent) { - const children = ref<OuterMenuItem[]>([]); - if (childrenCache.has(item)) { - children.value = childrenCache.get(item)!; - } else { - if (typeof item.children === 'function') { - children.value = [{ - type: 'pending', - }]; - Promise.resolve(item.children()).then(x => { - children.value = x; - childrenCache.set(item, x); - }); + const children = await (async () => { + if (childrenCache.has(item)) { + return childrenCache.get(item)!; } else { - children.value = item.children; + if (typeof item.children === 'function') { + return Promise.resolve(item.children()); + } else { + return item.children; + } } - } + })(); + + childrenCache.set(item, children); if (props.asDrawer) { - os.popupMenu(children, ev.currentTarget ?? ev.target); - close(); + os.popupMenu(children, ev.currentTarget ?? ev.target).finally(() => { + emit('close'); + }); + emit('hide'); } else { childTarget = ev.currentTarget ?? ev.target; - childMenu = children; + // これでもリアクティビティは保たれる + childMenu.value = children; childShowingItem = item; } } @@ -200,7 +207,7 @@ function switchItem(item: MenuSwitch & { ref: any }) { onMounted(() => { if (props.viaKeyboard) { nextTick(() => { - focusNext(itemsEl.children[0], true, false); + if (itemsEl) focusNext(itemsEl.children[0], true, false); }); } @@ -348,6 +355,7 @@ onBeforeUnmount(() => { } &.parent { + pointer-events: auto; display: flex; align-items: center; cursor: default; diff --git a/packages/frontend/src/components/MkPopupMenu.vue b/packages/frontend/src/components/MkPopupMenu.vue index 6e7656f745..ee7dbecf05 100644 --- a/packages/frontend/src/components/MkPopupMenu.vue +++ b/packages/frontend/src/components/MkPopupMenu.vue @@ -4,13 +4,13 @@ SPDX-License-Identifier: AGPL-3.0-only --> <template> -<MkModal ref="modal" v-slot="{ type, maxHeight }" :zPriority="'high'" :src="src" :transparentBg="true" @click="modal.close()" @close="emit('closing')" @closed="emit('closed')"> - <MkMenu :items="items" :align="align" :width="width" :max-height="maxHeight" :asDrawer="type === 'drawer'" :class="{ [$style.drawer]: type === 'drawer' }" @close="modal.close()"/> +<MkModal ref="modal" v-slot="{ type, maxHeight }" :manualShowing="manualShowing" :zPriority="'high'" :src="src" :transparentBg="true" @click="click" @close="onModalClose" @closed="onModalClosed"> + <MkMenu :items="items" :align="align" :width="width" :max-height="maxHeight" :asDrawer="type === 'drawer'" :class="{ [$style.drawer]: type === 'drawer' }" @close="onMenuClose" @hide="hide"/> </MkModal> </template> <script lang="ts" setup> -import { } from 'vue'; +import { ref } from 'vue'; import MkModal from './MkModal.vue'; import MkMenu from './MkMenu.vue'; import { MenuItem } from '@/types/menu'; @@ -29,6 +29,46 @@ const emit = defineEmits<{ }>(); let modal = $shallowRef<InstanceType<typeof MkModal>>(); +const manualShowing = ref(true); +const hiding = ref(false); + +function click() { + close(); +} + +function onModalClose() { + emit('closing'); +} + +function onMenuClose() { + close(); + if (hiding.value) { + // hidingであればclosedを発火 + emit('closed'); + } +} + +function onModalClosed() { + if (!hiding.value) { + // hidingでなければclosedを発火 + emit('closed'); + } +} + +function hide() { + manualShowing.value = false; + hiding.value = true; + + // closeは呼ぶ必要がある + modal?.close(); +} + +function close() { + manualShowing.value = false; + + // closeは呼ぶ必要がある + modal?.close(); +} </script> <style lang="scss" module> diff --git a/packages/frontend/src/scripts/get-note-menu.ts b/packages/frontend/src/scripts/get-note-menu.ts index 20cea45ee3..8e29fc0c9b 100644 --- a/packages/frontend/src/scripts/get-note-menu.ts +++ b/packages/frontend/src/scripts/get-note-menu.ts @@ -418,7 +418,9 @@ export function getNoteMenu(props: { const cleanup = () => { if (_DEV_) console.log('note menu cleanup', cleanups); - cleanups.forEach(cleanup => cleanup()); + for (const cl of cleanups) { + cl(); + } }; return { diff --git a/packages/frontend/src/scripts/get-user-menu.ts b/packages/frontend/src/scripts/get-user-menu.ts index 445560b0c3..69a6f75c12 100644 --- a/packages/frontend/src/scripts/get-user-menu.ts +++ b/packages/frontend/src/scripts/get-user-menu.ts @@ -330,7 +330,9 @@ export function getUserMenu(user: misskey.entities.UserDetailed, router: Router const cleanup = () => { if (_DEV_) console.log('user menu cleanup', cleanups); - cleanups.forEach(cleanup => cleanup()); + for (const cl of cleanups) { + cl(); + } }; return { diff --git a/packages/frontend/src/types/menu.ts b/packages/frontend/src/types/menu.ts index b2ba6290c4..66061fcd70 100644 --- a/packages/frontend/src/types/menu.ts +++ b/packages/frontend/src/types/menu.ts @@ -16,7 +16,7 @@ export type MenuA = { type: 'a', href: string, target?: string, download?: strin export type MenuUser = { type: 'user', user: Misskey.entities.User, active?: boolean, indicate?: boolean, action: MenuAction }; export type MenuSwitch = { type: 'switch', ref: Ref<boolean>, text: string, disabled?: boolean }; export type MenuButton = { type?: 'button', text: string, icon?: string, indicate?: boolean, danger?: boolean, active?: boolean, avatar?: Misskey.entities.User; action: MenuAction }; -export type MenuParent = { type: 'parent', text: string, icon?: string, children: OuterMenuItem[] | (() => Promise<OuterMenuItem[]> | OuterMenuItem[]) }; +export type MenuParent = { type: 'parent', text: string, icon?: string, children: MenuItem[] | (() => Promise<MenuItem[]> | MenuItem[]) }; export type MenuPending = { type: 'pending' };