mirror of
https://github.com/misskey-dev/misskey.git
synced 2026-05-22 05:45:32 +02:00
enhance(frontend): improve nested popup menu ux (#17187)
* wip
* Update MkMenu.vue
* wip
* wip
* Update MkMenu.vue
* wip
* Update MkMenu.vue
* Update MkMenu.vue
* Update MkMenu.vue
* Update MkMenu.vue
* Update MkMenu.vue
* Update MkMenu.vue
* Update MkMenu.vue
* Update MkMenu.vue
* 💢
* Update MkMenu.vue
* Update MkMenu.vue
* Update MkMenu.vue
This commit is contained in:
@@ -5,7 +5,15 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div ref="el" :class="$style.root">
|
<div ref="el" :class="$style.root">
|
||||||
<MkMenu :items="items" :align="align" :width="width" :asDrawer="false" @close="onChildClosed"/>
|
<MkMenu
|
||||||
|
:items="items"
|
||||||
|
:align="align"
|
||||||
|
:width="width"
|
||||||
|
:asDrawer="false"
|
||||||
|
:debugDisablePredictionCone="debugDisablePredictionCone"
|
||||||
|
:debugShowPredictionCone="debugShowPredictionCone"
|
||||||
|
@close="onChildClosed"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -19,6 +27,8 @@ const props = defineProps<{
|
|||||||
anchorElement: HTMLElement;
|
anchorElement: HTMLElement;
|
||||||
rootElement: HTMLElement;
|
rootElement: HTMLElement;
|
||||||
width?: number;
|
width?: number;
|
||||||
|
debugDisablePredictionCone?: boolean;
|
||||||
|
debugShowPredictionCone?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
@@ -80,6 +90,7 @@ onUnmounted(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
|
rootElement: el,
|
||||||
checkHit: (ev: MouseEvent) => {
|
checkHit: (ev: MouseEvent) => {
|
||||||
return (ev.target === el.value || el.value?.contains(ev.target as Node));
|
return (ev.target === el.value || el.value?.contains(ev.target as Node));
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
}"
|
}"
|
||||||
@keydown.stop="() => {}"
|
@keydown.stop="() => {}"
|
||||||
@contextmenu.self.prevent="() => {}"
|
@contextmenu.self.prevent="() => {}"
|
||||||
|
@mousemove.passive="onMouseMove"
|
||||||
|
@mouseleave.passive="onMouseLeave"
|
||||||
>
|
>
|
||||||
<template v-for="item in (items2 ?? [])">
|
<template v-for="item in (items2 ?? [])">
|
||||||
<div v-if="item.type === 'divider'" role="separator" tabindex="-1" :class="$style.divider"></div>
|
<div v-if="item.type === 'divider'" role="separator" tabindex="-1" :class="$style.divider"></div>
|
||||||
@@ -169,6 +171,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
tabindex="0"
|
tabindex="0"
|
||||||
:class="['_button', $style.item, $style.parent, { [$style.active]: childShowingItem === item }]"
|
:class="['_button', $style.item, $style.parent, { [$style.active]: childShowingItem === item }]"
|
||||||
@mouseenter.prevent="preferClick ? null : showChildren(item, $event)"
|
@mouseenter.prevent="preferClick ? null : showChildren(item, $event)"
|
||||||
|
@mousemove="parentMouseMove"
|
||||||
@keydown.enter.prevent="preferClick ? null : showChildren(item, $event)"
|
@keydown.enter.prevent="preferClick ? null : showChildren(item, $event)"
|
||||||
@click.prevent="!preferClick ? null : showChildren(item, $event)"
|
@click.prevent="!preferClick ? null : showChildren(item, $event)"
|
||||||
>
|
>
|
||||||
@@ -206,15 +209,30 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<span v-if="items2 == null || items2.length === 0" tabindex="-1" :class="[$style.none, $style.item]">
|
<span v-if="items2 == null || items2.length === 0" tabindex="-1" :class="[$style.none, $style.item]">
|
||||||
<span>{{ i18n.ts.none }}</span>
|
<span>{{ i18n.ts.none }}</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
<div
|
||||||
|
:class="[$style.guard, { [$style.showGuard]: debugShowPredictionCone }]"
|
||||||
|
:style="{ clipPath: guardPolygon, top: guard.top + 'px' }"
|
||||||
|
@mousemove="guardMouseMove"
|
||||||
|
></div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="childMenu">
|
|
||||||
<XChild ref="child" :items="childMenu" :anchorElement="childTarget!" :rootElement="itemsEl!" @actioned="childActioned" @closed="closeChild"/>
|
<XChild
|
||||||
</div>
|
v-if="childMenu" :key="childMenuKey"
|
||||||
|
ref="child"
|
||||||
|
:items="childMenu"
|
||||||
|
:anchorElement="childTarget!"
|
||||||
|
:rootElement="itemsEl!"
|
||||||
|
:debugDisablePredictionCone="props.debugDisablePredictionCone"
|
||||||
|
:debugShowPredictionCone="props.debugShowPredictionCone"
|
||||||
|
@actioned="childActioned"
|
||||||
|
@closed="closeChild"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineAsyncComponent, inject, nextTick, onBeforeUnmount, onMounted, ref, useTemplateRef, unref, watch, shallowRef } from 'vue';
|
import { computed, defineAsyncComponent, inject, nextTick, onBeforeUnmount, onMounted, ref, useTemplateRef, unref, watch, shallowRef, reactive } from 'vue';
|
||||||
import type { MenuItem, InnerMenuItem, MenuPending, MenuAction, MenuSwitch, MenuRadio, MenuRadioOption, MenuParent } from '@/types/menu.js';
|
import type { MenuItem, InnerMenuItem, MenuPending, MenuAction, MenuSwitch, MenuRadio, MenuRadioOption, MenuParent } from '@/types/menu.js';
|
||||||
import type { Keymap } from '@/utility/hotkey.js';
|
import type { Keymap } from '@/utility/hotkey.js';
|
||||||
import MkSwitchButton from '@/components/MkSwitch.button.vue';
|
import MkSwitchButton from '@/components/MkSwitch.button.vue';
|
||||||
@@ -236,6 +254,8 @@ const props = defineProps<{
|
|||||||
align?: 'center' | string;
|
align?: 'center' | string;
|
||||||
width?: number;
|
width?: number;
|
||||||
maxHeight?: number;
|
maxHeight?: number;
|
||||||
|
debugDisablePredictionCone?: boolean;
|
||||||
|
debugShowPredictionCone?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
@@ -292,6 +312,7 @@ watch(() => props.items, () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const childMenu = ref<MenuItem[] | null>();
|
const childMenu = ref<MenuItem[] | null>();
|
||||||
|
const childMenuKey = ref(0);
|
||||||
const childTarget = shallowRef<HTMLElement>();
|
const childTarget = shallowRef<HTMLElement>();
|
||||||
|
|
||||||
function closeChild() {
|
function closeChild() {
|
||||||
@@ -348,6 +369,7 @@ async function showRadioOptions(item: MenuRadio, ev: MouseEvent | PointerEvent |
|
|||||||
} else {
|
} else {
|
||||||
childTarget.value = (ev.currentTarget ?? ev.target) as HTMLElement;
|
childTarget.value = (ev.currentTarget ?? ev.target) as HTMLElement;
|
||||||
childMenu.value = children;
|
childMenu.value = children;
|
||||||
|
childMenuKey.value++;
|
||||||
childShowingItem.value = item;
|
childShowingItem.value = item;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -378,6 +400,7 @@ async function showChildren(item: MenuParent, ev: MouseEvent | PointerEvent | Ke
|
|||||||
childTarget.value = (ev.currentTarget ?? ev.target) as HTMLElement;
|
childTarget.value = (ev.currentTarget ?? ev.target) as HTMLElement;
|
||||||
// これでもリアクティビティは保たれる
|
// これでもリアクティビティは保たれる
|
||||||
childMenu.value = children;
|
childMenu.value = children;
|
||||||
|
childMenuKey.value++;
|
||||||
childShowingItem.value = item;
|
childShowingItem.value = item;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -472,6 +495,67 @@ onMounted(() => {
|
|||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
disposeHandlers();
|
disposeHandlers();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const guard = reactive({
|
||||||
|
enabled: false,
|
||||||
|
top: 0,
|
||||||
|
cursorSideX: 0,
|
||||||
|
cursorSideY: 0,
|
||||||
|
childSideTopY: 0,
|
||||||
|
childSideBottomY: 0,
|
||||||
|
direction: 'toRight',
|
||||||
|
});
|
||||||
|
|
||||||
|
const guardPolygon = computed(() =>
|
||||||
|
guard.enabled
|
||||||
|
? guard.direction === 'toRight'
|
||||||
|
? `polygon(${guard.cursorSideX}px ${guard.cursorSideY}px, 101% ${guard.childSideTopY}px, 101% ${guard.childSideBottomY}px)` // ぴったり端に100%で覆ってもなぜか端でカーソルのイベントが後ろに貫通するので1%だけ伸ばす
|
||||||
|
: `polygon(0% ${guard.childSideTopY}px, 0% ${guard.childSideBottomY}px, ${guard.cursorSideX}px ${guard.cursorSideY}px)`
|
||||||
|
: 'polygon(0 0, 0 0, 0 0)',
|
||||||
|
);
|
||||||
|
|
||||||
|
function parentMouseMove(ev: MouseEvent) {
|
||||||
|
if (props.debugDisablePredictionCone) return;
|
||||||
|
if (isTouchUsing) return;
|
||||||
|
if (child.value == null || child.value.rootElement == null) return;
|
||||||
|
|
||||||
|
ev.stopPropagation();
|
||||||
|
|
||||||
|
const itemBounding = (ev.currentTarget as HTMLElement).getBoundingClientRect();
|
||||||
|
const rootBounding = itemsEl.value!.getBoundingClientRect();
|
||||||
|
const childBounding = child.value.rootElement.getBoundingClientRect();
|
||||||
|
const isChildRight = childBounding.left > rootBounding.left;
|
||||||
|
|
||||||
|
const CURSOR_SIDE_X_PADDING = 3; // (px)
|
||||||
|
const CHILD_SIDE_Y_PADDING_BASE = 70; // (px)
|
||||||
|
const CHILD_SIDE_Y_PADDING_EXTEND = 30; // (px)
|
||||||
|
const SCALE_FACTOR_COMPUTE_DISTANCE = 300; // コーンの広さが最大になる距離(px)
|
||||||
|
const localMouseX = ev.clientX - itemBounding.left;
|
||||||
|
const localMouseY = ev.clientY - rootBounding.top;
|
||||||
|
const scaleFactor = isChildRight ? Math.min((itemBounding.width - localMouseX), SCALE_FACTOR_COMPUTE_DISTANCE) / SCALE_FACTOR_COMPUTE_DISTANCE : Math.min(localMouseX, SCALE_FACTOR_COMPUTE_DISTANCE) / SCALE_FACTOR_COMPUTE_DISTANCE;
|
||||||
|
const cursorSideXPadding = isChildRight ? CURSOR_SIDE_X_PADDING : -CURSOR_SIDE_X_PADDING;
|
||||||
|
const childSideYPadding = CHILD_SIDE_Y_PADDING_BASE + (CHILD_SIDE_Y_PADDING_EXTEND * scaleFactor);
|
||||||
|
|
||||||
|
guard.enabled = true;
|
||||||
|
guard.top = itemsEl.value!.scrollTop;
|
||||||
|
guard.cursorSideX = localMouseX - cursorSideXPadding;
|
||||||
|
guard.cursorSideY = localMouseY;
|
||||||
|
guard.childSideTopY = (childBounding.top - rootBounding.top) - childSideYPadding;
|
||||||
|
guard.childSideBottomY = (childBounding.bottom - rootBounding.top) + childSideYPadding;
|
||||||
|
guard.direction = isChildRight ? 'toRight' : 'toLeft';
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMouseLeave() {
|
||||||
|
guard.enabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMouseMove() {
|
||||||
|
guard.enabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function guardMouseMove(ev: MouseEvent) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
@@ -592,6 +676,8 @@ onBeforeUnmount(() => {
|
|||||||
&:focus-visible:active,
|
&:focus-visible:active,
|
||||||
&:focus-visible.active {
|
&:focus-visible.active {
|
||||||
color: var(--menuHoverFg, var(--MI_THEME-accent));
|
color: var(--menuHoverFg, var(--MI_THEME-accent));
|
||||||
|
position: relative;
|
||||||
|
z-index: 10; // guardより上にする
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
background-color: var(--menuHoverBg, var(--MI_THEME-accentedBg));
|
background-color: var(--menuHoverBg, var(--MI_THEME-accentedBg));
|
||||||
@@ -744,4 +830,20 @@ onBeforeUnmount(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.guard {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&.showGuard {
|
||||||
|
background: #0f04;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #f004;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -4,8 +4,31 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<MkModal ref="modal" v-slot="{ type, maxHeight }" :manualShowing="manualShowing" :zPriority="'high'" :anchorElement="anchorElement" :transparentBg="true" :returnFocusTo="returnFocusTo" @click="click" @close="onModalClose" @closed="onModalClosed">
|
<MkModal
|
||||||
<MkMenu :items="items" :align="align" :width="width" :max-height="maxHeight" :asDrawer="type === 'drawer'" :returnFocusTo="returnFocusTo" :class="{ [$style.drawer]: type === 'drawer' }" @close="onMenuClose" @hide="hide"/>
|
ref="modal"
|
||||||
|
v-slot="{ type, maxHeight }"
|
||||||
|
:manualShowing="manualShowing"
|
||||||
|
:zPriority="'high'"
|
||||||
|
:anchorElement="anchorElement"
|
||||||
|
:transparentBg="true"
|
||||||
|
:returnFocusTo="returnFocusTo"
|
||||||
|
@click="click"
|
||||||
|
@close="onModalClose"
|
||||||
|
@closed="onModalClosed"
|
||||||
|
>
|
||||||
|
<MkMenu
|
||||||
|
:items="items"
|
||||||
|
:align="align"
|
||||||
|
:width="width"
|
||||||
|
:max-height="maxHeight"
|
||||||
|
:asDrawer="type === 'drawer'"
|
||||||
|
:returnFocusTo="returnFocusTo"
|
||||||
|
:debugDisablePredictionCone="debugDisablePredictionCone"
|
||||||
|
:debugShowPredictionCone="debugShowPredictionCone"
|
||||||
|
:class="{ [$style.drawer]: type === 'drawer' }"
|
||||||
|
@close="onMenuClose"
|
||||||
|
@hide="hide"
|
||||||
|
/>
|
||||||
</MkModal>
|
</MkModal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -21,6 +44,8 @@ defineProps<{
|
|||||||
width?: number;
|
width?: number;
|
||||||
anchorElement?: HTMLElement | null;
|
anchorElement?: HTMLElement | null;
|
||||||
returnFocusTo?: HTMLElement | null;
|
returnFocusTo?: HTMLElement | null;
|
||||||
|
debugDisablePredictionCone?: boolean;
|
||||||
|
debugShowPredictionCone?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
|
|||||||
@@ -640,6 +640,8 @@ export function popupMenu(items: (MenuItem | null)[], anchorElement?: HTMLElemen
|
|||||||
width?: number;
|
width?: number;
|
||||||
onClosing?: () => void;
|
onClosing?: () => void;
|
||||||
onClosed?: () => void;
|
onClosed?: () => void;
|
||||||
|
debugDisablePredictionCone?: boolean;
|
||||||
|
debugShowPredictionCone?: boolean;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
if (!(anchorElement instanceof HTMLElement)) {
|
if (!(anchorElement instanceof HTMLElement)) {
|
||||||
anchorElement = null;
|
anchorElement = null;
|
||||||
@@ -653,6 +655,8 @@ export function popupMenu(items: (MenuItem | null)[], anchorElement?: HTMLElemen
|
|||||||
width: options?.width,
|
width: options?.width,
|
||||||
align: options?.align,
|
align: options?.align,
|
||||||
returnFocusTo,
|
returnFocusTo,
|
||||||
|
debugDisablePredictionCone: options?.debugDisablePredictionCone,
|
||||||
|
debugShowPredictionCone: options?.debugShowPredictionCone,
|
||||||
}, {
|
}, {
|
||||||
closed: () => {
|
closed: () => {
|
||||||
resolve();
|
resolve();
|
||||||
|
|||||||
@@ -7,30 +7,46 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<PageWithHeader>
|
<PageWithHeader>
|
||||||
<div class="_spacer" style="--MI_SPACER-w: 600px;">
|
<div class="_spacer" style="--MI_SPACER-w: 600px;">
|
||||||
<div class="_gaps_m">
|
<div class="_gaps_m">
|
||||||
<MkResult v-if="resultType === 'empty'" type="empty"/>
|
<MkFolder>
|
||||||
<MkResult v-if="resultType === 'notFound'" type="notFound"/>
|
<template #label>Icons</template>
|
||||||
<MkResult v-if="resultType === 'error'" type="error"/>
|
|
||||||
<MkSelect
|
|
||||||
v-model="resultType" :items="resultTypeDef"
|
|
||||||
></MkSelect>
|
|
||||||
|
|
||||||
<MkSystemIcon v-if="iconType === 'info'" type="info" style="width: 150px;"/>
|
<div class="_gaps_m">
|
||||||
<MkSystemIcon v-if="iconType === 'question'" type="question" style="width: 150px;"/>
|
<MkResult v-if="resultType === 'empty'" type="empty"/>
|
||||||
<MkSystemIcon v-if="iconType === 'success'" type="success" style="width: 150px;"/>
|
<MkResult v-if="resultType === 'notFound'" type="notFound"/>
|
||||||
<MkSystemIcon v-if="iconType === 'warn'" type="warn" style="width: 150px;"/>
|
<MkResult v-if="resultType === 'error'" type="error"/>
|
||||||
<MkSystemIcon v-if="iconType === 'error'" type="error" style="width: 150px;"/>
|
<MkSelect
|
||||||
<MkSystemIcon v-if="iconType === 'waiting'" type="waiting" style="width: 150px;"/>
|
v-model="resultType" :items="resultTypeDef"
|
||||||
<MkSelect
|
></MkSelect>
|
||||||
v-model="iconType" :items="iconTypeDef"
|
|
||||||
></MkSelect>
|
|
||||||
|
|
||||||
<div class="_buttons">
|
<MkSystemIcon v-if="iconType === 'info'" type="info" style="width: 150px;"/>
|
||||||
<MkButton @click="os.alert({ type: 'error', title: 'Error', text: 'error' })">Error</MkButton>
|
<MkSystemIcon v-if="iconType === 'question'" type="question" style="width: 150px;"/>
|
||||||
<MkButton @click="os.alert({ type: 'warning', title: 'Warning', text: 'warning' })">Warning</MkButton>
|
<MkSystemIcon v-if="iconType === 'success'" type="success" style="width: 150px;"/>
|
||||||
<MkButton @click="os.alert({ type: 'info', title: 'Info', text: 'info' })">Info</MkButton>
|
<MkSystemIcon v-if="iconType === 'warn'" type="warn" style="width: 150px;"/>
|
||||||
<MkButton @click="os.alert({ type: 'success', title: 'Success', text: 'success' })">Success</MkButton>
|
<MkSystemIcon v-if="iconType === 'error'" type="error" style="width: 150px;"/>
|
||||||
<MkButton @click="os.alert({ type: 'question', title: 'Question', text: 'question' })">Question</MkButton>
|
<MkSystemIcon v-if="iconType === 'waiting'" type="waiting" style="width: 150px;"/>
|
||||||
</div>
|
<MkSelect
|
||||||
|
v-model="iconType" :items="iconTypeDef"
|
||||||
|
></MkSelect>
|
||||||
|
|
||||||
|
<div class="_buttons">
|
||||||
|
<MkButton @click="os.alert({ type: 'error', title: 'Error', text: 'error' })">Error</MkButton>
|
||||||
|
<MkButton @click="os.alert({ type: 'warning', title: 'Warning', text: 'warning' })">Warning</MkButton>
|
||||||
|
<MkButton @click="os.alert({ type: 'info', title: 'Info', text: 'info' })">Info</MkButton>
|
||||||
|
<MkButton @click="os.alert({ type: 'success', title: 'Success', text: 'success' })">Success</MkButton>
|
||||||
|
<MkButton @click="os.alert({ type: 'question', title: 'Question', text: 'question' })">Question</MkButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</MkFolder>
|
||||||
|
|
||||||
|
<MkFolder>
|
||||||
|
<template #label>Nested menu guard (a.k.a "prediction cone")</template>
|
||||||
|
|
||||||
|
<div class="_buttons">
|
||||||
|
<MkButton @click="select($event, false, false)">select without guard</MkButton>
|
||||||
|
<MkButton @click="select($event, true, false)">select with guard</MkButton>
|
||||||
|
<MkButton @click="select($event, true, true)">select with guard (visualize)</MkButton>
|
||||||
|
</div>
|
||||||
|
</MkFolder>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</PageWithHeader>
|
</PageWithHeader>
|
||||||
@@ -47,6 +63,7 @@ import MkSelect from '@/components/MkSelect.vue';
|
|||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import { useMkSelect } from '@/composables/use-mkselect.js';
|
import { useMkSelect } from '@/composables/use-mkselect.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
|
import MkFolder from '@/components/MkFolder.vue';
|
||||||
|
|
||||||
const {
|
const {
|
||||||
model: resultType,
|
model: resultType,
|
||||||
@@ -74,6 +91,64 @@ const {
|
|||||||
initialValue: 'info',
|
initialValue: 'info',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function select(ev: PointerEvent, enablePredictionCone: boolean, showPredictionCone: boolean) {
|
||||||
|
os.popupMenu([
|
||||||
|
{ type: 'parent', text: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX', children: [
|
||||||
|
{ text: 'Option', action: () => {} },
|
||||||
|
{ text: 'Option', action: () => {} },
|
||||||
|
{ text: 'Option', action: () => {} },
|
||||||
|
] },
|
||||||
|
{ type: 'parent', text: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX', children: [
|
||||||
|
{ text: 'Option', action: () => {} },
|
||||||
|
{ text: 'Option', action: () => {} },
|
||||||
|
{ text: 'Option', action: () => {} },
|
||||||
|
{ text: 'Option', action: () => {} },
|
||||||
|
{ text: 'Option', action: () => {} },
|
||||||
|
{ text: 'Option', action: () => {} },
|
||||||
|
{ text: 'Option', action: () => {} },
|
||||||
|
{ text: 'Option', action: () => {} },
|
||||||
|
{ text: 'Option', action: () => {} },
|
||||||
|
] },
|
||||||
|
{ type: 'parent', text: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX', children: [
|
||||||
|
{ text: 'Option', action: () => {} },
|
||||||
|
{ text: 'Option', action: () => {} },
|
||||||
|
{ text: 'Option', action: () => {} },
|
||||||
|
] },
|
||||||
|
{ text: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX', action: () => {} },
|
||||||
|
{ type: 'parent', text: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX', children: [
|
||||||
|
{ text: 'Option', action: () => {} },
|
||||||
|
] },
|
||||||
|
{ type: 'parent', text: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX', children: [
|
||||||
|
{ text: 'Option', action: () => {} },
|
||||||
|
{ text: 'Option', action: () => {} },
|
||||||
|
{ text: 'Option', action: () => {} },
|
||||||
|
{ text: 'Option', action: () => {} },
|
||||||
|
{ text: 'Option', action: () => {} },
|
||||||
|
] },
|
||||||
|
{ type: 'parent', text: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX', children: [
|
||||||
|
{ text: 'Option', action: () => {} },
|
||||||
|
{ text: 'Option', action: () => {} },
|
||||||
|
{ type: 'parent', text: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX', children: [
|
||||||
|
{ text: 'Option', action: () => {} },
|
||||||
|
{ text: 'Option', action: () => {} },
|
||||||
|
{ text: 'Option', action: () => {} },
|
||||||
|
{ text: 'Option', action: () => {} },
|
||||||
|
{ text: 'Option', action: () => {} },
|
||||||
|
] },
|
||||||
|
{ text: 'Option', action: () => {} },
|
||||||
|
{ text: 'Option', action: () => {} },
|
||||||
|
] },
|
||||||
|
{ type: 'parent', text: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX', children: [
|
||||||
|
{ text: 'Option', action: () => {} },
|
||||||
|
] },
|
||||||
|
], ev.currentTarget ?? ev.target, {
|
||||||
|
debugDisablePredictionCone: !enablePredictionCone,
|
||||||
|
debugShowPredictionCone: showPredictionCone,
|
||||||
|
}).then((value) => {
|
||||||
|
console.log('Selected:', value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
definePage(() => ({
|
definePage(() => ({
|
||||||
title: 'DEBUG ROOM',
|
title: 'DEBUG ROOM',
|
||||||
icon: 'ti ti-help-circle',
|
icon: 'ti ti-help-circle',
|
||||||
|
|||||||
Reference in New Issue
Block a user