FOCUS TRAPPING!!!!
This commit is contained in:
parent
b818e97c67
commit
b3c0c6124e
6 changed files with 68 additions and 27 deletions
|
@ -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"
|
||||||
|
|
|
@ -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<{
|
||||||
|
|
|
@ -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, () => {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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<{
|
||||||
|
|
|
@ -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'}
|
||||||
|
|
Loading…
Reference in a new issue