FOCUS TRAPPING!!!!

This commit is contained in:
Freeplay 2023-02-12 21:56:01 -05:00
parent ded8ab980a
commit 5e07df2ed0
6 changed files with 68 additions and 27 deletions

View File

@ -40,6 +40,8 @@
"@bull-board/ui": "^4.10.2", "@bull-board/ui": "^4.10.2",
"@tensorflow/tfjs": "^3.21.0", "@tensorflow/tfjs": "^3.21.0",
"calckey-js": "^0.0.22", "calckey-js": "^0.0.22",
"focus-trap": "^7.2.0",
"focus-trap-vue": "^4.0.1",
"js-yaml": "4.1.0", "js-yaml": "4.1.0",
"phosphor-icons": "^1.4.2", "phosphor-icons": "^1.4.2",
"seedrandom": "^3.0.5" "seedrandom": "^3.0.5"

View File

@ -1,6 +1,8 @@
<template> <template>
<div ref="el" class="sfhdhdhr"> <div ref="el" class="sfhdhdhr">
<MkMenu ref="menu" :items="items" :align="align" :width="width" :as-drawer="false" @close="onChildClosed"/> <FocusTrap v-bind:active="isActive">
<MkMenu ref="menu" :items="items" :align="align" :width="width" :as-drawer="false" @close="onChildClosed"/>
</FocusTrap>
</div> </div>
</template> </template>
@ -9,6 +11,7 @@ import { on } from 'events';
import { nextTick, onBeforeUnmount, onMounted, onUnmounted, ref, watch } from 'vue'; import { nextTick, onBeforeUnmount, onMounted, onUnmounted, ref, watch } from 'vue';
import MkMenu from './MkMenu.vue'; import MkMenu from './MkMenu.vue';
import { MenuItem } from '@/types/menu'; import { MenuItem } from '@/types/menu';
import { FocusTrap } from 'focus-trap-vue';
import * as os from '@/os'; import * as os from '@/os';
const props = defineProps<{ const props = defineProps<{

View File

@ -1,7 +1,7 @@
<template> <template>
<div tabindex="-1" v-focus> <div tabindex="-1" v-focus>
<div <div
ref="itemsEl" v-hotkey="keymap" ref="itemsEl"
class="rrevdjwt _popup _shadow" class="rrevdjwt _popup _shadow"
:class="{ center: align === 'center', asDrawer }" :class="{ center: align === 'center', asDrawer }"
:style="{ width: (width && !asDrawer) ? width + 'px' : '', maxHeight: maxHeight ? maxHeight + 'px' : '' }" :style="{ width: (width && !asDrawer) ? width + 'px' : '', maxHeight: maxHeight ? maxHeight + 'px' : '' }"
@ -84,12 +84,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(() => props.items, () => { watch(() => props.items, () => {

View File

@ -1,11 +1,13 @@
<template> <template>
<transition :name="$store.state.animation ? (type === 'drawer') ? 'modal-drawer' : (type === 'popup') ? 'modal-popup' : 'modal' : ''" :duration="$store.state.animation ? 200 : 0" appear @after-leave="emit('closed')" @enter="emit('opening')" @keyup.esc="emit('click')" @after-enter="onOpened"> <transition :name="$store.state.animation ? (type === 'drawer') ? 'modal-drawer' : (type === 'popup') ? 'modal-popup' : 'modal' : ''" :duration="$store.state.animation ? 200 : 0" appear @after-leave="emit('closed')" @enter="emit('opening')" @keyup.esc="emit('click')" @after-enter="onOpened">
<div v-show="manualShowing != null ? manualShowing : showing" v-hotkey.global="keymap" class="qzhlnise" :class="{ drawer: type === 'drawer', dialog: type === 'dialog' || type === 'dialog:top', popup: type === 'popup' }" :style="{ zIndex, pointerEvents: (manualShowing != null ? manualShowing : showing) ? 'auto' : 'none', '--transformOrigin': transformOrigin }"> <focus-trap v-model:active="isActive">
<div class="bg _modalBg" :class="{ transparent: transparentBg && (type === 'popup') }" :style="{ zIndex }" @click="onBgClick" @contextmenu.prevent.stop="() => {}"></div> <div v-show="manualShowing != null ? manualShowing : showing" v-hotkey.global="keymap" class="qzhlnise" :class="{ drawer: type === 'drawer', dialog: type === 'dialog' || type === 'dialog:top', popup: type === 'popup' }" :style="{ zIndex, pointerEvents: (manualShowing != null ? manualShowing : showing) ? 'auto' : 'none', '--transformOrigin': transformOrigin }">
<div ref="content" class="content" :class="{ fixed, top: type === 'dialog:top' }" :style="{ zIndex }" @click.self="onBgClick" tabindex="-1" v-focus> <div class="bg _modalBg" :class="{ transparent: transparentBg && (type === 'popup') }" :style="{ zIndex }" @click="onBgClick" @contextmenu.prevent.stop="() => {}"></div>
<slot :max-height="maxHeight" :type="type"></slot> <div ref="content" class="content" :class="{ fixed, top: type === 'dialog:top' }" :style="{ zIndex }" @click.self="onBgClick" tabindex="-1" v-focus>
<slot :max-height="maxHeight" :type="type"></slot>
</div>
</div> </div>
</div> </focus-trap>
</transition> </transition>
</template> </template>
@ -15,6 +17,7 @@ import * as os from '@/os';
import { isTouchUsing } from '@/scripts/touch'; import { isTouchUsing } from '@/scripts/touch';
import { defaultStore } from '@/store'; import { defaultStore } from '@/store';
import { deviceKind } from '@/scripts/device-kind'; import { deviceKind } from '@/scripts/device-kind';
import { FocusTrap } from 'focus-trap-vue';
function getFixedContainer(el: Element | null): Element | null { function getFixedContainer(el: Element | null): Element | null {
if (el == null || el.tagName === 'BODY') return null; if (el == null || el.tagName === 'BODY') return null;

View File

@ -1,23 +1,26 @@
<template> <template>
<MkModal ref="modal" :prefer-type="'dialog'" @click="onBgClick" @closed="$emit('closed')"> <MkModal ref="modal" :prefer-type="'dialog'" @click="onBgClick" @keyup.esc="$emit('close')" @closed="$emit('closed')">
<div ref="rootEl" class="ebkgoccj _narrow_" :style="{ width: `${width}px`, height: scroll ? (height ? `${height}px` : null) : (height ? `min(${height}px, 100%)` : '100%') }" @keydown="onKeydown"> <FocusTrap v-model:active="isActive">
<div ref="headerEl" class="header"> <div ref="rootEl" class="ebkgoccj _narrow_" :style="{ width: `${width}px`, height: scroll ? (height ? `${height}px` : null) : (height ? `min(${height}px, 100%)` : '100%') }" @keydown="onKeydown">
<button v-if="withOkButton" class="_button" @click="$emit('close')"><i class="ph-x-bold ph-lg"></i></button> <div ref="headerEl" class="header">
<span class="title"> <button v-if="withOkButton" class="_button" @click="$emit('close')"><i class="ph-x-bold ph-lg"></i></button>
<slot name="header"></slot> <span class="title">
</span> <slot name="header"></slot>
<button v-if="!withOkButton" class="_button" @click="$emit('close')"><i class="ph-x-bold ph-lg"></i></button> </span>
<button v-if="withOkButton" class="_button" :disabled="okButtonDisabled" @click="$emit('ok')"><i class="ph-check-bold ph-lg"></i></button> <button v-if="!withOkButton" class="_button" @click="$emit('close')"><i class="ph-x-bold ph-lg"></i></button>
<button v-if="withOkButton" class="_button" :disabled="okButtonDisabled" @click="$emit('ok')"><i class="ph-check-bold ph-lg"></i></button>
</div>
<div class="body">
<slot :width="bodyWidth" :height="bodyHeight"></slot>
</div>
</div> </div>
<div class="body"> </FocusTrap>
<slot :width="bodyWidth" :height="bodyHeight"></slot>
</div>
</div>
</MkModal> </MkModal>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, onUnmounted } from 'vue'; import { onMounted, onUnmounted } from 'vue';
import { FocusTrap } from 'focus-trap-vue';
import MkModal from './MkModal.vue'; import MkModal from './MkModal.vue';
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{

View File

@ -16,6 +16,8 @@ importers:
cross-env: 7.0.3 cross-env: 7.0.3
cypress: 10.11.0 cypress: 10.11.0
execa: 5.1.1 execa: 5.1.1
focus-trap: ^7.2.0
focus-trap-vue: ^4.0.1
gulp: 4.0.2 gulp: 4.0.2
gulp-cssnano: 2.1.3 gulp-cssnano: 2.1.3
gulp-rename: 2.0.0 gulp-rename: 2.0.0
@ -33,6 +35,8 @@ importers:
'@bull-board/ui': 4.10.2 '@bull-board/ui': 4.10.2
'@tensorflow/tfjs': 3.21.0_seedrandom@3.0.5 '@tensorflow/tfjs': 3.21.0_seedrandom@3.0.5
calckey-js: 0.0.22 calckey-js: 0.0.22
focus-trap: 7.2.0
focus-trap-vue: 4.0.1_focus-trap@7.2.0
js-yaml: 4.1.0 js-yaml: 4.1.0
phosphor-icons: 1.4.2 phosphor-icons: 1.4.2
seedrandom: 3.0.5 seedrandom: 3.0.5
@ -3387,7 +3391,7 @@ packages:
/axios/0.25.0_debug@4.3.4: /axios/0.25.0_debug@4.3.4:
resolution: {integrity: sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==} resolution: {integrity: sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==}
dependencies: dependencies:
follow-redirects: 1.15.2 follow-redirects: 1.15.2_debug@4.3.4
transitivePeerDependencies: transitivePeerDependencies:
- debug - debug
dev: true dev: true
@ -6364,6 +6368,21 @@ packages:
readable-stream: 2.3.7 readable-stream: 2.3.7
dev: true dev: true
/focus-trap-vue/4.0.1_focus-trap@7.2.0:
resolution: {integrity: sha512-2iqOeoSvgq7Um6aL+255a/wXPskj6waLq2oKCa4gOnMORPo15JX7wN6J5bl1SMhMlTlkHXGSrQ9uJPJLPZDl5w==}
peerDependencies:
focus-trap: ^7.0.0
vue: ^3.0.0
dependencies:
focus-trap: 7.2.0
dev: false
/focus-trap/7.2.0:
resolution: {integrity: sha512-v4wY6HDDYvzkBy4735kW5BUEuw6Yz9ABqMYLuTNbzAFPcBOGiGHwwcNVMvUz4G0kgSYh13wa/7TG3XwTeT4O/A==}
dependencies:
tabbable: 6.0.1
dev: false
/follow-redirects/1.15.2: /follow-redirects/1.15.2:
resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==} resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==}
engines: {node: '>=4.0'} engines: {node: '>=4.0'}
@ -6372,6 +6391,19 @@ packages:
peerDependenciesMeta: peerDependenciesMeta:
debug: debug:
optional: true optional: true
dev: false
/follow-redirects/1.15.2_debug@4.3.4:
resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==}
engines: {node: '>=4.0'}
peerDependencies:
debug: '*'
peerDependenciesMeta:
debug:
optional: true
dependencies:
debug: 4.3.4
dev: true
/for-each/0.3.3: /for-each/0.3.3:
resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==}
@ -11986,6 +12018,10 @@ packages:
resolution: {integrity: sha512-g9rPT3V1Q4WjWFZ/t5BdGC1mT/FpYnsLdBl+M5e6MlRkuE1RSR+R43wcY/3mKI59B9KEr+vxdWCuWNMD3oNHKA==} resolution: {integrity: sha512-g9rPT3V1Q4WjWFZ/t5BdGC1mT/FpYnsLdBl+M5e6MlRkuE1RSR+R43wcY/3mKI59B9KEr+vxdWCuWNMD3oNHKA==}
dev: true dev: true
/tabbable/6.0.1:
resolution: {integrity: sha512-SYJSIgeyXW7EuX1ytdneO5e8jip42oHWg9xl/o3oTYhmXusZVgiA+VlPvjIN+kHii9v90AmzTZEBcsEvuAY+TA==}
dev: false
/tapable/2.2.1: /tapable/2.2.1:
resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==}
engines: {node: '>=6'} engines: {node: '>=6'}