diff --git a/browser/main.ts b/browser/main.ts index d5ab2ff..fe23001 100644 --- a/browser/main.ts +++ b/browser/main.ts @@ -12,6 +12,17 @@ locate.addEventListener("click", () => { if (!errorLocation) return; input.focus(); input.setSelectionRange(errorLocation.start, errorLocation.end); + // WebKit can move the selection without scrolling a wrapped textarea. + const measure = input.cloneNode(false) as HTMLTextAreaElement; + measure.removeAttribute("id"); + measure.className = "diagnostic-measure"; + measure.setAttribute("aria-hidden", "true"); + measure.tabIndex = -1; + measure.style.width = input.clientWidth + "px"; + measure.value = input.value.slice(0, errorLocation.start) + "\u200b"; + input.after(measure); + input.scrollTop = Math.max(0, measure.scrollHeight - input.clientHeight / 2); + measure.remove(); }); const fields = document.querySelector("#fields")!; const example = "---\nname: inventory-check\ndescription: 对照清单核对库存。\n---\n\n先读取清单,再逐项核对应有与实收数量。"; diff --git a/browser/style.css b/browser/style.css index ba74a23..8f158b3 100644 --- a/browser/style.css +++ b/browser/style.css @@ -1 +1,12 @@ * { box-sizing: border-box } body { margin: 0; color: #202c36; background: #fbfcfd; font: 16px/1.55 system-ui,sans-serif } header,main { max-width: 68rem; padding: 1rem clamp(1.15rem,4vw,3rem); margin:auto } header { display:flex; align-items:center; gap:.7rem; min-height:4.5rem; border-bottom:1px solid #d9dfe5 } header a { margin-left:auto; color:#35556e; font-size:.9rem } main { padding-top:3.2rem } h1 { font-size:clamp(1.9rem,5vw,3rem); letter-spacing:-.045em; line-height:1.1; margin:0 } h2,label { display:block; font-size:1.08rem; font-weight:600; margin:0 0 1rem } main>p,small { color:#54616e } .columns { display:grid; grid-template-columns:1.25fr 1fr; gap:1rem; margin-top:2rem } section { min-width:0; padding:clamp(1rem,3vw,1.6rem); border:1px solid #d9dfe5; border-radius:12px; background:#fff } textarea { width:100%; height:18rem; padding:1rem; border:1px solid #9aa9b7; border-radius:6px; resize:vertical; font: .94rem/1.6 ui-monospace,monospace } small { display:block; margin-top:.5rem } button { border:1px solid #284862; color:#fff; background:#284862; border-radius:6px; padding:.63rem 1rem; font:inherit; cursor:pointer } button.secondary { color:#284862; background:#fff } .actions { display:flex; gap:.6rem; flex-wrap:wrap; margin-top:1rem } dd { margin:0 0 .7rem; overflow-wrap:anywhere } dt { color:#596c7b; font-size:.9rem } :focus-visible { outline:3px solid #648dad; outline-offset:2px } @media(max-width:650px){.columns{grid-template-columns:1fr}main{padding-top:2rem}} + +textarea.diagnostic-measure { + position: fixed; + inset-inline-start: -9999px; + visibility: hidden; + min-height: 0; + height: 1px; + border: 0; + overflow: hidden; + resize: none; +}