fix(ui): 좁은 폭에서는 끌어 둔 패널 자리를 잠시 물림 — 저장값은 그대로
⚠ 인라인 좌표는 미디어 규칙보다 세다. 넓은 화면에서 끌어 둔 자리가 좁은 화면에도
그대로 남아 본문을 덮었음(앞 커밋으로 폭은 줄였으나 자리는 그대로였음).
- 좁아지면 인라인 좌표를 **떼어** CSS 자리로 보냄. ⚠ **저장값은 안 지움** —
지우면 사용자가 맞춰 둔 자리가 사라져 더 나쁨.
- 넓어지면 **끌어 둔 자리를 되찾음.**
- ⚠ 좁은 폭에서 사용자가 직접 끌면 **그 뜻이 이김** — 규칙이 다시 덮지 않음.
그 자리는 **저장하지 않아** 넓은 화면 자리를 밀어내지도 않음.
실측(진행단계 패널, 저장값 {left:420, top:300} 을 심어 두고)
1400 420/300 (끌어 둔 자리) 저장값 유지
760 물러남 · 본문 남음 288px 저장값 유지
500 물러남 · 본문 남음 194px 저장값 유지
620 에서 직접 끌기 → 410/309 로 붙고, 폭을 600 으로 바꿔도 그 자리 유지
다시 1400 → 420/300 되찾음 · 저장값 안 바뀜
곁들여 — 좁은 폭 진행단계 규칙에서 `top: auto; bottom: …` 을 뺌. 위·아래가 함께
잡혀 패널이 화면 높이만큼 늘어났음(실측 717px). 폭만 줄이고 자리는 그대로 둠.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -73,12 +73,11 @@
|
||||
width: min(var(--wf-left-panel-narrow-width), calc(100vw - var(--wf-min-content-width)));
|
||||
}
|
||||
|
||||
/* ⚠ 아래로 내리려고 `top: auto; bottom: …` 을 썼더니 위·아래가 함께 잡혀 패널이
|
||||
화면 높이만큼 늘어났다(실측 717px). 폭만 줄이고 자리는 그대로 둔다. */
|
||||
.ui-workflow-overlay__panel--progress {
|
||||
top: auto;
|
||||
bottom: var(--spacing-16);
|
||||
right: var(--spacing-8);
|
||||
width: min(calc(var(--wf-left-panel-width) / 2), 22vw);
|
||||
max-height: 40vh;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,13 @@
|
||||
* 안 그러면 옮길 때마다 패널이 접힌다.
|
||||
*
|
||||
* 자리는 세션에만 남긴다(5장 데이터 3층 — 화면 조작값은 캐시 몫).
|
||||
*
|
||||
* ⚠ 좁은 폭에서는 **끌어 둔 자리를 잠시 물린다**(2026-09-08). 인라인 좌표는 미디어
|
||||
* 규칙보다 세서, 넓은 화면에서 끌어 둔 자리가 좁은 화면에서도 그대로 남아 본문을
|
||||
* 덮었다(실측: 본문에 4px 만 남았다). ⇒ 좁아지면 인라인 좌표를 **지우지 않고 잠깐
|
||||
* 떼어** CSS 자리로 보내고, 넓어지면 **끌어 둔 자리를 되찾는다.**
|
||||
* ⚠ 다만 **좁은 폭에서 사용자가 직접 끌면 그 뜻이 이긴다** — 그때는 규칙이 다시 덮지
|
||||
* 않는다. 그 자리는 저장하지 않아 넓은 화면 자리를 밀어내지도 않는다.
|
||||
*/
|
||||
|
||||
import { readByKey, writeByKey } from "../A00_Common/b_page_state";
|
||||
@@ -18,6 +25,8 @@ const DRAG_THRESHOLD_PX = 4;
|
||||
const MIN_TOP_PX = 64;
|
||||
/** 화면 가장자리에서 남겨 둘 여백. */
|
||||
const EDGE_GAP_PX = 8;
|
||||
/** 좁은 폭 경계 — `ui_template_overlay.css` 의 미디어 규칙과 같은 값이어야 한다. */
|
||||
const NARROW_MAX_PX = 860;
|
||||
|
||||
export interface PanelDragHandle {
|
||||
/** 펼침/접힘이 바뀐 뒤 열리는 방향을 다시 판정한다. */
|
||||
@@ -46,7 +55,10 @@ export function makePanelDraggable(
|
||||
header: HTMLElement,
|
||||
storageKey: string,
|
||||
): PanelDragHandle {
|
||||
/** 끌어 둔 자리(넓은 화면 기준) — 좁은 폭에서도 **지우지 않는다.** */
|
||||
let position: PanelPosition | null = readPosition(storageKey);
|
||||
/** 좁은 폭에서 사용자가 직접 끌어 둔 자리 — 저장하지 않고 그 폭에서만 쓴다. */
|
||||
let narrowPosition: PanelPosition | null = null;
|
||||
let pointerId: number | null = null;
|
||||
let startX = 0;
|
||||
let startY = 0;
|
||||
@@ -76,8 +88,33 @@ export function makePanelDraggable(
|
||||
* 자리를 인라인 좌표로 박고, 아래 공간이 모자라면 헤더 **위로** 펼치게 뒤집는다.
|
||||
* 뒤집을 때는 `bottom` 기준으로 잡아 헤더가 놓아 둔 자리에 그대로 남는다.
|
||||
*/
|
||||
function apply(next: PanelPosition): void {
|
||||
position = next;
|
||||
function isNarrow(): boolean {
|
||||
return window.innerWidth <= NARROW_MAX_PX;
|
||||
}
|
||||
|
||||
/** 인라인 좌표를 뗀다 — CSS(미디어 규칙)가 정한 자리로 돌아간다. 저장값은 그대로 둔다. */
|
||||
function detach(): void {
|
||||
root.style.left = "";
|
||||
root.style.right = "";
|
||||
root.style.top = "";
|
||||
root.style.bottom = "";
|
||||
root.classList.remove("is-flip-up");
|
||||
}
|
||||
|
||||
/** 지금 폭에 맞는 자리를 앉힌다. 좁은 폭에서 직접 끈 자리가 있으면 그것이 이긴다. */
|
||||
function settle(): void {
|
||||
if (isNarrow()) {
|
||||
if (narrowPosition) apply(clamp(narrowPosition.left, narrowPosition.top), false);
|
||||
else detach();
|
||||
return;
|
||||
}
|
||||
if (position) apply(clamp(position.left, position.top), false);
|
||||
else detach();
|
||||
}
|
||||
|
||||
function apply(next: PanelPosition, persist = true): void {
|
||||
if (persist && isNarrow()) narrowPosition = next;
|
||||
else if (persist) position = next;
|
||||
const headerHeight = header.offsetHeight;
|
||||
const body = bodyHeight();
|
||||
const spaceBelow = window.innerHeight - (next.top + headerHeight) - EDGE_GAP_PX;
|
||||
@@ -94,7 +131,9 @@ export function makePanelDraggable(
|
||||
root.style.bottom = "auto";
|
||||
root.style.top = `${next.top}px`;
|
||||
}
|
||||
writeByKey(storageKey, JSON.stringify(next));
|
||||
// ⚠ 좁은 폭에서 끈 자리는 **저장하지 않는다** — 저장하면 넓은 화면에서 맞춰 둔
|
||||
// 자리를 밀어낸다. 그 폭에 있는 동안만 쓴다.
|
||||
if (persist && !isNarrow()) writeByKey(storageKey, JSON.stringify(next));
|
||||
}
|
||||
|
||||
function swallowNextClick(): void {
|
||||
@@ -153,7 +192,8 @@ export function makePanelDraggable(
|
||||
root.classList.remove("is-dragging");
|
||||
if (!moved) return;
|
||||
// 놓는 시점에 열리는 방향을 다시 판정한다.
|
||||
if (position) apply(clamp(position.left, position.top));
|
||||
const held = isNarrow() ? narrowPosition : position;
|
||||
if (held) apply(clamp(held.left, held.top));
|
||||
swallowNextClick();
|
||||
}
|
||||
|
||||
@@ -161,11 +201,19 @@ export function makePanelDraggable(
|
||||
window.addEventListener("pointercancel", endDrag);
|
||||
|
||||
// 만들어진 직후에는 아직 DOM에 붙기 전이라 실측 크기가 0이다 — 한 프레임 뒤에 앉힌다.
|
||||
if (position) requestAnimationFrame(() => position && apply(clamp(position.left, position.top)));
|
||||
requestAnimationFrame(settle);
|
||||
|
||||
// 창 폭이 바뀌면 다시 판정한다 — 좁아지면 물러나고 넓어지면 끌어 둔 자리를 되찾는다.
|
||||
let pending = 0;
|
||||
window.addEventListener("resize", () => {
|
||||
if (pending) return;
|
||||
pending = requestAnimationFrame(() => {
|
||||
pending = 0;
|
||||
settle();
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
refresh: () => {
|
||||
if (position) apply(clamp(position.left, position.top));
|
||||
},
|
||||
refresh: settle,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user