diff --git a/packages/frontend/src/components/MkButton.vue b/packages/frontend/src/components/MkButton.vue
index 25b003ba5a59..9560efb7d95f 100644
--- a/packages/frontend/src/components/MkButton.vue
+++ b/packages/frontend/src/components/MkButton.vue
@@ -250,7 +250,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 841d37a56847..35dc3ad4bf83 100644
--- a/packages/frontend/src/components/MkChannelFollowButton.vue
+++ b/packages/frontend/src/components/MkChannelFollowButton.vue
@@ -87,17 +87,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 a807742bb9ba..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 4bd4bee1e57c..1f7892fadb69 100644
--- a/packages/frontend/src/components/MkEmojiPicker.vue
+++ b/packages/frontend/src/components/MkEmojiPicker.vue
@@ -702,11 +702,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 6a4081079c08..ea76950c0d2e 100644
--- a/packages/frontend/src/components/MkFollowButton.vue
+++ b/packages/frontend/src/components/MkFollowButton.vue
@@ -185,17 +185,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 4e3fafe845fc..617404f5c404 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 e8dfcc776898..d392d1f3e9ca 100644
--- a/packages/frontend/src/components/MkMediaAudio.vue
+++ b/packages/frontend/src/components/MkMediaAudio.vue
@@ -39,23 +39,37 @@ SPDX-License-Identifier: AGPL-3.0-only
-
-
+ {}"
+ @mousedown.prevent.stop="showMenu"
+ >
{{ hms(elapsedTimeMs) }}
-
+
@@ -64,6 +78,10 @@ SPDX-License-Identifier: AGPL-3.0-only
:class="$style.volumeSeekbar"
/>
+
{
border-radius: var(--radius);
overflow: clip;
- &:focus {
+ &:focus-visible {
outline: none;
}
}
@@ -437,6 +455,10 @@ onDeactivated(() => {
color: var(--accent);
background-color: var(--accentedBg);
}
+
+ &:focus-visible {
+ outline: none;
+ }
}
}
diff --git a/packages/frontend/src/components/MkMediaList.vue b/packages/frontend/src/components/MkMediaList.vue
index b1321a8ef99e..d890c5c86920 100644
--- a/packages/frontend/src/components/MkMediaList.vue
+++ b/packages/frontend/src/components/MkMediaList.vue
@@ -39,6 +39,7 @@ import XVideo from '@/components/MkMediaVideo.vue';
import * as os from '@/os.js';
import { FILE_TYPE_BROWSERSAFE } from '@/const.js';
import { defaultStore } from '@/store.js';
+import { focusParent } from '@/scripts/tms/focus.js';
const props = defineProps<{
mediaList: Misskey.entities.DriveFile[];
@@ -49,7 +50,9 @@ const gallery = shallowRef();
const pswpZIndex = os.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) {
@@ -60,7 +63,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 = '';
@@ -131,6 +134,7 @@ onMounted(() => {
bgOpacity: 1,
showAnimationDuration: 100,
hideAnimationDuration: 100,
+ returnFocus: false,
pswpModule: PhotoSwipe,
});
@@ -159,39 +163,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 => {
@@ -199,6 +211,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,
+});