Modalにfocus trapを追加
This commit is contained in:
parent
434089521a
commit
b104485bb3
|
@ -32,7 +32,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
})"
|
})"
|
||||||
:duration="transitionDuration" appear @afterLeave="emit('closed')" @enter="emit('opening')" @afterEnter="onOpened"
|
:duration="transitionDuration" appear @afterLeave="emit('closed')" @enter="emit('opening')" @afterEnter="onOpened"
|
||||||
>
|
>
|
||||||
<div v-show="manualShowing != null ? manualShowing : showing" v-hotkey.global="keymap" :class="[$style.root, { [$style.drawer]: type === 'drawer', [$style.dialog]: type === 'dialog', [$style.popup]: type === 'popup' }]" :style="{ zIndex, pointerEvents: (manualShowing != null ? manualShowing : showing) ? 'auto' : 'none', '--transformOrigin': transformOrigin }">
|
<div ref="modalRootEl" v-show="manualShowing != null ? manualShowing : showing" v-hotkey.global="keymap" :class="[$style.root, { [$style.drawer]: type === 'drawer', [$style.dialog]: type === 'dialog', [$style.popup]: type === 'popup' }]" :style="{ zIndex, pointerEvents: (manualShowing != null ? manualShowing : showing) ? 'auto' : 'none', '--transformOrigin': transformOrigin }">
|
||||||
<div data-cy-bg :data-cy-transparent="isEnableBgTransparent" class="_modalBg" :class="[$style.bg, { [$style.bgTransparent]: isEnableBgTransparent }]" :style="{ zIndex }" @click="onBgClick" @mousedown="onBgClick" @contextmenu.prevent.stop="() => {}"></div>
|
<div data-cy-bg :data-cy-transparent="isEnableBgTransparent" class="_modalBg" :class="[$style.bg, { [$style.bgTransparent]: isEnableBgTransparent }]" :style="{ zIndex }" @click="onBgClick" @mousedown="onBgClick" @contextmenu.prevent.stop="() => {}"></div>
|
||||||
<div ref="content" :class="[$style.content, { [$style.fixed]: fixed }]" :style="{ zIndex }" @click.self="onBgClick">
|
<div ref="content" :class="[$style.content, { [$style.fixed]: fixed }]" :style="{ zIndex }" @click.self="onBgClick">
|
||||||
<slot :max-height="maxHeight" :type="type"></slot>
|
<slot :max-height="maxHeight" :type="type"></slot>
|
||||||
|
@ -48,6 +48,7 @@ import { isTouchUsing } from '@/scripts/touch.js';
|
||||||
import { defaultStore } from '@/store.js';
|
import { defaultStore } from '@/store.js';
|
||||||
import { deviceKind } from '@/scripts/device-kind.js';
|
import { deviceKind } from '@/scripts/device-kind.js';
|
||||||
import { type Keymap } from '@/scripts/hotkey.js';
|
import { type Keymap } from '@/scripts/hotkey.js';
|
||||||
|
import { focusTrap } from '@/scripts/focus-trap.js';
|
||||||
|
|
||||||
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;
|
||||||
|
@ -94,6 +95,7 @@ const maxHeight = ref<number>();
|
||||||
const fixed = ref(false);
|
const fixed = ref(false);
|
||||||
const transformOrigin = ref('center');
|
const transformOrigin = ref('center');
|
||||||
const showing = ref(true);
|
const showing = ref(true);
|
||||||
|
const modalRootEl = shallowRef<HTMLElement>();
|
||||||
const content = shallowRef<HTMLElement>();
|
const content = shallowRef<HTMLElement>();
|
||||||
const zIndex = os.claimZIndex(props.zPriority);
|
const zIndex = os.claimZIndex(props.zPriority);
|
||||||
const useSendAnime = ref(false);
|
const useSendAnime = ref(false);
|
||||||
|
@ -132,6 +134,7 @@ const transitionDuration = computed((() =>
|
||||||
: 0
|
: 0
|
||||||
));
|
));
|
||||||
|
|
||||||
|
let releaseFocusTrap: (() => void) | null = null;
|
||||||
let contentClicking = false;
|
let contentClicking = false;
|
||||||
|
|
||||||
function close(opts: { useSendAnimation?: boolean } = {}) {
|
function close(opts: { useSendAnimation?: boolean } = {}) {
|
||||||
|
@ -142,6 +145,7 @@ function close(opts: { useSendAnimation?: boolean } = {}) {
|
||||||
// eslint-disable-next-line vue/no-mutating-props
|
// eslint-disable-next-line vue/no-mutating-props
|
||||||
if (props.src) props.src.style.pointerEvents = 'auto';
|
if (props.src) props.src.style.pointerEvents = 'auto';
|
||||||
showing.value = false;
|
showing.value = false;
|
||||||
|
releaseFocusTrap?.();
|
||||||
emit('close');
|
emit('close');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -283,6 +287,13 @@ const onOpened = () => {
|
||||||
// NOTE: Chromatic テストの際に undefined になる場合がある
|
// NOTE: Chromatic テストの際に undefined になる場合がある
|
||||||
if (content.value == null) return;
|
if (content.value == null) return;
|
||||||
|
|
||||||
|
if (modalRootEl.value != null) {
|
||||||
|
const { release } = focusTrap(modalRootEl.value);
|
||||||
|
|
||||||
|
releaseFocusTrap = release;
|
||||||
|
modalRootEl.value.focus();
|
||||||
|
}
|
||||||
|
|
||||||
// モーダルコンテンツにマウスボタンが押され、コンテンツ外でマウスボタンが離されたときにモーダルバックグラウンドクリックと判定させないためにマウスイベントを監視しフラグ管理する
|
// モーダルコンテンツにマウスボタンが押され、コンテンツ外でマウスボタンが離されたときにモーダルバックグラウンドクリックと判定させないためにマウスイベントを監視しフラグ管理する
|
||||||
const el = content.value.children[0];
|
const el = content.value.children[0];
|
||||||
el.addEventListener('mousedown', ev => {
|
el.addEventListener('mousedown', ev => {
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
import { getHTMLElementOrNull } from '@/scripts/get-or-null.js';
|
||||||
|
|
||||||
|
function releaseFocusTrap(el: HTMLElement): void {
|
||||||
|
if (el.parentElement && el.parentElement !== document.body) {
|
||||||
|
el.parentElement.childNodes.forEach((siblingNode) => {
|
||||||
|
const siblingEl = getHTMLElementOrNull(siblingNode);
|
||||||
|
if (!siblingEl) return;
|
||||||
|
if (siblingEl !== el) {
|
||||||
|
siblingEl.inert = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
releaseFocusTrap(el.parentElement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function focusTrap(el: HTMLElement): { release: () => void; } {
|
||||||
|
if (el.parentElement && el.parentElement !== document.body) {
|
||||||
|
el.parentElement.childNodes.forEach((siblingNode) => {
|
||||||
|
const siblingEl = getHTMLElementOrNull(siblingNode);
|
||||||
|
if (!siblingEl) return;
|
||||||
|
if (siblingEl !== el) {
|
||||||
|
siblingEl.inert = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
focusTrap(el.parentElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
release: () => {
|
||||||
|
releaseFocusTrap(el);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
Loading…
Reference in New Issue