add the focus trap thingies again

This commit is contained in:
Freeplay 2023-04-28 19:57:19 -04:00
parent 6498a90dc1
commit 3be2147397
4 changed files with 380 additions and 382 deletions

View File

@ -1,157 +1,159 @@
<template> <template>
<div <FocusTrap v-bind:active="isActive">
class="omfetrab" <div
:class="['s' + size, 'w' + width, 'h' + height, { asDrawer }]" class="omfetrab"
:style="{ maxHeight: maxHeight ? maxHeight + 'px' : undefined }" :class="['s' + size, 'w' + width, 'h' + height, { asDrawer }]"
> :style="{ maxHeight: maxHeight ? maxHeight + 'px' : undefined }"
<input >
ref="search" <input
v-model.trim="q" ref="search"
class="search" v-model.trim="q"
data-prevent-emoji-insert class="search"
:class="{ filled: q != null && q != '' }" data-prevent-emoji-insert
:placeholder="i18n.ts.search" :class="{ filled: q != null && q != '' }"
type="search" :placeholder="i18n.ts.search"
@paste.stop="paste" type="search"
@keyup.enter="done()" @paste.stop="paste"
/> @keyup.enter="done()"
<div ref="emojis" class="emojis"> />
<section class="result"> <div ref="emojis" class="emojis">
<div v-if="searchResultCustom.length > 0" class="body"> <section class="result">
<button <div v-if="searchResultCustom.length > 0" class="body">
v-for="emoji in searchResultCustom"
:key="emoji.id"
class="_button item"
:title="emoji.name"
tabindex="0"
@click="chosen(emoji, $event)"
>
<!--<MkEmoji v-if="emoji.char != null" :emoji="emoji.char"/>-->
<img
class="emoji"
:src="
disableShowingAnimatedImages
? getStaticImageUrl(emoji.url)
: emoji.url
"
/>
</button>
</div>
<div v-if="searchResultUnicode.length > 0" class="body">
<button
v-for="emoji in searchResultUnicode"
:key="emoji.name"
class="_button item"
:title="emoji.name"
tabindex="0"
@click="chosen(emoji, $event)"
>
<MkEmoji class="emoji" :emoji="emoji.char" />
</button>
</div>
</section>
<div v-if="tab === 'index'" class="group index">
<section v-if="showPinned">
<div class="body">
<button <button
v-for="emoji in pinned" v-for="emoji in searchResultCustom"
:key="emoji" :key="emoji.id"
class="_button item" class="_button item"
:title="emoji.name"
tabindex="0" tabindex="0"
@click="chosen(emoji, $event)" @click="chosen(emoji, $event)"
> >
<MkEmoji <!--<MkEmoji v-if="emoji.char != null" :emoji="emoji.char"/>-->
<img
class="emoji" class="emoji"
:emoji="emoji" :src="
:normal="true" disableShowingAnimatedImages
? getStaticImageUrl(emoji.url)
: emoji.url
"
/> />
</button> </button>
</div> </div>
<div v-if="searchResultUnicode.length > 0" class="body">
<button
v-for="emoji in searchResultUnicode"
:key="emoji.name"
class="_button item"
:title="emoji.name"
tabindex="0"
@click="chosen(emoji, $event)"
>
<MkEmoji class="emoji" :emoji="emoji.char" />
</button>
</div>
</section> </section>
<section> <div v-if="tab === 'index'" class="group index">
<header class="_acrylic"> <section v-if="showPinned">
<i class="ph-alarm ph-bold ph-fw ph-lg"></i> <div class="body">
{{ i18n.ts.recentUsed }} <button
</header> v-for="emoji in pinned"
<div class="body"> :key="emoji"
<button class="_button item"
v-for="emoji in recentlyUsedEmojis" tabindex="0"
:key="emoji" @click="chosen(emoji, $event)"
class="_button item" >
@click="chosen(emoji, $event)" <MkEmoji
> class="emoji"
<MkEmoji :emoji="emoji"
class="emoji" :normal="true"
:emoji="emoji" />
:normal="true" </button>
/> </div>
</button> </section>
</div>
</section> <section>
<header class="_acrylic">
<i class="ph-alarm ph-bold ph-fw ph-lg"></i>
{{ i18n.ts.recentUsed }}
</header>
<div class="body">
<button
v-for="emoji in recentlyUsedEmojis"
:key="emoji"
class="_button item"
@click="chosen(emoji, $event)"
>
<MkEmoji
class="emoji"
:emoji="emoji"
:normal="true"
/>
</button>
</div>
</section>
</div>
<div v-once class="group">
<header>{{ i18n.ts.customEmojis }}</header>
<XSection
v-for="category in customEmojiCategories"
:key="'custom:' + category"
:initial-shown="false"
:emojis="
customEmojis
.filter((e) => e.category === category)
.map((e) => ':' + e.name + ':')
"
@chosen="chosen"
>{{ category || i18n.ts.other }}</XSection
>
</div>
<div v-once class="group">
<header>{{ i18n.ts.emoji }}</header>
<XSection
v-for="category in categories"
:key="category"
:emojis="
emojilist
.filter((e) => e.category === category)
.map((e) => e.char)
"
@chosen="chosen"
>{{ category }}</XSection
>
</div>
</div> </div>
<div v-once class="group"> <div class="tabs">
<header>{{ i18n.ts.customEmojis }}</header> <button
<XSection class="_button tab"
v-for="category in customEmojiCategories" :class="{ active: tab === 'index' }"
:key="'custom:' + category" @click="tab = 'index'"
:initial-shown="false"
:emojis="
customEmojis
.filter((e) => e.category === category)
.map((e) => ':' + e.name + ':')
"
@chosen="chosen"
>{{ category || i18n.ts.other }}</XSection
> >
</div> <i class="ph-asterisk ph-bold ph-lg ph-fw ph-lg"></i>
<div v-once class="group"> </button>
<header>{{ i18n.ts.emoji }}</header> <button
<XSection class="_button tab"
v-for="category in categories" :class="{ active: tab === 'custom' }"
:key="category" @click="tab = 'custom'"
:emojis="
emojilist
.filter((e) => e.category === category)
.map((e) => e.char)
"
@chosen="chosen"
>{{ category }}</XSection
> >
<i class="ph-smiley ph-bold ph-lg ph-fw ph-lg"></i>
</button>
<button
class="_button tab"
:class="{ active: tab === 'unicode' }"
@click="tab = 'unicode'"
>
<i class="ph-leaf ph-bold ph-lg ph-fw ph-lg"></i>
</button>
<button
class="_button tab"
:class="{ active: tab === 'tags' }"
@click="tab = 'tags'"
>
<i class="ph-hash ph-bold ph-lg ph-fw ph-lg"></i>
</button>
</div> </div>
</div> </div>
<div class="tabs"> </FocusTrap>
<button
class="_button tab"
:class="{ active: tab === 'index' }"
@click="tab = 'index'"
>
<i class="ph-asterisk ph-bold ph-lg ph-fw ph-lg"></i>
</button>
<button
class="_button tab"
:class="{ active: tab === 'custom' }"
@click="tab = 'custom'"
>
<i class="ph-smiley ph-bold ph-lg ph-fw ph-lg"></i>
</button>
<button
class="_button tab"
:class="{ active: tab === 'unicode' }"
@click="tab = 'unicode'"
>
<i class="ph-leaf ph-bold ph-lg ph-fw ph-lg"></i>
</button>
<button
class="_button tab"
:class="{ active: tab === 'tags' }"
@click="tab = 'tags'"
>
<i class="ph-hash ph-bold ph-lg ph-fw ph-lg"></i>
</button>
</div>
</div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -171,6 +173,7 @@ import { deviceKind } from "@/scripts/device-kind";
import { emojiCategories, instance } from "@/instance"; import { emojiCategories, instance } from "@/instance";
import { i18n } from "@/i18n"; import { i18n } from "@/i18n";
import { defaultStore } from "@/store"; import { defaultStore } from "@/store";
import { FocusTrap } from 'focus-trap-vue';
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{

View File

@ -1,191 +1,185 @@
<template> <template>
<div> <FocusTrap v-bind:active="isActive">
<div <div tabindex="-1" v-focus>
ref="itemsEl" <div
v-hotkey="keymap" ref="itemsEl"
class="rrevdjwt _popup _shadow" class="rrevdjwt _popup _shadow"
:class="{ center: align === 'center', asDrawer }" :class="{ center: align === 'center', asDrawer }"
:style="{ :style="{
width: width && !asDrawer ? width + 'px' : '', width: width && !asDrawer ? width + 'px' : '',
maxHeight: maxHeight ? maxHeight + 'px' : '', maxHeight: maxHeight ? maxHeight + 'px' : '',
}" }"
@contextmenu.self="(e) => e.preventDefault()" @contextmenu.self="(e) => e.preventDefault()"
> >
<template v-for="(item, i) in items2"> <template v-for="(item, i) in items2">
<div v-if="item === null" class="divider"></div> <div v-if="item === null" class="divider"></div>
<span v-else-if="item.type === 'label'" class="label item"> <span v-else-if="item.type === 'label'" class="label item">
<span :style="item.textStyle || ''">{{ item.text }}</span> <span :style="item.textStyle || ''">{{ item.text }}</span>
</span>
<span
v-else-if="item.type === 'pending'"
:tabindex="i"
class="pending item"
>
<span><MkEllipsis /></span>
</span>
<MkA
v-else-if="item.type === 'link'"
:to="item.to"
:tabindex="i"
class="_button item"
@click.passive="close(true)"
@mouseenter.passive="onItemMouseEnter(item)"
@mouseleave.passive="onItemMouseLeave(item)"
>
<i
v-if="item.icon"
class="ph-fw ph-lg"
:class="item.icon"
></i>
<span v-else-if="item.icons">
<i
v-for="icon in item.icons"
class="ph-fw ph-lg"
:class="icon"
></i>
</span> </span>
<MkAvatar <span
v-if="item.avatar" v-else-if="item.type === 'pending'"
:user="item.avatar" class="pending item"
class="avatar"
/>
<span :style="item.textStyle || ''">{{ item.text }}</span>
<span v-if="item.indicate" class="indicator"
><i class="ph-circle ph-fill"></i
></span>
</MkA>
<a
v-else-if="item.type === 'a'"
:href="item.href"
:target="item.target"
:download="item.download"
:tabindex="i"
class="_button item"
@click="close(true)"
@mouseenter.passive="onItemMouseEnter(item)"
@mouseleave.passive="onItemMouseLeave(item)"
>
<i
v-if="item.icon"
class="ph-fw ph-lg"
:class="item.icon"
></i>
<span v-else-if="item.icons">
<i
v-for="icon in item.icons"
class="ph-fw ph-lg"
:class="icon"
></i>
</span>
<span :style="item.textStyle || ''">{{ item.text }}</span>
<span v-if="item.indicate" class="indicator"
><i class="ph-circle ph-fill"></i
></span>
</a>
<button
v-else-if="item.type === 'user' && !items.hidden"
:tabindex="i"
class="_button item"
:class="{ active: item.active }"
:disabled="item.active"
@click="clicked(item.action, $event)"
@mouseenter.passive="onItemMouseEnter(item)"
@mouseleave.passive="onItemMouseLeave(item)"
>
<MkAvatar :user="item.user" class="avatar" /><MkUserName
:user="item.user"
/>
<span v-if="item.indicate" class="indicator"
><i class="ph-circle ph-fill"></i
></span>
</button>
<span
v-else-if="item.type === 'switch'"
:tabindex="i"
class="item"
@mouseenter.passive="onItemMouseEnter(item)"
@mouseleave.passive="onItemMouseLeave(item)"
>
<FormSwitch
v-model="item.ref"
:disabled="item.disabled"
class="form-switch"
:style="item.textStyle || ''"
>{{ item.text }}</FormSwitch
> >
<span><MkEllipsis /></span>
</span>
<MkA
v-else-if="item.type === 'link'"
:to="item.to"
class="_button item"
@click.passive="close(true)"
@mouseenter.passive="onItemMouseEnter(item)"
@mouseleave.passive="onItemMouseLeave(item)"
>
<i
v-if="item.icon"
class="ph-fw ph-lg"
:class="item.icon"
></i>
<span v-else-if="item.icons">
<i
v-for="icon in item.icons"
class="ph-fw ph-lg"
:class="icon"
></i>
</span>
<MkAvatar
v-if="item.avatar"
:user="item.avatar"
class="avatar"
/>
<span :style="item.textStyle || ''">{{ item.text }}</span>
<span v-if="item.indicate" class="indicator"
><i class="ph-circle ph-fill"></i
></span>
</MkA>
<a
v-else-if="item.type === 'a'"
:href="item.href"
:target="item.target"
:download="item.download"
class="_button item"
@click="close(true)"
@mouseenter.passive="onItemMouseEnter(item)"
@mouseleave.passive="onItemMouseLeave(item)"
>
<i
v-if="item.icon"
class="ph-fw ph-lg"
:class="item.icon"
></i>
<span v-else-if="item.icons">
<i
v-for="icon in item.icons"
class="ph-fw ph-lg"
:class="icon"
></i>
</span>
<span :style="item.textStyle || ''">{{ item.text }}</span>
<span v-if="item.indicate" class="indicator"
><i class="ph-circle ph-fill"></i
></span>
</a>
<button
v-else-if="item.type === 'user' && !items.hidden"
class="_button item"
:class="{ active: item.active }"
:disabled="item.active"
@click="clicked(item.action, $event)"
@mouseenter.passive="onItemMouseEnter(item)"
@mouseleave.passive="onItemMouseLeave(item)"
>
<MkAvatar :user="item.user" class="avatar" /><MkUserName
:user="item.user"
/>
<span v-if="item.indicate" class="indicator"
><i class="ph-circle ph-fill"></i
></span>
</button>
<span
v-else-if="item.type === 'switch'"
class="item"
@mouseenter.passive="onItemMouseEnter(item)"
@mouseleave.passive="onItemMouseLeave(item)"
>
<FormSwitch
v-model="item.ref"
:disabled="item.disabled"
class="form-switch"
:style="item.textStyle || ''"
>{{ item.text }}</FormSwitch
>
</span>
<button
v-else-if="item.type === 'parent'"
class="_button item parent"
:class="{ childShowing: childShowingItem === item }"
@mouseenter="showChildren(item, $event)"
>
<i
v-if="item.icon"
class="ph-fw ph-lg"
:class="item.icon"
></i>
<span v-else-if="item.icons">
<i
v-for="icon in item.icons"
class="ph-fw ph-lg"
:class="icon"
></i>
</span>
<span :style="item.textStyle || ''">{{ item.text }}</span>
<span class="caret"
><i class="ph-caret-right ph-bold ph-lg ph-fw ph-lg"></i
></span>
</button>
<button
v-else-if="!item.hidden"
class="_button item"
:class="{ danger: item.danger, 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="ph-fw ph-lg"
:class="item.icon"
></i>
<span v-else-if="item.icons">
<i
v-for="icon in item.icons"
class="ph-fw ph-lg"
:class="icon"
></i>
</span>
<MkAvatar
v-if="item.avatar"
:user="item.avatar"
class="avatar"
/>
<span :style="item.textStyle || ''">{{ item.text }}</span>
<span v-if="item.indicate" class="indicator"
><i class="ph-circle ph-fill"></i
></span>
</button>
</template>
<span v-if="items2.length === 0" class="none item">
<span>{{ i18n.ts.none }}</span>
</span> </span>
<button </div>
v-else-if="item.type === 'parent'" <div v-if="childMenu" class="child">
:tabindex="i" <XChild
class="_button item parent" ref="child"
:class="{ childShowing: childShowingItem === item }" :items="childMenu"
@mouseenter="showChildren(item, $event)" :target-element="childTarget"
> :root-element="itemsEl"
<i showing
v-if="item.icon" @actioned="childActioned"
class="ph-fw ph-lg" />
:class="item.icon" </div>
></i>
<span v-else-if="item.icons">
<i
v-for="icon in item.icons"
class="ph-fw ph-lg"
:class="icon"
></i>
</span>
<span :style="item.textStyle || ''">{{ item.text }}</span>
<span class="caret"
><i class="ph-caret-right ph-bold ph-lg ph-fw ph-lg"></i
></span>
</button>
<button
v-else-if="!item.hidden"
:tabindex="i"
class="_button item"
:class="{ danger: item.danger, 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="ph-fw ph-lg"
:class="item.icon"
></i>
<span v-else-if="item.icons">
<i
v-for="icon in item.icons"
class="ph-fw ph-lg"
:class="icon"
></i>
</span>
<MkAvatar
v-if="item.avatar"
:user="item.avatar"
class="avatar"
/>
<span :style="item.textStyle || ''">{{ item.text }}</span>
<span v-if="item.indicate" class="indicator"
><i class="ph-circle ph-fill"></i
></span>
</button>
</template>
<span v-if="items2.length === 0" class="none item">
<span>{{ i18n.ts.none }}</span>
</span>
</div> </div>
<div v-if="childMenu" class="child"> </FocusTrap>
<XChild
ref="child"
:items="childMenu"
:target-element="childTarget"
:root-element="itemsEl"
showing
@actioned="childActioned"
/>
</div>
</div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -229,12 +223,6 @@ let items2: InnerMenuItem[] = $ref([]);
let child = $ref<InstanceType<typeof XChild>>(); let child = $ref<InstanceType<typeof XChild>>();
let keymap = computed(() => ({
"up|k|shift+tab": focusUp,
"down|j|tab": focusDown,
esc: close,
}));
let childShowingItem = $ref<MenuItem | null>(); let childShowingItem = $ref<MenuItem | null>();
watch( watch(

View File

@ -35,6 +35,8 @@
: 'none', : 'none',
'--transformOrigin': transformOrigin, '--transformOrigin': transformOrigin,
}" }"
tabindex="-1"
v-focus
> >
<div <div
class="_modalBg data-cy-bg" class="_modalBg data-cy-bg"
@ -50,17 +52,19 @@
@mousedown="onBgClick" @mousedown="onBgClick"
@contextmenu.prevent.stop="() => {}" @contextmenu.prevent.stop="() => {}"
></div> ></div>
<div <focus-trap v-model:active="isActive">
ref="content" <div
:class="[ ref="content"
$style.content, :class="[
{ [$style.fixed]: fixed, top: type === 'dialog:top' }, $style.content,
]" { [$style.fixed]: fixed, top: type === 'dialog:top' },
:style="{ zIndex }" ]"
@click.self="onBgClick" :style="{ zIndex }"
> @click.self="onBgClick"
<slot :max-height="maxHeight" :type="type"></slot> >
</div> <slot :max-height="maxHeight" :type="type"></slot>
</div>
</focus-trap>
</div> </div>
</Transition> </Transition>
</template> </template>

