diff --git a/packages/frontend/src/components/MkModal.vue b/packages/frontend/src/components/MkModal.vue index 264d8b6c9c..8d2462d5ee 100644 --- a/packages/frontend/src/components/MkModal.vue +++ b/packages/frontend/src/components/MkModal.vue @@ -32,7 +32,7 @@ SPDX-License-Identifier: AGPL-3.0-only })" :duration="transitionDuration" appear @afterLeave="emit('closed')" @enter="emit('opening')" @afterEnter="onOpened" > -
+
@@ -48,6 +48,7 @@ import { isTouchUsing } from '@/scripts/touch.js'; import { defaultStore } from '@/store.js'; import { deviceKind } from '@/scripts/device-kind.js'; import { type Keymap } from '@/scripts/hotkey.js'; +import { focusTrap } from '@/scripts/focus-trap.js'; function getFixedContainer(el: Element | null): Element | null { if (el == null || el.tagName === 'BODY') return null; @@ -94,6 +95,7 @@ const maxHeight = ref(); const fixed = ref(false); const transformOrigin = ref('center'); const showing = ref(true); +const modalRootEl = shallowRef(); const content = shallowRef(); const zIndex = os.claimZIndex(props.zPriority); const useSendAnime = ref(false); @@ -132,6 +134,7 @@ const transitionDuration = computed((() => : 0 )); +let releaseFocusTrap: (() => void) | null = null; let contentClicking = false; function close(opts: { useSendAnimation?: boolean } = {}) { @@ -142,6 +145,7 @@ function close(opts: { useSendAnimation?: boolean } = {}) { // eslint-disable-next-line vue/no-mutating-props if (props.src) props.src.style.pointerEvents = 'auto'; showing.value = false; + releaseFocusTrap?.(); emit('close'); } @@ -283,6 +287,13 @@ const onOpened = () => { // NOTE: Chromatic テストの際に undefined になる場合がある 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]; el.addEventListener('mousedown', ev => { diff --git a/packages/frontend/src/scripts/focus-trap.ts b/packages/frontend/src/scripts/focus-trap.ts new file mode 100644 index 0000000000..efd009af09 --- /dev/null +++ b/packages/frontend/src/scripts/focus-trap.ts @@ -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); + }, + }; +}