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) {
|
||||
.fc-viewer__body { flex-direction: column; }
|
||||
/* Side panel drops below the image — the next arrow uses the full width. */
|
||||
.fc-viewer__nav--next { right: 16px; }
|
||||
/* Stacked layout (operator-flagged 2026-06-05): pin the image pane and
|
||||
its controls at the top while the metadata panel scrolls beneath it.
|
||||
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 {
|
||||
width: 100%;
|
||||
max-height: 40vh;
|
||||
max-height: none;
|
||||
border-left: none;
|
||||
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>
|
||||
|
||||
@@ -65,8 +65,17 @@ const store = useTagStore()
|
||||
// Vuetify's v-text-field exposes .focus() on the component instance;
|
||||
// nextTick waits for the modal's mount to finish so the inner <input>
|
||||
// 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)
|
||||
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
|
||||
// backend's parse_kind_prefix lives below — kept in sync with
|
||||
|
||||
Reference in New Issue
Block a user