From 08ec6f42d1dd2b5a62541c4348610e437469dccf Mon Sep 17 00:00:00 2001 From: zenn Date: Mon, 6 Apr 2026 13:44:16 +0900 Subject: [PATCH] ui: measure editor sidebar height --- docs/history.md | 3 +++ docs/update.md | 4 +++ frontend/src/views/TierEditorView.vue | 37 ++++++++++++++++++++++++--- 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/docs/history.md b/docs/history.md index fab4023..b5a390e 100644 --- a/docs/history.md +++ b/docs/history.md @@ -1,5 +1,8 @@ # 의사결정 이력 +## 2026-04-06 v1.4.97 +- 티어표 편집기의 오른쪽 아이템 패널은 페이지 내부 위치가 헤더, 제목, 스크롤 상태에 따라 달라지므로 `100dvh - 고정값` 방식으로는 왼쪽 레일처럼 하단이 자연스럽게 맞지 않을 수 있다. 실제 패널의 화면 내 시작 위치를 측정해 남은 높이를 계산하는 편이 더 안정적이라고 정리했다. + ## 2026-04-06 v1.4.96 - 템플릿 제목을 버튼화하면 접근성은 좋아지지만, 포커스가 남은 상태의 `Space` 입력이 브라우저 스크롤과 섞이면 작업 화면을 갑자기 밀어낼 수 있다. 따라서 제목 버튼에서는 `Space` 기본 스크롤을 막고 의도한 본문 이동만 실행하는 편이 맞다고 정리했다. diff --git a/docs/update.md b/docs/update.md index 1dbfe04..b3aea31 100644 --- a/docs/update.md +++ b/docs/update.md @@ -1,5 +1,9 @@ # 업데이트 로그 +## 2026-04-06 v1.4.97 +- 티어표 편집 화면의 오른쪽 아이템 패널 높이를 고정 숫자 대신 실제 화면 내 시작 위치 기준으로 계산하도록 바꿨다. 공통 헤더/제목 영역/스크롤 위치가 달라져도 아이템 풀의 하단이 viewport 안에 더 자연스럽게 맞도록 보정했다. +- 확인: `npm run build` + ## 2026-04-06 v1.4.96 - 티어표 편집 화면의 템플릿 제목에 포커스가 남은 상태에서 `Space`를 누르면 브라우저 기본 스크롤이 섞일 수 있어, 제목 버튼의 `Space` 기본 동작을 막고 본문 이동만 실행되도록 보정했다. - 확인: `npm run build` diff --git a/frontend/src/views/TierEditorView.vue b/frontend/src/views/TierEditorView.vue index 0b16305..3de0457 100644 --- a/frontend/src/views/TierEditorView.vue +++ b/frontend/src/views/TierEditorView.vue @@ -90,6 +90,7 @@ let editorLoadToken = 0 const boardEl = ref(null) const exportBoardEl = ref(null) const groupListEl = ref(null) +const sidebarEl = ref(null) const poolEl = ref(null) const groupDropEls = ref({}) const fileEl = ref(null) @@ -97,6 +98,8 @@ const thumbnailFileEl = ref(null) const groupSortable = ref(null) const poolSortable = ref(null) const dropSortables = ref([]) +const editorSidebarMaxHeight = ref('') +let editorSidebarMeasureFrame = 0 const isNewTierList = computed(() => tierListId.value === 'new') const isOwnTierList = computed(() => !!auth.user && !!ownerId.value && ownerId.value === auth.user.id) @@ -364,6 +367,25 @@ function scrollWorkspaceBodyToTop() { workspaceBody?.scrollIntoView({ behavior: 'smooth', block: 'start' }) } +function updateEditorSidebarMaxHeight() { + if (typeof window === 'undefined' || !sidebarEl.value) return + const bottomGap = 14 + const stickyTop = 14 + const minHeight = 260 + const sidebarTop = Math.max(sidebarEl.value.getBoundingClientRect().top, stickyTop) + const nextHeight = Math.max(minHeight, Math.floor(window.innerHeight - sidebarTop - bottomGap)) + editorSidebarMaxHeight.value = `${nextHeight}px` +} + +function scheduleEditorSidebarMeasure() { + if (typeof window === 'undefined') return + if (editorSidebarMeasureFrame) return + editorSidebarMeasureFrame = window.requestAnimationFrame(() => { + editorSidebarMeasureFrame = 0 + updateEditorSidebarMaxHeight() + }) +} + function openItemContextMenu(itemId, event) { if (!canEdit.value || !itemId || !itemsById.value[itemId] || shouldIgnoreItemClick()) return selectedItemId.value = itemId @@ -1355,6 +1377,7 @@ async function loadEditorState() { if (loadToken !== editorLoadToken) return syncSavedEditorSnapshot() + scheduleEditorSidebarMeasure() if (canEdit.value) { await initSortables() } @@ -1374,6 +1397,9 @@ onMounted(() => { window.addEventListener('contextmenu', handleGlobalContextMenu, true) window.addEventListener('blur', closeItemContextMenu) window.addEventListener('scroll', closeItemContextMenu, true) + window.addEventListener('resize', scheduleEditorSidebarMeasure) + window.addEventListener('scroll', scheduleEditorSidebarMeasure, true) + nextTick(() => scheduleEditorSidebarMeasure()) }) onUnmounted(() => { @@ -1382,6 +1408,12 @@ onUnmounted(() => { window.removeEventListener('contextmenu', handleGlobalContextMenu, true) window.removeEventListener('blur', closeItemContextMenu) window.removeEventListener('scroll', closeItemContextMenu, true) + window.removeEventListener('resize', scheduleEditorSidebarMeasure) + window.removeEventListener('scroll', scheduleEditorSidebarMeasure, true) + if (editorSidebarMeasureFrame) { + window.cancelAnimationFrame(editorSidebarMeasureFrame) + editorSidebarMeasureFrame = 0 + } } if (thumbnailPreviewUrl.value) URL.revokeObjectURL(thumbnailPreviewUrl.value) destroySortables() @@ -1769,7 +1801,7 @@ onUnmounted(() => { -