From 283365be58f8e19c64fe7488fff5eaa96ef9bd5a Mon Sep 17 00:00:00 2001 From: taiyme <53635909+taiyme@users.noreply.github.com> Date: Mon, 27 May 2024 16:36:49 +0900 Subject: [PATCH] =?UTF-8?q?fix(frontend):=20=E3=83=95=E3=82=A9=E3=83=BC?= =?UTF-8?q?=E3=82=AB=E3=82=B9/=E3=82=BF=E3=83=96=E7=A7=BB=E5=8B=95?= =?UTF-8?q?=E3=81=AB=E9=96=A2=E3=81=99=E3=82=8B=E6=8C=99=E5=8B=95=E3=82=92?= =?UTF-8?q?=E8=AA=BF=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/src/components/MkButton.vue | 1 - .../src/components/MkChannelFollowButton.vue | 12 +- .../frontend/src/components/MkContextMenu.vue | 3 +- .../frontend/src/components/MkEmojiPicker.vue | 5 - .../src/components/MkFollowButton.vue | 12 +- .../src/components/MkImgWithBlurhash.vue | 4 +- .../frontend/src/components/MkMediaAudio.vue | 25 +- .../frontend/src/components/MkMediaBanner.vue | 13 +- .../frontend/src/components/MkMediaImage.vue | 25 +- .../frontend/src/components/MkMediaList.vue | 54 +++- .../frontend/src/components/MkMediaVideo.vue | 44 ++- .../frontend/src/components/MkMenu.child.vue | 5 +- packages/frontend/src/components/MkMenu.vue | 284 ++++++++++-------- packages/frontend/src/components/MkNote.vue | 97 +++--- .../src/components/MkNoteDetailed.vue | 82 +++-- .../frontend/src/components/MkPopupMenu.vue | 4 +- packages/frontend/src/components/MkSelect.vue | 27 +- .../frontend/src/components/MkSuperMenu.vue | 4 +- .../src/components/TmsAccountButton.vue | 13 +- .../frontend/src/components/TmsServerLogo.vue | 12 +- packages/frontend/src/os.ts | 49 +-- .../frontend/src/pages/drive.file.info.vue | 1 + packages/frontend/src/pages/page.vue | 1 + .../frontend/src/pages/settings/profile.vue | 1 + packages/frontend/src/scripts/focus.ts | 32 -- packages/frontend/src/scripts/hotkey.ts | 1 + packages/frontend/src/scripts/keycode.ts | 7 +- packages/frontend/src/scripts/tms/focus.ts | 70 +++++ .../frontend/src/scripts/tms/get-or-null.ts | 19 ++ packages/frontend/src/style.scss | 23 +- 30 files changed, 598 insertions(+), 332 deletions(-) delete mode 100644 packages/frontend/src/scripts/focus.ts create mode 100644 packages/frontend/src/scripts/tms/focus.ts create mode 100644 packages/frontend/src/scripts/tms/get-or-null.ts diff --git a/packages/frontend/src/components/MkButton.vue b/packages/frontend/src/components/MkButton.vue index 8f7c0f4d9ac8..669775cbe909 100644 --- a/packages/frontend/src/components/MkButton.vue +++ b/packages/frontend/src/components/MkButton.vue @@ -249,7 +249,6 @@ function onMousedown(evt: MouseEvent): void { } &:focus-visible { - outline: solid 2px var(--focus); outline-offset: 2px; } diff --git a/packages/frontend/src/components/MkChannelFollowButton.vue b/packages/frontend/src/components/MkChannelFollowButton.vue index e0966e4d444d..5069fac98c76 100644 --- a/packages/frontend/src/components/MkChannelFollowButton.vue +++ b/packages/frontend/src/components/MkChannelFollowButton.vue @@ -86,17 +86,7 @@ async function onClick() { } &:focus-visible { - &:after { - content: ""; - pointer-events: none; - position: absolute; - top: -5px; - right: -5px; - bottom: -5px; - left: -5px; - border: 2px solid var(--focus); - border-radius: 32px; - } + outline-offset: 2px; } &:hover { diff --git a/packages/frontend/src/components/MkContextMenu.vue b/packages/frontend/src/components/MkContextMenu.vue index 8ea8fa6cf3a3..b0f6bc9d8126 100644 --- a/packages/frontend/src/components/MkContextMenu.vue +++ b/packages/frontend/src/components/MkContextMenu.vue @@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only :leaveToClass="defaultStore.state.animation ? $style.transition_fade_leaveTo : ''" >
- +
@@ -28,6 +28,7 @@ import * as os from '@/os.js'; const props = defineProps<{ items: MenuItem[]; ev: MouseEvent; + returnFocusElement?: HTMLElement | null; }>(); const emit = defineEmits<{ diff --git a/packages/frontend/src/components/MkEmojiPicker.vue b/packages/frontend/src/components/MkEmojiPicker.vue index c8d7d01e5c49..9d50953e848d 100644 --- a/packages/frontend/src/components/MkEmojiPicker.vue +++ b/packages/frontend/src/components/MkEmojiPicker.vue @@ -700,11 +700,6 @@ defineExpose({ border-radius: 4px; font-size: 24px; - &:focus-visible { - outline: solid 2px var(--focus); - z-index: 1; - } - &:hover { background: rgba(0, 0, 0, 0.05); } diff --git a/packages/frontend/src/components/MkFollowButton.vue b/packages/frontend/src/components/MkFollowButton.vue index feb555214bb2..cdb74d8219ee 100644 --- a/packages/frontend/src/components/MkFollowButton.vue +++ b/packages/frontend/src/components/MkFollowButton.vue @@ -183,17 +183,7 @@ onBeforeUnmount(() => { } &:focus-visible { - &:after { - content: ""; - pointer-events: none; - position: absolute; - top: -5px; - right: -5px; - bottom: -5px; - left: -5px; - border: 2px solid var(--focus); - border-radius: 32px; - } + outline-offset: 2px; } &:hover { diff --git a/packages/frontend/src/components/MkImgWithBlurhash.vue b/packages/frontend/src/components/MkImgWithBlurhash.vue index d40bf19f0dda..8ba48ae3fbf4 100644 --- a/packages/frontend/src/components/MkImgWithBlurhash.vue +++ b/packages/frontend/src/components/MkImgWithBlurhash.vue @@ -14,8 +14,8 @@ SPDX-License-Identifier: AGPL-3.0-only :enterToClass="defaultStore.state.animation && props.transition?.enterToClass || undefined" :leaveFromClass="defaultStore.state.animation && props.transition?.leaveFromClass || undefined" > - - + + diff --git a/packages/frontend/src/components/MkMediaAudio.vue b/packages/frontend/src/components/MkMediaAudio.vue index a586730f6b39..2ef41f355fe5 100644 --- a/packages/frontend/src/components/MkMediaAudio.vue +++ b/packages/frontend/src/components/MkMediaAudio.vue @@ -61,19 +61,32 @@ SPDX-License-Identifier: AGPL-3.0-only
-
-
{{ hms(elapsedTimeMs) }}
- @@ -366,7 +379,7 @@ onDeactivated(() => { border: 0.5px solid var(--divider); border-radius: var(--mediaList-radius, 8px); - &:focus { + &:focus-visible { outline: none; } } @@ -445,6 +458,10 @@ onDeactivated(() => { color: var(--accent); background-color: var(--accentedBg); } + + &:focus-visible { + outline: none; + } } } diff --git a/packages/frontend/src/components/MkMediaBanner.vue b/packages/frontend/src/components/MkMediaBanner.vue index 4253b2fbd398..3a0ec5d139b0 100644 --- a/packages/frontend/src/components/MkMediaBanner.vue +++ b/packages/frontend/src/components/MkMediaBanner.vue @@ -49,7 +49,12 @@ SPDX-License-Identifier: AGPL-3.0-only
{{ mediaRef.name }}
- @@ -112,7 +117,7 @@ const showMediaMenu = (ev: MouseEvent) => { border: 0.5px solid var(--divider); border-radius: var(--mediaList-radius, 8px); - &:focus { + &:focus-visible { outline: none; } } @@ -215,6 +220,10 @@ const showMediaMenu = (ev: MouseEvent) => { color: var(--accent); background-color: var(--accentedBg); } + + &:focus-visible { + outline: none; + } } @container mediaBanner (max-width: 250px) { diff --git a/packages/frontend/src/components/MkMediaImage.vue b/packages/frontend/src/components/MkMediaImage.vue index 2e91e823126e..de9b2c99bfae 100644 --- a/packages/frontend/src/components/MkMediaImage.vue +++ b/packages/frontend/src/components/MkMediaImage.vue @@ -24,8 +24,9 @@ SPDX-License-Identifier: AGPL-3.0-only title: imageRef.name, class: $style.imageContainer, href: imageRef.url, - style: 'cursor: zoom-in;' + style: 'cursor: zoom-in;', }" + tabindex="-1" >
@@ -89,6 +92,8 @@ SPDX-License-Identifier: AGPL-3.0-only v-if="imageRef.comment" v-tooltip:dialog="imageRef.comment" :class="['_button', $style.controlItem]" + tabindex="-1" + @click.stop="() => {}" >
ALT
@@ -99,6 +104,8 @@ SPDX-License-Identifier: AGPL-3.0-only v-if="['image/gif', 'image/apng'].includes(imageRef.type)" v-tooltip:dialog="i18n.ts._tms.displayingGifFiles" :class="['_button', $style.controlItem]" + tabindex="-1" + @click.stop="() => {}" >
GIF
@@ -106,6 +113,8 @@ SPDX-License-Identifier: AGPL-3.0-only v-if="imageRef.isSensitive" v-tooltip:dialog="i18n.ts._tms.displayingSensitiveFiles" :class="['_button', $style.controlItem]" + tabindex="-1" + @click.stop="() => {}" >
NSFW
@@ -196,6 +205,10 @@ const showImageMenu = (ev: MouseEvent) => { overflow: hidden; // fallback (overflow: clip) overflow: clip; border-radius: var(--mediaList-radius, 8px); + + &:focus-visible { + outline: none; + } } .rootVisible { @@ -296,6 +309,10 @@ const showImageMenu = (ev: MouseEvent) => { &:last-child { padding-right: clamp(4px, calc(8px * var(--mediaImage-scale)), 8px); } + + &:focus-visible { + outline: none; + } } .controlsUpperRight { diff --git a/packages/frontend/src/components/MkMediaList.vue b/packages/frontend/src/components/MkMediaList.vue index d9d2541fe21c..295efce3d1e5 100644 --- a/packages/frontend/src/components/MkMediaList.vue +++ b/packages/frontend/src/components/MkMediaList.vue @@ -39,6 +39,7 @@ import 'photoswipe/style.css'; import { FILE_TYPE_BROWSERSAFE } from '@/const.js'; import { claimZIndex } from '@/os.js'; import { defaultStore } from '@/store.js'; +import { focusParent } from '@/scripts/tms/focus.js'; import XAudio from '@/components/MkMediaAudio.vue'; import XBanner from '@/components/MkMediaBanner.vue'; import XImage from '@/components/MkMediaImage.vue'; @@ -55,7 +56,9 @@ const gallery = shallowRef(); const pswpZIndex = claimZIndex('middle'); document.documentElement.style.setProperty('--mk-pswp-root-z-index', pswpZIndex.toString()); const count = computed(() => props.mediaList.filter(media => previewable(media)).length); -let lightbox: PhotoSwipeLightbox | null; +let lightbox: PhotoSwipeLightbox | null = null; + +let activeEl: HTMLElement | null = null; const popstateHandler = (): void => { if (lightbox?.pswp && lightbox.pswp.isOpen === true) { @@ -66,7 +69,7 @@ const popstateHandler = (): void => { async function calcAspectRatio() { if (!gallery.value) return; - let img = props.mediaList[0]; + const img = props.mediaList[0]; if (props.mediaList.length !== 1 || !(img.properties.width && img.properties.height)) { gallery.value.style.aspectRatio = ''; @@ -141,6 +144,7 @@ onMounted(() => { bgOpacity: 1, showAnimationDuration: 100, hideAnimationDuration: 100, + returnFocus: false, pswpModule: PhotoSwipe, }); @@ -169,39 +173,47 @@ onMounted(() => { lightbox.on('uiRegister', () => { lightbox?.pswp?.ui?.registerElement({ name: 'altText', - className: 'pwsp__alt-text-container', + className: 'pswp__alt-text-container', appendTo: 'wrapper', - onInit: (el, pwsp) => { - let textBox = document.createElement('p'); - textBox.className = 'pwsp__alt-text _acrylic'; + onInit: (el, pswp) => { + const textBox = document.createElement('p'); + textBox.className = 'pswp__alt-text _acrylic'; el.appendChild(textBox); - pwsp.on('change', () => { - textBox.textContent = pwsp.currSlide?.data.comment; + pswp.on('change', () => { + textBox.textContent = pswp.currSlide?.data.comment; }); }, }); }); - lightbox.init(); - - window.addEventListener('popstate', popstateHandler); - - lightbox.on('beforeOpen', () => { + lightbox.on('afterInit', () => { + activeEl = document.activeElement instanceof HTMLElement ? document.activeElement : null; + focusParent(activeEl, true, true); + lightbox?.pswp?.element?.focus({ + preventScroll: true, + }); history.pushState(null, '', '#pswp'); }); - lightbox.on('close', () => { + lightbox.on('destroy', () => { + focusParent(activeEl, true, false); + activeEl = null; if (window.location.hash === '#pswp') { history.back(); } }); + + window.addEventListener('popstate', popstateHandler); + + lightbox.init(); }); onUnmounted(() => { window.removeEventListener('popstate', popstateHandler); lightbox?.destroy(); lightbox = null; + activeEl = null; }); const previewable = (file: Misskey.entities.DriveFile): boolean => { @@ -209,6 +221,16 @@ const previewable = (file: Misskey.entities.DriveFile): boolean => { // FILE_TYPE_BROWSERSAFEに適合しないものはブラウザで表示するのに不適切 return (file.type.startsWith('video') || file.type.startsWith('image')) && FILE_TYPE_BROWSERSAFE.includes(file.type); }; + +const openGallery = () => { + if (props.mediaList.filter(media => previewable(media)).length > 0) { + lightbox?.loadAndOpen(0); + } +}; + +defineExpose({ + openGallery, +});