Proper use of focus trap
This commit is contained in:
parent
be6a640edb
commit
c05f151b1a
4 changed files with 63 additions and 84 deletions
|
@ -1,10 +1,12 @@
|
||||||
<template>
|
<template>
|
||||||
<FocusTrap
|
<FocusTrap
|
||||||
:active="false"
|
|
||||||
ref="focusTrap"
|
ref="focusTrap"
|
||||||
|
v-model:active="isActive"
|
||||||
:return-focus-on-deactivate="!noReturnFocus"
|
:return-focus-on-deactivate="!noReturnFocus"
|
||||||
|
:initial-focus="() => itemsEl.children[0]"
|
||||||
|
@deactivate="emit('close')"
|
||||||
>
|
>
|
||||||
<div tabindex="-1">
|
<div>
|
||||||
<div
|
<div
|
||||||
ref="itemsEl"
|
ref="itemsEl"
|
||||||
class="rrevdjwt _popup _shadow"
|
class="rrevdjwt _popup _shadow"
|
||||||
|
@ -14,6 +16,7 @@
|
||||||
maxHeight: maxHeight ? maxHeight + 'px' : '',
|
maxHeight: maxHeight ? maxHeight + 'px' : '',
|
||||||
}"
|
}"
|
||||||
@contextmenu.self="(e) => e.preventDefault()"
|
@contextmenu.self="(e) => e.preventDefault()"
|
||||||
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<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>
|
||||||
|
@ -173,6 +176,7 @@
|
||||||
:root-element="itemsEl"
|
:root-element="itemsEl"
|
||||||
showing
|
showing
|
||||||
@actioned="childActioned"
|
@actioned="childActioned"
|
||||||
|
@closed="closeChild"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -303,23 +307,7 @@ function close(actioned = false) {
|
||||||
emit("close", actioned);
|
emit("close", actioned);
|
||||||
}
|
}
|
||||||
|
|
||||||
function focusUp() {
|
|
||||||
focusPrev(document.activeElement);
|
|
||||||
}
|
|
||||||
|
|
||||||
function focusDown() {
|
|
||||||
focusNext(document.activeElement);
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
focusTrap.value.activate();
|
|
||||||
|
|
||||||
if (props.viaKeyboard) {
|
|
||||||
nextTick(() => {
|
|
||||||
focusNext(itemsEl.children[0], true, false);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener("mousedown", onGlobalMousedown, {
|
document.addEventListener("mousedown", onGlobalMousedown, {
|
||||||
passive: true,
|
passive: true,
|
||||||
});
|
});
|
||||||
|
|
|
@ -14,17 +14,17 @@
|
||||||
:duration="transitionDuration"
|
:duration="transitionDuration"
|
||||||
appear
|
appear
|
||||||
@after-leave="emit('closed')"
|
@after-leave="emit('closed')"
|
||||||
@keyup.esc="emit('click')"
|
|
||||||
@enter="emit('opening')"
|
@enter="emit('opening')"
|
||||||
@after-enter="onOpened"
|
@after-enter="onOpened"
|
||||||
>
|
>
|
||||||
<FocusTrap
|
<FocusTrap
|
||||||
v-model:active="isActive"
|
v-model:active="isActive"
|
||||||
|
:initial-focus="() => $refs.content"
|
||||||
:return-focus-on-deactivate="!noReturnFocus"
|
:return-focus-on-deactivate="!noReturnFocus"
|
||||||
|
@deactivate="close"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-show="manualShowing != null ? manualShowing : showing"
|
v-show="manualShowing != null ? manualShowing : showing"
|
||||||
v-hotkey.global="keymap"
|
|
||||||
:class="[
|
:class="[
|
||||||
$style.root,
|
$style.root,
|
||||||
{
|
{
|
||||||
|
@ -44,7 +44,6 @@
|
||||||
'--transformOrigin': transformOrigin,
|
'--transformOrigin': transformOrigin,
|
||||||
}"
|
}"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
v-focus
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="_modalBg data-cy-bg"
|
class="_modalBg data-cy-bg"
|
||||||
|
@ -180,7 +179,7 @@ let transitionDuration = $computed(() =>
|
||||||
|
|
||||||
let contentClicking = false;
|
let contentClicking = false;
|
||||||
|
|
||||||
const focusedElement = document.activeElement;
|
// const focusedElement = document.activeElement;
|
||||||
function close(ev, opts: { useSendAnimation?: boolean } = {}) {
|
function close(ev, opts: { useSendAnimation?: boolean } = {}) {
|
||||||
// removeEventListener("popstate", close);
|
// removeEventListener("popstate", close);
|
||||||
// if (props.preferType == "dialog") {
|
// if (props.preferType == "dialog") {
|
||||||
|
@ -194,16 +193,16 @@ function close(ev, opts: { useSendAnimation?: boolean } = {}) {
|
||||||
if (props.src) props.src.style.pointerEvents = "auto";
|
if (props.src) props.src.style.pointerEvents = "auto";
|
||||||
showing = false;
|
showing = false;
|
||||||
emit("close");
|
emit("close");
|
||||||
if (!props.noReturnFocus) {
|
// if (!props.noReturnFocus) {
|
||||||
focusedElement.focus();
|
// focusedElement.focus();
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
function onBgClick() {
|
function onBgClick() {
|
||||||
if (contentClicking) return;
|
if (contentClicking) return;
|
||||||
if (!props.noReturnFocus) {
|
// if (!props.noReturnFocus) {
|
||||||
focusedElement.focus();
|
// focusedElement.focus();
|
||||||
}
|
// }
|
||||||
emit("click");
|
emit("click");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -211,10 +210,6 @@ if (type === "drawer") {
|
||||||
maxHeight = window.innerHeight / 1.5;
|
maxHeight = window.innerHeight / 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
const keymap = {
|
|
||||||
esc: () => emit("esc"),
|
|
||||||
};
|
|
||||||
|
|
||||||
const MARGIN = 16;
|
const MARGIN = 16;
|
||||||
|
|
||||||
const align = () => {
|
const align = () => {
|
||||||
|
|
|
@ -6,58 +6,55 @@
|
||||||
@keyup.esc="$emit('close')"
|
@keyup.esc="$emit('close')"
|
||||||
@closed="$emit('closed')"
|
@closed="$emit('closed')"
|
||||||
>
|
>
|
||||||
<FocusTrap v-model:active="isActive">
|
<div
|
||||||
<div
|
ref="rootEl"
|
||||||
ref="rootEl"
|
class="ebkgoccj"
|
||||||
class="ebkgoccj"
|
:style="{
|
||||||
:style="{
|
width: `${width}px`,
|
||||||
width: `${width}px`,
|
height: scroll
|
||||||
height: scroll
|
? height
|
||||||
? height
|
? `${height}px`
|
||||||
? `${height}px`
|
: null
|
||||||
: null
|
: height
|
||||||
: height
|
? `min(${height}px, 100%)`
|
||||||
? `min(${height}px, 100%)`
|
: '100%',
|
||||||
: '100%',
|
}"
|
||||||
}"
|
tabindex="-1"
|
||||||
@keydown="onKeydown"
|
>
|
||||||
tabindex="-1"
|
<div ref="headerEl" class="header">
|
||||||
>
|
<button
|
||||||
<div ref="headerEl" class="header">
|
v-if="withOkButton"
|
||||||
<button
|
:aria-label="i18n.t('close')"
|
||||||
v-if="withOkButton"
|
class="_button"
|
||||||
:aria-label="i18n.t('close')"
|
@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
|
:aria-label="i18n.t('close')"
|
||||||
v-if="!withOkButton"
|
class="_button"
|
||||||
:aria-label="i18n.t('close')"
|
@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
|
:aria-label="i18n.t('ok')"
|
||||||
v-if="withOkButton"
|
class="_button"
|
||||||
:aria-label="i18n.t('ok')"
|
: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></slot>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</FocusTrap>
|
<div class="body">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</MkModal>
|
</MkModal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,6 @@
|
||||||
@click="modal.close()"
|
@click="modal.close()"
|
||||||
@closed="emit('closed')"
|
@closed="emit('closed')"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
v-focus
|
|
||||||
>
|
>
|
||||||
<MkMenu
|
<MkMenu
|
||||||
:items="items"
|
:items="items"
|
||||||
|
|
Loading…
Reference in a new issue