fix(frontend): フォーカスの挙動を修正 (#14158)
* fix(frontend): 直前のパターンを記録するように * fix(frontend): フォーカス/タブ移動に関する挙動を調整 (#226) Cherry-pick commit e8c030673326871edf3623cf2b8675d68f9e1b13 Co-authored-by: taiyme <53635909+taiyme@users.noreply.github.com> * focusのデザイン修正 * move scripts * Modalにfocus trapを追加 * 記録するホットキーはレートリミット式にする * escキーのハンドリングをMkModalに統一 * fix * enterで子メニューを開けるように * lint * fix focus trap * improve switch accessibility * 一部のmodalのフォーカストラップが外れない問題を修正 * fix * fix * Revert "記録するホットキーはレートリミット式にする" This reverts commit40a7509286
. * Revert "fix(frontend): 直前のパターンを記録するように" This reverts commit5372b25940
. * Revert "Revert "fix(frontend): 直前のパターンを記録するように"" This reverts commita9bb52e799
. * Revert "Revert "記録するホットキーはレートリミット式にする"" This reverts commitbdac34273e
. * 試験的にCypressでのFocustrapを無効化 * fix * fix focus-trap * Update Changelog * ✌️ * fix focustrap invocation logic * スクロールがsticky headerを考慮するように * 🎨 * スタイルの微調整 * 🎨 * remove deprecated key aliases * focusElementが足りなかったので修正 * preview系にfocus時スタイルが足りなかったので修正 * `returnFocusElement` -> `returnFocusTo` * lint * Update packages/frontend/src/components/MkModalWindow.vue * Apply suggestions from code review Co-authored-by: taiy <53635909+taiyme@users.noreply.github.com> * keydownイベントをまとめる * use correct pesudo-element selector * fix * rename --------- Co-authored-by: taiyme <53635909+taiyme@users.noreply.github.com> Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>
This commit is contained in:
@@ -30,9 +30,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
[$style.transition_modal_leaveTo]: transitionName === 'modal',
|
||||
[$style.transition_send_leaveTo]: transitionName === 'send',
|
||||
})"
|
||||
:duration="transitionDuration" appear @afterLeave="emit('closed')" @enter="emit('opening')" @afterEnter="onOpened"
|
||||
:duration="transitionDuration" appear @afterLeave="onClosed" @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 v-show="manualShowing != null ? manualShowing : showing" ref="modalRootEl" 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 ref="content" :class="[$style.content, { [$style.fixed]: fixed }]" :style="{ zIndex }" @click.self="onBgClick">
|
||||
<slot :max-height="maxHeight" :type="type"></slot>
|
||||
@@ -48,6 +48,8 @@ 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';
|
||||
import { focusParent } from '@/scripts/focus.js';
|
||||
|
||||
function getFixedContainer(el: Element | null): Element | null {
|
||||
if (el == null || el.tagName === 'BODY') return null;
|
||||
@@ -69,6 +71,7 @@ const props = withDefaults(defineProps<{
|
||||
zPriority?: 'low' | 'middle' | 'high';
|
||||
noOverlap?: boolean;
|
||||
transparentBg?: boolean;
|
||||
returnFocusTo?: HTMLElement | null;
|
||||
}>(), {
|
||||
manualShowing: null,
|
||||
src: null,
|
||||
@@ -77,6 +80,7 @@ const props = withDefaults(defineProps<{
|
||||
zPriority: 'low',
|
||||
noOverlap: true,
|
||||
transparentBg: false,
|
||||
returnFocusTo: null,
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -94,6 +98,7 @@ const maxHeight = ref<number>();
|
||||
const fixed = ref(false);
|
||||
const transformOrigin = ref('center');
|
||||
const showing = ref(true);
|
||||
const modalRootEl = shallowRef<HTMLElement>();
|
||||
const content = shallowRef<HTMLElement>();
|
||||
const zIndex = os.claimZIndex(props.zPriority);
|
||||
const useSendAnime = ref(false);
|
||||
@@ -132,6 +137,7 @@ const transitionDuration = computed((() =>
|
||||
: 0
|
||||
));
|
||||
|
||||
let releaseFocusTrap: (() => void) | null = null;
|
||||
let contentClicking = false;
|
||||
|
||||
function close(opts: { useSendAnimation?: boolean } = {}) {
|
||||
@@ -296,6 +302,10 @@ const onOpened = () => {
|
||||
}, { passive: true });
|
||||
};
|
||||
|
||||
const onClosed = () => {
|
||||
emit('closed');
|
||||
};
|
||||
|
||||
const alignObserver = new ResizeObserver((entries, observer) => {
|
||||
align();
|
||||
});
|
||||
@@ -313,6 +323,20 @@ onMounted(() => {
|
||||
align();
|
||||
}, { immediate: true });
|
||||
|
||||
watch([showing, () => props.manualShowing], ([showing, manualShowing]) => {
|
||||
if (manualShowing === true || (manualShowing == null && showing === true)) {
|
||||
if (modalRootEl.value != null) {
|
||||
const { release } = focusTrap(modalRootEl.value);
|
||||
|
||||
releaseFocusTrap = release;
|
||||
modalRootEl.value.focus();
|
||||
}
|
||||
} else {
|
||||
releaseFocusTrap?.();
|
||||
focusParent(props.returnFocusTo ?? props.src, true, false);
|
||||
}
|
||||
}, { immediate: true });
|
||||
|
||||
nextTick(() => {
|
||||
alignObserver.observe(content.value!);
|
||||
});
|
||||
|
Reference in New Issue
Block a user