ui: measure editor sidebar height
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
# 의사결정 이력
|
||||
|
||||
## 2026-04-06 v1.4.97
|
||||
- 티어표 편집기의 오른쪽 아이템 패널은 페이지 내부 위치가 헤더, 제목, 스크롤 상태에 따라 달라지므로 `100dvh - 고정값` 방식으로는 왼쪽 레일처럼 하단이 자연스럽게 맞지 않을 수 있다. 실제 패널의 화면 내 시작 위치를 측정해 남은 높이를 계산하는 편이 더 안정적이라고 정리했다.
|
||||
|
||||
## 2026-04-06 v1.4.96
|
||||
- 템플릿 제목을 버튼화하면 접근성은 좋아지지만, 포커스가 남은 상태의 `Space` 입력이 브라우저 스크롤과 섞이면 작업 화면을 갑자기 밀어낼 수 있다. 따라서 제목 버튼에서는 `Space` 기본 스크롤을 막고 의도한 본문 이동만 실행하는 편이 맞다고 정리했다.
|
||||
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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(() => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sidebar">
|
||||
<div ref="sidebarEl" class="sidebar" :style="{ '--editor-sidebar-max-height': editorSidebarMaxHeight || undefined }">
|
||||
<div class="sidebar__titleRow">
|
||||
<div class="sidebar__title">아이템</div>
|
||||
<div class="sidebar__count">{{ visiblePoolCount }} / {{ pool.length }}</div>
|
||||
@@ -2774,7 +2806,6 @@ onUnmounted(() => {
|
||||
object-fit: cover;
|
||||
}
|
||||
.sidebar {
|
||||
--editor-sidebar-visible-offset: 136px;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -2785,7 +2816,7 @@ onUnmounted(() => {
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04);
|
||||
position: sticky;
|
||||
top: 14px;
|
||||
max-height: calc(100dvh - var(--editor-sidebar-visible-offset));
|
||||
max-height: var(--editor-sidebar-max-height, calc(100dvh - 136px));
|
||||
overflow: hidden;
|
||||
overscroll-behavior: contain;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user