merge upstream
This commit is contained in:
commit
d8908ef2d8
1065 changed files with 32953 additions and 20092 deletions
|
|
@ -11,6 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
[$style.center]: align === 'center',
|
||||
[$style.big]: big,
|
||||
[$style.asDrawer]: asDrawer,
|
||||
[$style.widthSpecified]: width != null,
|
||||
}"
|
||||
@focusin.passive.stop="() => {}"
|
||||
>
|
||||
|
|
@ -29,12 +30,19 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
>
|
||||
<template v-for="item in (items2 ?? [])">
|
||||
<div v-if="item.type === 'divider'" role="separator" tabindex="-1" :class="$style.divider"></div>
|
||||
|
||||
<span v-else-if="item.type === 'label'" role="menuitem" tabindex="-1" :class="[$style.label, $style.item]">
|
||||
<span style="opacity: 0.7;">{{ item.text }}</span>
|
||||
</span>
|
||||
|
||||
<span v-else-if="item.type === 'pending'" role="menuitem" tabindex="0" :class="[$style.pending, $style.item]">
|
||||
<span><MkEllipsis/></span>
|
||||
</span>
|
||||
|
||||
<div v-else-if="item.type === 'component'" role="menuitem" tabindex="-1" :class="[$style.componentItem]">
|
||||
<component :is="item.component" v-bind="item.props"/>
|
||||
</div>
|
||||
|
||||
<MkA
|
||||
v-else-if="item.type === 'link'"
|
||||
role="menuitem"
|
||||
|
|
@ -48,10 +56,14 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<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"/>
|
||||
<div :class="$style.item_content">
|
||||
<span :class="$style.item_content_text">{{ item.text }}</span>
|
||||
<div :class="$style.item_content_text">
|
||||
<div :class="$style.item_content_text_title">{{ item.text }}</div>
|
||||
<div v-if="item.caption" :class="$style.item_content_text_caption">{{ item.caption }}</div>
|
||||
</div>
|
||||
<span v-if="item.indicate" :class="$style.indicator" class="_blink"><i class="_indicatorCircle"></i></span>
|
||||
</div>
|
||||
</MkA>
|
||||
|
||||
<a
|
||||
v-else-if="item.type === 'a'"
|
||||
role="menuitem"
|
||||
|
|
@ -67,10 +79,14 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
>
|
||||
<i v-if="item.icon" class="ti-fw" :class="[$style.icon, item.icon]"></i>
|
||||
<div :class="$style.item_content">
|
||||
<span :class="$style.item_content_text">{{ item.text }}</span>
|
||||
<div :class="$style.item_content_text">
|
||||
<div :class="$style.item_content_text_title">{{ item.text }}</div>
|
||||
<div v-if="item.caption" :class="$style.item_content_text_caption">{{ item.caption }}</div>
|
||||
</div>
|
||||
<span v-if="item.indicate" :class="$style.indicator" class="_blink"><i class="_indicatorCircle"></i></span>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<button
|
||||
v-else-if="item.type === 'user'"
|
||||
role="menuitem"
|
||||
|
|
@ -85,6 +101,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<span :class="$style.indicator" class="_blink"><i class="_indicatorCircle"></i></span>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
v-else-if="item.type === 'switch'"
|
||||
role="menuitemcheckbox"
|
||||
|
|
@ -98,10 +115,14 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<i v-if="item.icon" class="ti-fw" :class="[$style.icon, item.icon]"></i>
|
||||
<MkSwitchButton v-else :class="$style.switchButton" :checked="item.ref" :disabled="item.disabled" @toggle="switchItem(item)"/>
|
||||
<div :class="$style.item_content">
|
||||
<span :class="[$style.item_content_text, { [$style.switchText]: !item.icon }]">{{ item.text }}</span>
|
||||
<div :class="[$style.item_content_text, { [$style.switchText]: !item.icon }]">
|
||||
<div :class="$style.item_content_text_title">{{ item.text }}</div>
|
||||
<div v-if="item.caption" :class="$style.item_content_text_caption">{{ item.caption }}</div>
|
||||
</div>
|
||||
<MkSwitchButton v-if="item.icon" :class="[$style.switchButton, $style.caret]" :checked="item.ref" :disabled="item.disabled" @toggle="switchItem(item)"/>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
v-else-if="item.type === 'radio'"
|
||||
role="menuitem"
|
||||
|
|
@ -114,10 +135,14 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
>
|
||||
<i v-if="item.icon" class="ti-fw" :class="[$style.icon, item.icon]" style="pointer-events: none;"></i>
|
||||
<div :class="$style.item_content">
|
||||
<span :class="$style.item_content_text" style="pointer-events: none;">{{ item.text }}</span>
|
||||
<div :class="$style.item_content_text" style="pointer-events: none;">
|
||||
<div :class="$style.item_content_text_title">{{ item.text }}</div>
|
||||
<div v-if="item.caption" :class="$style.item_content_text_caption">{{ item.caption }}</div>
|
||||
</div>
|
||||
<span :class="$style.caret" style="pointer-events: none;"><i class="ti ti-chevron-right ti-fw"></i></span>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
v-else-if="item.type === 'radioOption'"
|
||||
role="menuitemradio"
|
||||
|
|
@ -131,9 +156,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<span :class="[$style.radioIcon, { [$style.radioChecked]: unref(item.active) }]"></span>
|
||||
</div>
|
||||
<div :class="$style.item_content">
|
||||
<span :class="$style.item_content_text">{{ item.text }}</span>
|
||||
<div :class="$style.item_content_text">
|
||||
<div :class="$style.item_content_text_title">{{ item.text }}</div>
|
||||
<div v-if="item.caption" :class="$style.item_content_text_caption">{{ item.caption }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
v-else-if="item.type === 'parent'"
|
||||
role="menuitem"
|
||||
|
|
@ -145,12 +174,17 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
>
|
||||
<i v-if="item.icon" class="ti-fw" :class="[$style.icon, item.icon]" style="pointer-events: none;"></i>
|
||||
<div :class="$style.item_content">
|
||||
<span :class="$style.item_content_text" style="pointer-events: none;">{{ item.text }}</span>
|
||||
<div :class="$style.item_content_text" style="pointer-events: none;">
|
||||
<div :class="$style.item_content_text_title">{{ item.text }}</div>
|
||||
<div v-if="item.caption" :class="$style.item_content_text_caption">{{ item.caption }}</div>
|
||||
</div>
|
||||
<span :class="$style.caret" style="pointer-events: none;"><i class="ti ti-chevron-right ti-fw"></i></span>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
v-else role="menuitem"
|
||||
v-else
|
||||
role="menuitem"
|
||||
tabindex="0"
|
||||
:class="['_button', $style.item, { [$style.danger]: item.danger, [$style.active]: unref(item.active) }]"
|
||||
@click.prevent="unref(item.active) ? close(false) : clicked(item.action, $event)"
|
||||
|
|
@ -160,11 +194,15 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<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"/>
|
||||
<div :class="$style.item_content">
|
||||
<span :class="$style.item_content_text">{{ item.text }}</span>
|
||||
<div :class="$style.item_content_text">
|
||||
<div :class="$style.item_content_text_title">{{ item.text }}</div>
|
||||
<div v-if="item.caption" :class="$style.item_content_text_caption">{{ item.caption }}</div>
|
||||
</div>
|
||||
<span v-if="item.indicate" :class="$style.indicator" class="_blink"><i class="_indicatorCircle"></i></span>
|
||||
</div>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<span v-if="items2 == null || items2.length === 0" tabindex="-1" :class="[$style.none, $style.item]">
|
||||
<span>{{ i18n.ts.none }}</span>
|
||||
</span>
|
||||
|
|
@ -176,15 +214,15 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineAsyncComponent, inject, nextTick, onBeforeUnmount, onMounted, ref, shallowRef, unref, watch } from 'vue';
|
||||
import { computed, defineAsyncComponent, inject, nextTick, onBeforeUnmount, onMounted, ref, useTemplateRef, unref, watch, shallowRef } from 'vue';
|
||||
import type { MenuItem, InnerMenuItem, MenuPending, MenuAction, MenuSwitch, MenuRadio, MenuRadioOption, MenuParent } from '@/types/menu.js';
|
||||
import type { Keymap } from '@/utility/hotkey.js';
|
||||
import MkSwitchButton from '@/components/MkSwitch.button.vue';
|
||||
import { MenuItem, InnerMenuItem, MenuPending, MenuAction, MenuSwitch, MenuRadio, MenuRadioOption, MenuParent } from '@/types/menu.js';
|
||||
import * as os from '@/os.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { isTouchUsing } from '@/scripts/touch.js';
|
||||
import { type Keymap } from '@/scripts/hotkey.js';
|
||||
import { isFocusable } from '@/scripts/focus.js';
|
||||
import { getNodeOrNull } from '@/scripts/get-dom-node-or-null.js';
|
||||
import { isTouchUsing } from '@/utility/touch.js';
|
||||
import { isFocusable } from '@/utility/focus.js';
|
||||
import { getNodeOrNull } from '@/utility/get-dom-node-or-null.js';
|
||||
|
||||
const childrenCache = new WeakMap<MenuParent, MenuItem[]>();
|
||||
</script>
|
||||
|
|
@ -209,11 +247,11 @@ const big = isTouchUsing;
|
|||
|
||||
const isNestingMenu = inject<boolean>('isNestingMenu', false);
|
||||
|
||||
const itemsEl = shallowRef<HTMLElement>();
|
||||
const itemsEl = useTemplateRef('itemsEl');
|
||||
|
||||
const items2 = ref<InnerMenuItem[]>();
|
||||
|
||||
const child = shallowRef<InstanceType<typeof XChild>>();
|
||||
const child = useTemplateRef('child');
|
||||
|
||||
const keymap = {
|
||||
'up|k|shift+tab': {
|
||||
|
|
@ -254,7 +292,7 @@ watch(() => props.items, () => {
|
|||
});
|
||||
|
||||
const childMenu = ref<MenuItem[] | null>();
|
||||
const childTarget = shallowRef<HTMLElement | null>();
|
||||
const childTarget = shallowRef<HTMLElement>();
|
||||
|
||||
function closeChild() {
|
||||
childMenu.value = null;
|
||||
|
|
@ -355,10 +393,10 @@ function switchItem(item: MenuSwitch & { ref: any }) {
|
|||
|
||||
function focusUp() {
|
||||
if (disposed) return;
|
||||
if (!itemsEl.value?.contains(document.activeElement)) return;
|
||||
if (!itemsEl.value?.contains(window.document.activeElement)) return;
|
||||
|
||||
const focusableElements = Array.from(itemsEl.value.children).filter(isFocusable);
|
||||
const activeIndex = focusableElements.findIndex(el => el === document.activeElement);
|
||||
const activeIndex = focusableElements.findIndex(el => el === window.document.activeElement);
|
||||
const targetIndex = (activeIndex !== -1 && activeIndex !== 0) ? (activeIndex - 1) : (focusableElements.length - 1);
|
||||
const targetElement = focusableElements.at(targetIndex) ?? itemsEl.value;
|
||||
|
||||
|
|
@ -367,10 +405,10 @@ function focusUp() {
|
|||
|
||||
function focusDown() {
|
||||
if (disposed) return;
|
||||
if (!itemsEl.value?.contains(document.activeElement)) return;
|
||||
if (!itemsEl.value?.contains(window.document.activeElement)) return;
|
||||
|
||||
const focusableElements = Array.from(itemsEl.value.children).filter(isFocusable);
|
||||
const activeIndex = focusableElements.findIndex(el => el === document.activeElement);
|
||||
const activeIndex = focusableElements.findIndex(el => el === window.document.activeElement);
|
||||
const targetIndex = (activeIndex !== -1 && activeIndex !== (focusableElements.length - 1)) ? (activeIndex + 1) : 0;
|
||||
const targetElement = focusableElements.at(targetIndex) ?? itemsEl.value;
|
||||
|
||||
|
|
@ -397,9 +435,9 @@ const onGlobalMousedown = (ev: MouseEvent) => {
|
|||
|
||||
const setupHandlers = () => {
|
||||
if (!isNestingMenu) {
|
||||
document.addEventListener('focusin', onGlobalFocusin, { passive: true });
|
||||
window.document.addEventListener('focusin', onGlobalFocusin, { passive: true });
|
||||
}
|
||||
document.addEventListener('mousedown', onGlobalMousedown, { passive: true });
|
||||
window.document.addEventListener('mousedown', onGlobalMousedown, { passive: true });
|
||||
};
|
||||
|
||||
let disposed = false;
|
||||
|
|
@ -407,9 +445,9 @@ let disposed = false;
|
|||
const disposeHandlers = () => {
|
||||
disposed = true;
|
||||
if (!isNestingMenu) {
|
||||
document.removeEventListener('focusin', onGlobalFocusin);
|
||||
window.document.removeEventListener('focusin', onGlobalFocusin);
|
||||
}
|
||||
document.removeEventListener('mousedown', onGlobalMousedown);
|
||||
window.document.removeEventListener('mousedown', onGlobalMousedown);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
|
|
@ -435,6 +473,12 @@ onBeforeUnmount(() => {
|
|||
}
|
||||
}
|
||||
|
||||
&:not(.widthSpecified) {
|
||||
> .menu {
|
||||
max-width: 400px;
|
||||
}
|
||||
}
|
||||
|
||||
&.big:not(.asDrawer) {
|
||||
> .menu {
|
||||
min-width: 230px;
|
||||
|
|
@ -558,11 +602,11 @@ onBeforeUnmount(() => {
|
|||
}
|
||||
|
||||
&.danger {
|
||||
--menuFg: #ff2a2a;
|
||||
--menuFg: var(--MI_THEME-error);
|
||||
--menuHoverFg: #fff;
|
||||
--menuHoverBg: #ff4242;
|
||||
--menuHoverBg: var(--MI_THEME-error);
|
||||
--menuActiveFg: #fff;
|
||||
--menuActiveBg: #d42e2e;
|
||||
--menuActiveBg: hsl(from var(--MI_THEME-error) h s calc(l - 10));
|
||||
}
|
||||
|
||||
&.radio {
|
||||
|
|
@ -604,10 +648,19 @@ onBeforeUnmount(() => {
|
|||
|
||||
.item_content_text {
|
||||
max-width: calc(100vw - 4rem);
|
||||
}
|
||||
|
||||
.item_content_text_title {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.item_content_text_caption {
|
||||
text-wrap: auto;
|
||||
font-size: 85%;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.switchButton {
|
||||
margin-left: -2px;
|
||||
--height: 1.35em;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue