fix(modal): mobile — no tag autofocus + sticky image over scrolling panel
Operator-flagged 2026-06-05 (mobile): - TagAutocomplete no longer autofocuses on the ≤900px stacked layout — focusing popped the soft keyboard, shrinking the viewport and shoving the pinned image + nav/close controls out of view. matchMedia gate (safe on plain HTTP); desktop autofocus unchanged. - ImageViewer stacked layout: the body now scrolls and the media pane is sticky (height:55vh, top:0), so the image + prev/next/close + integrity badge stay pinned while the metadata panel scrolls beneath. Replaces the old fixed image + 40vh internally-scrolled panel that could push the controls off. Prev/next re-centered over the image band; opaque obsidian bg so the scrolling panel doesn't bleed through. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -203,14 +203,37 @@ function nextFrame() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 900px) {
|
@media (max-width: 900px) {
|
||||||
.fc-viewer__body { flex-direction: column; }
|
/* Stacked layout (operator-flagged 2026-06-05): pin the image pane and
|
||||||
/* Side panel drops below the image — the next arrow uses the full width. */
|
its controls at the top while the metadata panel scrolls beneath it.
|
||||||
.fc-viewer__nav--next { right: 16px; }
|
Previously the image + a 40vh panel shared one fixed viewport and the
|
||||||
|
controls could be pushed out of view; now the body scrolls and the
|
||||||
|
media is sticky, so the image + prev/next/close (and the integrity
|
||||||
|
badge) stay visible no matter how far the panel is scrolled. */
|
||||||
|
.fc-viewer__body {
|
||||||
|
flex-direction: column;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
.fc-viewer__media {
|
||||||
|
flex: none;
|
||||||
|
height: 55vh;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 1;
|
||||||
|
/* Opaque obsidian so the scrolling panel never bleeds through the
|
||||||
|
haze behind the pinned image. */
|
||||||
|
background: rgb(20, 23, 26);
|
||||||
|
}
|
||||||
.fc-viewer__side {
|
.fc-viewer__side {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-height: 40vh;
|
max-height: none;
|
||||||
border-left: none;
|
border-left: none;
|
||||||
border-top: 1px solid rgb(var(--v-theme-surface-light));
|
border-top: 1px solid rgb(var(--v-theme-surface-light));
|
||||||
}
|
}
|
||||||
|
/* Re-center the prev/next arrows over the 55vh image band (their base
|
||||||
|
top:50% would land on the scrolling panel); next uses the full width
|
||||||
|
now that the panel is below, not beside. Close + integrity badge keep
|
||||||
|
their top:16px/72px — already within the pinned band. */
|
||||||
|
.fc-viewer__nav { top: 27.5vh; }
|
||||||
|
.fc-viewer__nav--next { right: 16px; }
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -65,8 +65,17 @@ const store = useTagStore()
|
|||||||
// Vuetify's v-text-field exposes .focus() on the component instance;
|
// Vuetify's v-text-field exposes .focus() on the component instance;
|
||||||
// nextTick waits for the modal's mount to finish so the inner <input>
|
// nextTick waits for the modal's mount to finish so the inner <input>
|
||||||
// element exists.
|
// element exists.
|
||||||
|
//
|
||||||
|
// EXCEPTION — not on mobile (operator-flagged 2026-06-05): focusing the
|
||||||
|
// field pops the soft keyboard, which shrinks the visual viewport and
|
||||||
|
// shoves the pinned image + its nav/close controls out of view. 900px is
|
||||||
|
// the modal's stacked-layout breakpoint (ImageViewer.vue). matchMedia is
|
||||||
|
// safe on plain HTTP (not a secure-context API).
|
||||||
const inputRef = ref(null)
|
const inputRef = ref(null)
|
||||||
onMounted(() => { nextTick(() => inputRef.value?.focus?.()) })
|
onMounted(() => {
|
||||||
|
if (window.matchMedia?.('(max-width: 900px)')?.matches) return
|
||||||
|
nextTick(() => inputRef.value?.focus?.())
|
||||||
|
})
|
||||||
|
|
||||||
// Single text input; no kind dropdown. Client-side mirror of the
|
// Single text input; no kind dropdown. Client-side mirror of the
|
||||||
// backend's parse_kind_prefix lives below — kept in sync with
|
// backend's parse_kind_prefix lives below — kept in sync with
|
||||||
|
|||||||
Reference in New Issue
Block a user