feat(sw): 3-column property compare, clamp Overview panel, narrower viewer file name

- Property Panel compare page: from 1080px, none/all stacked in column 1,
  circle and expanded side by side (2x2 and 1 column below)
- Overview Stage: the property panel is clamped to the Stage height so the
  Stage gets no scrollbar; the panel body scrolls
- Viewer header file name: clamp(140px, calc(45cqw - 256px), 360px) so a
  950px header shows no More; 1280px and wider unchanged
This commit is contained in:
shkim4 committed 2026-10-26 19:36:08 +09:00
1 parent 6780abb42d
commit 7976801034
7 files changed
+43 -20

No files matched your search

+6 -4
View File
@@ -164,9 +164,11 @@ button.hm-sw-viewer-header__brand { text-align: left; }
display: flex;
align-items: center;
/* Very large shrink weight (2026-10-23): the file name gives way first, as before; the object (shrink 1) only once
the file name is at its minimum (see .hm-sw-viewer-header__object). */
flex: 0 1000000 clamp(140px, 25cqw, 360px);
width: clamp(220px, 25cqw, 360px);
the file name is at its minimum (see .hm-sw-viewer-header__object).
2026-10-26 (user request): narrower below 1280px (171px at 950px, was 25cqw = 237px) so a 950px header has no More;
from 1280px the same as before (320px, 360px from 1370px). The measurement (data-measuring) uses the same value. */
flex: 0 1000000 clamp(140px, calc(45cqw - 256px), 360px);
width: clamp(220px, calc(45cqw - 256px), 360px);
min-width: 56px;
height: calc(var(--hm-sw-viewer-height) - 2px);
padding: 0 16px 0 24px;
@@ -312,7 +314,7 @@ button.hm-sw-viewer-header__brand { text-align: left; }
Every part at its natural width: the file name at its usual flex basis (2026-10-21 user request: the header width
decides it, never the length of the name, so a short and a long name give the same steps, Open label and widths),
the object surface neither stretched nor shrunk, the trailing area without its auto margin. */
.hm-sw-viewer-header[data-measuring] .hm-sw-viewer-header__document { flex: 0 0 clamp(140px, 25cqw, 360px); }
.hm-sw-viewer-header[data-measuring] .hm-sw-viewer-header__document { flex: 0 0 clamp(140px, calc(45cqw - 256px), 360px); }
.hm-sw-viewer-header[data-measuring] .hm-sw-viewer-header__object { flex: 0 0 auto; }
.hm-sw-viewer-header[data-measuring] .hm-sw-viewer-header__actions { margin-left: 0; }
/* While the logo menu or the document list is open, the header takes the viewer dropdown layer (SWViewerMenu, 60) so
@@ -47,10 +47,11 @@ try {
await evaluate('(()=>{const s=document.querySelector("select");s.value="expanded";s.dispatchEvent(new Event("change",{bubbles:true}));return true})()');await settle()
await evaluate('(()=>{const b=document.querySelector("[data-property-panel-trigger]");if(b.getClientRects().length)b.click();else{document.querySelector(".hm-sw-header__more-trigger").click()}return true})()');await settle()
await evaluate('(()=>{const m=[...document.querySelectorAll("[role=menuitemcheckbox]")].find(e=>e.textContent.trim()==="속성창");if(m)m.click();return true})()');await settle()
const popup=await data('(()=>{const e=document.querySelector(".hm-sw-property"),r=e.getBoundingClientRect();return {width:r.width,height:r.height,focus:document.activeElement===e}})()')
check(`Firefox ${width}: full original popup 314x754`,popup.width===314&&popup.height===754,popup)
const bottom=await data('(()=>{const z=document.querySelector(".hm-sw-property [data-property-id=z] input"),s=document.querySelector("[data-preview-stage]");z.scrollIntoView({block:"nearest",inline:"nearest"});z.focus();const r=z.getBoundingClientRect();return {top:s.scrollTop,visible:document.elementFromPoint(r.x+r.width/2,r.y+r.height/2)===z}})()')
check(`Firefox ${width}: popup bottom reachable and focusable`,bottom.top>0&&bottom.visible,bottom)
// 2026-10-26 (user request): the popup is clamped to the Stage room, so the Stage gets no scrollbar.
const popup=await data('(()=>{const e=document.querySelector(".hm-sw-property"),r=e.getBoundingClientRect(),c=getComputedStyle(document.querySelector("[data-preview-stage]"));return {width:r.width,height:r.height,limit:parseFloat(c.getPropertyValue("--sw-preview-stage-height"))-parseFloat(c.getPropertyValue("--sw-preview-panel-top"))-4,focus:document.activeElement===e}})()')
check(`Firefox ${width}: popup 314 x min(754, Stage room)`,popup.width===314&&Math.abs(popup.height-Math.min(754,popup.limit))<.5,popup)
const bottom=await data('(()=>{const z=document.querySelector(".hm-sw-property [data-property-id=z] input"),s=document.querySelector("[data-preview-stage]");z.scrollIntoView({block:"nearest",inline:"nearest"});z.focus();const r=z.getBoundingClientRect();return {top:s.scrollTop,stageScroll:s.scrollHeight>s.clientHeight,visible:document.elementFromPoint(r.x+r.width/2,r.y+r.height/2)===z}})()')
check(`Firefox ${width}: popup bottom reachable and focusable without Stage scroll`,!bottom.stageScroll&&bottom.visible,bottom)
await key('\uE00C');await settle()
check(`Firefox ${width}: Escape closes popup and restores focus`,await evaluate('!document.querySelector(".hm-sw-property")&&document.activeElement.matches("[data-property-panel-trigger],.hm-sw-header__more-trigger")'))
await writeFile(path.join(out,`overview-firefox-${width}.png`),Buffer.from((await send('browsingContext.captureScreenshot',{context})).data,'base64'))
+5 -3
View File
@@ -211,7 +211,9 @@ try {
await page.getByLabel('선택 상태',{exact:true}).selectOption(selection)
if(await page.locator('.hm-sw-property').count()===0)await panelButton()
const box=await page.locator('.hm-sw-property').boundingBox()
check(`original panel size ${selection}:314x${height}`,box.width===314&&box.height===height,box)
// 2026-10-26 (user request): no Stage scrollbar when the panel opens; a panel taller than the Stage room is clamped.
const room=await stage.evaluate(e=>{const s=getComputedStyle(e);return {limit:parseFloat(s.getPropertyValue('--sw-preview-stage-height'))-parseFloat(s.getPropertyValue('--sw-preview-panel-top'))-4,scrollHeight:e.scrollHeight,height:e.clientHeight}})
check(`panel size ${selection}:314 x min(${height}, Stage room), no Stage scroll`,box.width===314&&Math.abs(box.height-Math.min(height,room.limit))<.5&&room.scrollHeight<=room.height,{box,room})
}
await page.screenshot({path:path.join(out,'overview-after-expanded.png')})
await stage.evaluate(e=>{e.scrollTop=e.scrollHeight})
@@ -219,11 +221,11 @@ try {
check('expanded popup bottom accessible through internal scroll',await panelInput('z').inputValue()==='5')
await page.screenshot({path:path.join(out,'overview-after-scrolled.png')})
await go();await page.screenshot({path:path.join(out,'overview-after.png')})
// A small Inner keeps native component dimensions; the Stage, rather than the browser document, scrolls.
// A small Inner keeps the native Header width; the Stage, rather than the browser document, scrolls sideways only.
await page.setViewportSize({width:390,height:700});await go();await page.getByLabel('선택 상태',{exact:true}).selectOption('expanded');await panelButton()
await panelInput('z').scrollIntoViewIfNeeded();await panelInput('z').fill('3');await panelInput('z').press('Enter')
const narrow=await stage.evaluate(e=>({top:e.scrollTop,left:e.scrollLeft,height:e.clientHeight,scrollHeight:e.scrollHeight,width:e.clientWidth,scrollWidth:e.scrollWidth}))
check('small Inner reaches full popup bottom with native Header and popup dimensions',await panelInput('z').inputValue()==='3'&&narrow.scrollHeight>narrow.height&&narrow.scrollWidth>=390,narrow)
check('small Inner reaches the popup bottom through the panel body, no Stage vertical scroll',await panelInput('z').inputValue()==='3'&&narrow.scrollHeight<=narrow.height&&narrow.scrollWidth>=390,narrow)
await page.screenshot({path:path.join(out,'overview-narrow.png')})
await panelInput('z').press('Escape')
check('narrow popup Escape restores More focus',await header.getByRole('button',{name:'더보기',exact:true}).evaluate(e=>document.activeElement===e))
@@ -90,8 +90,14 @@ try {
const colGap = b.cell.x - a.cell.right, colGap2 = d.cell.x - c.cell.right, rowGap = c.cell.y - Math.max(a.cell.bottom, b.cell.bottom)
report.data[`gaps${width}`] = { colGap, colGap2, rowGap }
// 2026-10-22 (user request): no Inner scroller; the page scrolls. 2 columns × 2 rows, one column below 720px.
const oneColumn = width < 720
if (oneColumn) check(`${width}: one column in order`, [a, b, c, d].every((cell, i, all) => i === 0 || (Math.abs(cell.cell.x - a.cell.x) < 0.5 && cell.cell.y >= all[i - 1].cell.bottom)), { a: a.cell, b: b.cell, c: c.cell, d: d.cell })
// 2026-10-26 (user request): from 1080px, 3 columns — none/all stacked in column 1, circle and expanded beside them.
const oneColumn = width < 720, threeColumns = width >= 1080
if (threeColumns) {
const gaps = { stack: b.cell.y - a.cell.bottom, col1: c.cell.x - Math.max(a.cell.right, b.cell.right), col2: d.cell.x - c.cell.right }
report.data[`gaps${width}`] = gaps
check(`${width}: 3 columns — none/all stacked, circle and expanded side by side`, Math.abs(a.cell.x - b.cell.x) < 0.5 && Math.abs(a.cell.y - c.cell.y) < 0.5 && Math.abs(a.cell.y - d.cell.y) < 0.5 && c.cell.x >= a.cell.right && d.cell.x >= c.cell.right, { a: a.cell, b: b.cell, c: c.cell, d: d.cell })
check(`${width}: consistent 24px stack and column gaps`, Object.values(gaps).every(v => Math.abs(v - 24) < 0.5), gaps)
} else if (oneColumn) check(`${width}: one column in order`, [a, b, c, d].every((cell, i, all) => i === 0 || (Math.abs(cell.cell.x - a.cell.x) < 0.5 && cell.cell.y >= all[i - 1].cell.bottom)), { a: a.cell, b: b.cell, c: c.cell, d: d.cell })
else {
check(`${width}: 2 columns × 2 rows by coordinates`, Math.abs(a.cell.y - b.cell.y) < 0.5 && Math.abs(c.cell.y - d.cell.y) < 0.5 && Math.abs(a.cell.x - c.cell.x) < 0.5 && Math.abs(b.cell.x - d.cell.x) < 0.5 && b.cell.x >= a.cell.right && c.cell.y >= Math.max(a.cell.bottom, b.cell.bottom), { a: a.cell, b: b.cell, c: c.cell, d: d.cell })
check(`${width}: consistent 24px column and row gaps`, [colGap, colGap2, rowGap].every(v => Math.abs(v - 24) < 0.5), { colGap, colGap2, rowGap })