fix(frontend): MkPopupMenuがドロワーで子メニューの出現と同時にpopupをresolveさせるのをやめさせる (#11441)
* fix(frontend): MkPopupMenuがドロワーで子メニューの出現と同時にpopupをresolveさせるのをやめさせる * fix * noCache * ✌️ * fix * ???? * a * a * ✌️ * fix emoji picker * ????? * close * 1 * fix2 * ✌️ * fix * ✌️ * ✌️ * ✌️ * preferClick * ✌️ * fix lint * a * rm nocache
This commit is contained in:
		| @@ -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; | ||||
|   | ||||
| @@ -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> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 tamaina
					tamaina