feat(M02): 일위대가 · 면적산출 상세 오른쪽 패널 따로 스크롤 — follow:false · 고정 높이 · 원문 강조는 패널 몸만(85-5)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W4uKaV4ZBhsJN4GHG7yRj4
This commit is contained in:
@@ -42,3 +42,24 @@ def test_폭_자르기():
|
||||
) # fmt: skip
|
||||
assert result.returncode == 0, result.stderr
|
||||
assert json.loads(result.stdout) == [280, 500, 1360, 280]
|
||||
|
||||
|
||||
def test_따라붙기_끔_옵션():
|
||||
"""85-5 — `follow: false` = followView 안 부름 · 칸 높이 채움(is-fill) · M02 상세 둘만 씀 · M01 그대로."""
|
||||
src = PART.read_text(encoding="utf-8")
|
||||
assert (
|
||||
'if (opts.follow === false) root.classList.add("is-fill");\n else followView(root);' in src
|
||||
)
|
||||
css = PART.with_suffix(".css").read_text(encoding="utf-8")
|
||||
assert ".ui-side-panels.is-fill {" in css and "overscroll-behavior: contain" in css
|
||||
m02 = ROOT / "M02_MasterTemplete"
|
||||
for name in ("M02_MasterTemplete_AreaPanel.ts", "M02_MasterTemplete_Labor_Detail.ts"):
|
||||
assert "follow: false," in (m02 / name).read_text(encoding="utf-8"), name
|
||||
m01 = (ROOT / "M01_MasterData/M01_MasterData_UI_LogicLab_Side.ts").read_text(encoding="utf-8")
|
||||
assert "follow" not in m01
|
||||
style = (m02 / "M02_MasterTemplete_UI_Style.css").read_text(encoding="utf-8")
|
||||
assert ".m02-main:has(> .m02-main__host > .ui-side-panels-row)" in style
|
||||
source = (ROOT / "M01_MasterData/M01_MasterData_UI_LogicLab_Source.ts").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
assert 'scrollIntoView({ block: "center" })' not in source and "box.scrollTop +=" in source
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
* - handles = 패널 수(M01 2 · 일위대가 1 · 면적 2) · 손잡이 x < 패널 x(왼쪽 가장자리 밖)
|
||||
* - steps[i].open = 누른 패널 하나만 · width 모두 같음(폭 공유) · none.open = [] · none.rootW = 0(손잡이만 남음)
|
||||
* - sticky.tops = [0 · 250 · 400 굴렸을 때 패널 top] — 머리띠(head) 아래 8 에 닿으면 그 자리에 멈춤(250 = 400) · 본문이 짧으면 덜 굴러감(scrolled) · inView = 패널 위아래 끝이 창 안
|
||||
* · M02 일위대가 · 면적산출(85-5 · is-fill) = 본문 스크롤 칸이 패널 형제 본문 · tops 셋 같음(이동 0)
|
||||
* - ownScroll: 패널 몸을 굴리면 몸 scrollTop 만 바뀌고 본문 스크롤 그대로(mainMoved 0)
|
||||
* - m01Cells = m02Cells(같은 로직 GC000198 원문 표 칸 수) · 표 둘(7칸 · 2칸)
|
||||
* - m01Look = m02Look(sameLook true) — 같은 클래스 · 글자 크기(16px) · 표 칸 꼴 · tableFit 모두 true(표 높이 = 내용 · 자체 세로 스크롤 없음)
|
||||
@@ -60,7 +61,16 @@
|
||||
const panel = box(scrollId);
|
||||
const body = $(".ui-side-panels__body", panel);
|
||||
// 본문 스크롤 칸 — 위 칸 중 굴러가는 것 · 없으면 창 · 숨은 탭은 scroll 알림이 안 와 직접 보냄
|
||||
let sc = null;
|
||||
let sc = sp.classList.contains("is-fill")
|
||||
? ([...sp.parentElement.querySelectorAll("*")].find((n) => {
|
||||
const oy = getComputedStyle(n).overflowY;
|
||||
return (
|
||||
!sp.contains(n) &&
|
||||
(oy === "auto" || oy === "scroll") &&
|
||||
n.scrollHeight > n.clientHeight + 1
|
||||
);
|
||||
}) ?? null)
|
||||
: null;
|
||||
for (let n = sp.parentElement; n && !sc; n = n.parentElement) {
|
||||
const oy = getComputedStyle(n).overflowY;
|
||||
if ((oy === "auto" || oy === "scroll") && n.scrollHeight > n.clientHeight + 1) sc = n;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* 시작 화면: 패널이 이미 열린 상세 화면 하나(M02 일위대가 상세 · M02 면적산출 · M01 로직 상세 중 하나) · 화면마다 한 번씩 부름
|
||||
* 부르는 법: orca eval --expression "$(tr '\n' ' ' < 이 파일)" (async 식 · JSON 글을 돌려줌)
|
||||
* 기대: ok = true · h = want(innerHeight − max(머리띠, 칸 위)) ±2 · 칸이 올라가 붙은 뒤는 innerHeight − 머리띠 · inView = 그대로·처음·중간·끝 모두 true
|
||||
* · M02 일위대가 · 면적산출 상세(85-5 · follow:false = is-fill) — want = 칸 높이 · 본문 스크롤 칸 = 패널 형제 본문 · moved(처음~끝 top 차) = 0
|
||||
*/
|
||||
(async () => {
|
||||
const w = (ms) => new Promise((r) => setTimeout(r, ms));
|
||||
@@ -10,7 +11,17 @@
|
||||
);
|
||||
if (!sp) return JSON.stringify({ error: "펴진 패널 없음" });
|
||||
const head = document.querySelector(".app-header")?.getBoundingClientRect().bottom ?? 0;
|
||||
const fill = sp.classList.contains("is-fill");
|
||||
const scrolls = (n) =>
|
||||
["auto", "scroll"].includes(getComputedStyle(n).overflowY) &&
|
||||
n.scrollHeight > n.clientHeight + 1;
|
||||
const scroller = (() => {
|
||||
// is-fill(85-5) — 메인 = 같은 칸의 본문(패널 밖)
|
||||
if (fill)
|
||||
return (
|
||||
[...sp.parentElement.querySelectorAll("*")].find((n) => !sp.contains(n) && scrolls(n)) ??
|
||||
document.scrollingElement
|
||||
);
|
||||
for (let n = sp.parentElement; n; n = n.parentElement) {
|
||||
const y = getComputedStyle(n).overflowY;
|
||||
if ((y === "auto" || y === "scroll") && n.scrollHeight > n.clientHeight + 1) return n;
|
||||
@@ -23,9 +34,9 @@
|
||||
window.dispatchEvent(new Event("scroll")); // 뒤에 가린 탭은 스크롤 알림이 안 와서 직접
|
||||
await w(150);
|
||||
const r = sp.getBoundingClientRect();
|
||||
const want = Math.round(
|
||||
innerHeight - Math.max(head, sp.parentElement.getBoundingClientRect().top),
|
||||
);
|
||||
const want = fill
|
||||
? Math.round(sp.parentElement.getBoundingClientRect().height)
|
||||
: Math.round(innerHeight - Math.max(head, sp.parentElement.getBoundingClientRect().top));
|
||||
const body = sp.querySelector(".ui-side-panels__panel:not([hidden]) .ui-side-panels__body");
|
||||
return {
|
||||
name,
|
||||
@@ -43,7 +54,9 @@
|
||||
const still = {
|
||||
name: "그대로",
|
||||
h: Math.round(r0.height),
|
||||
want: Math.round(innerHeight - Math.max(head, sp.parentElement.getBoundingClientRect().top)),
|
||||
want: fill
|
||||
? Math.round(sp.parentElement.getBoundingClientRect().height)
|
||||
: Math.round(innerHeight - Math.max(head, sp.parentElement.getBoundingClientRect().top)),
|
||||
inView: r0.top >= head - 2 && r0.bottom <= innerHeight + 2,
|
||||
};
|
||||
const rows = [
|
||||
@@ -58,6 +71,11 @@
|
||||
head: Math.round(head),
|
||||
scrollMax: Math.round(max),
|
||||
rows,
|
||||
ok: rows.every((r) => Math.abs(r.h - r.want) <= 2 && r.inView),
|
||||
fill,
|
||||
moved:
|
||||
Math.max(...rows.slice(1).map((r) => r.top)) - Math.min(...rows.slice(1).map((r) => r.top)),
|
||||
ok:
|
||||
rows.every((r) => Math.abs(r.h - r.want) <= 2 && r.inView) &&
|
||||
(!fill || rows.slice(1).every((r) => r.top === rows[1].top)),
|
||||
});
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user