View File

@ -3,54 +3,57 @@
ref="modal" ref="modal"
:prefer-type="'dialog'" :prefer-type="'dialog'"
@click="onBgClick" @click="onBgClick"
@keyup.esc="$emit('close')"
@closed="$emit('closed')" @closed="$emit('closed')"
> >
<div <focus-trap v-model:active="isActive">
ref="rootEl" <div
class="ebkgoccj" ref="rootEl"
:style="{ class="ebkgoccj"
width: `${width}px`, :style="{
height: scroll width: `${width}px`,
? height height: scroll
? `${height}px` ? height
: null ? `${height}px`
: height : null
? `min(${height}px, 100%)` : height
: '100%', ? `min(${height}px, 100%)`
}" : '100%',
@keydown="onKeydown" }"
> @keydown="onKeydown"
<div ref="headerEl" class="header"> >
<button <div ref="headerEl" class="header">
v-if="withOkButton" <button
class="_button" v-if="withOkButton"
@click="$emit('close')" class="_button"
> @click="$emit('close')"
<i class="ph-x ph-bold ph-lg"></i> >
</button> <i class="ph-x ph-bold ph-lg"></i>
<span class="title"> </button>
<slot name="header"></slot> <span class="title">
</span> <slot name="header"></slot>
<button </span>
v-if="!withOkButton" <button
class="_button" v-if="!withOkButton"
@click="$emit('close')" class="_button"
> @click="$emit('close')"
<i class="ph-x ph-bold ph-lg"></i> >
</button> <i class="ph-x ph-bold ph-lg"></i>
<button </button>
v-if="withOkButton" <button
class="_button" v-if="withOkButton"
:disabled="okButtonDisabled" class="_button"
@click="$emit('ok')" :disabled="okButtonDisabled"
> @click="$emit('ok')"
<i class="ph-check ph-bold ph-lg"></i> >
</button> <i class="ph-check ph-bold ph-lg"></i>
</button>
</div>
<div class="body">
<slot :width="bodyWidth" :height="bodyHeight"></slot>
</div>
</div> </div>
<div class="body"> </focus-trap>
<slot :width="bodyWidth" :height="bodyHeight"></slot>
</div>
</div>
</MkModal> </MkModal>
</template> </template>