diff --git a/B07_DesignDetail/B07_DesignDetail_UI_Settings_Scale.ts b/B07_DesignDetail/B07_DesignDetail_UI_Settings_Scale.ts index 28352bd08..c868e41bb 100644 --- a/B07_DesignDetail/B07_DesignDetail_UI_Settings_Scale.ts +++ b/B07_DesignDetail/B07_DesignDetail_UI_Settings_Scale.ts @@ -116,14 +116,22 @@ function buildScaleFields( void save({ x: scaleToSend(x, fields[0].fallback), y: scaleToSend(y, fields[1].fallback) }); }, }); + // 칸 밑 오류 글은 숨기고(틀린 칸만 길어짐) 줄 아래 한 줄로 알림 + const alertLine = document.createElement("p"); + alertLine.className = "b07-scale__error"; + alertLine.setAttribute("role", "alert"); + alertLine.hidden = true; const check = (): void => { - let bad = false; + const errors = new Map(); for (const f of fields) { const result = f.read(); - f.field.setError(typeof result === "string" ? result : undefined); - bad ||= typeof result === "string"; + const message = typeof result === "string" ? result : undefined; + f.field.setError(message); + if (message) errors.set(message, [...(errors.get(message) ?? []), f.axis.toUpperCase()]); } - saveButton.disabled = bad; + alertLine.textContent = [...errors].map(([m, axes]) => `${axes.join(" · ")}: ${m}`).join(" / "); + alertLine.hidden = errors.size === 0; + saveButton.disabled = errors.size > 0; }; for (const f of fields) { f.field.input.addEventListener("input", check); @@ -153,5 +161,5 @@ function buildScaleFields( const buttons = document.createElement("div"); buttons.className = "b07-scale__actions"; buttons.append(reset, saveButton); - section.append(title, row, buttons); + section.append(title, row, alertLine, buttons); } diff --git a/B07_DesignDetail/B07_DesignDetail_UI_Style.css b/B07_DesignDetail/B07_DesignDetail_UI_Style.css index 7ac6e74ca..08a7af3f9 100644 --- a/B07_DesignDetail/B07_DesignDetail_UI_Style.css +++ b/B07_DesignDetail/B07_DesignDetail_UI_Style.css @@ -480,7 +480,7 @@ html.is-bm-route .b07-cad-stack > .cad-tabs { top: 0; right: var(--cad-tab-slot-right, 0px); left: var(--cad-tab-slot-left, 0px); - height: 26px; + height: 25px; padding: 2px 4px 0; border-bottom: none; background: transparent; @@ -522,6 +522,16 @@ html.is-bm-route .b07-cad-stack > .cad-tabs { flex: 1; } +.b07-scale .ui-field__error { + display: none; +} + +.b07-scale__error { + margin: 0; + color: var(--color-danger); + font-size: 12px; +} + .b07-scale__actions { display: flex; justify-content: flex-end; diff --git a/B07_DesignDetail/openwebcad/src/App.css b/B07_DesignDetail/openwebcad/src/App.css index 1554c60cf..c4b983701 100644 --- a/B07_DesignDetail/openwebcad/src/App.css +++ b/B07_DesignDetail/openwebcad/src/App.css @@ -105,7 +105,7 @@ body > canvas[data-id="canvas"] { gap: 2px; height: 26px; padding: 0 8px; - border-bottom: 1px solid var(--cad-line); + border-bottom: 1px solid var(--color-smoke); } .cad-ribbon-tabs__slot { flex: 1; diff --git a/B07_DesignDetail/openwebcad/src/components/QuickAccessBar.tsx b/B07_DesignDetail/openwebcad/src/components/QuickAccessBar.tsx index 046c48930..c99d8e61c 100644 --- a/B07_DesignDetail/openwebcad/src/components/QuickAccessBar.tsx +++ b/B07_DesignDetail/openwebcad/src/components/QuickAccessBar.tsx @@ -19,10 +19,7 @@ export const QuickAccessBar: FC = () => { key={command.id} hidden={QUICK_ACCESS_HIDDEN.includes(id)} title={`${command.label} (${command.id})`} - onClick={(event) => { - event.currentTarget.blur(); - runCommand(command); - }} + onClick={() => runCommand(command)} > {command.glyph} diff --git a/B07_DesignDetail/openwebcad/src/inputController/input-touch.css b/B07_DesignDetail/openwebcad/src/inputController/input-touch.css deleted file mode 100644 index e60591156..000000000 --- a/B07_DesignDetail/openwebcad/src/inputController/input-touch.css +++ /dev/null @@ -1,29 +0,0 @@ -/* 손가락 기기 명령 단추(기기대응 4-1 · `input-touch.ts`) — 자판 없이 Esc · Enter · 지움 · 되돌리기 · 다시 · 스냅. - 자판 없는 기기(손가락이 주 입력 · 마우스 스침 없음)만 보임 — 터치 노트북엔 안 보임(요구 280-6). 그림 면 오른쪽 가운데에 세로로. */ -.cad-touch-bar { - display: none; -} - -@media (hover: none) and (pointer: coarse) { - .cad-touch-bar { - position: fixed; - top: 50%; - right: 8px; - z-index: 50; - display: flex; - flex-direction: column; - gap: 6px; - transform: translateY(-50%); - } - - .cad-touch-bar > button { - width: 44px; - height: 44px; - border: 1px solid var(--color-border); - border-radius: 8px; - background: var(--color-surface-raised); - color: var(--color-text); - font-size: 18px; - box-shadow: var(--shadow-sm); - } -} diff --git a/B07_DesignDetail/openwebcad/src/inputController/input-touch.ts b/B07_DesignDetail/openwebcad/src/inputController/input-touch.ts index 432cf5b54..75cc9752e 100644 --- a/B07_DesignDetail/openwebcad/src/inputController/input-touch.ts +++ b/B07_DesignDetail/openwebcad/src/inputController/input-touch.ts @@ -2,7 +2,6 @@ import { Point } from '@flatten-js/core'; import { setTouchPickPx } from '../helpers/pick-radius.ts'; import { getCanvas, getScreenCanvasDrawController } from '../state.ts'; import type { InputController } from './input-controller.ts'; -import './input-touch.css'; /** * 손가락 CAD(기기대응 4-1) — 마우스 코드는 그대로 두고 손가락만 여기서 번역한다. @@ -11,7 +10,6 @@ import './input-touch.css'; * · 한 손가락 끌기 = 화면 옮기기 · 두 손가락 = 벌려 확대 + 같이 끌어 옮기기 * · 길게 누름(450ms) = 정밀 고르기 — 손가락 위 70px 에 십자선 · 끌어 맞추고 떼면 그 자리 클릭 * · 고름 반경 = 접촉 크기(최소 24px) · 마우스로 누르면 원래 반경 - * · 자판 없이 쓰는 명령 단추(Esc · Enter · 지움 · 되돌리기 · 다시 · 스냅) — 손가락 기기에서만 보임(App.css) */ const LONG_MS = 450; const SLOP_PX = 10; @@ -115,30 +113,4 @@ export function attachTouch(ctl: InputController): void { }; canvas.addEventListener('pointerup', (e) => lift(e, false)); canvas.addEventListener('pointercancel', (e) => lift(e, true)); - - mountTouchBar(ctl); -} - -/** 자판 없는 명령 단추 — 단축키와 같은 길(`handleKeyStroke`)로 보낸다 */ -function mountTouchBar(ctl: InputController): void { - const bar = document.createElement('div'); - bar.className = 'cad-touch-bar'; - const keys: [string, string, KeyboardEventInit][] = [ - ['Esc', '취소(Esc)', { key: 'Escape' }], - ['↵', '확정(Enter)', { key: 'Enter' }], - ['🗑', '지움(Delete)', { key: 'Delete' }], - ['↶', '되돌리기(Ctrl+Z)', { key: 'z', code: 'KeyZ', ctrlKey: true }], - ['↷', '다시(Ctrl+Y)', { key: 'y', code: 'KeyY', ctrlKey: true }], - ['⌖', '스냅 켬/끔(F3)', { key: 'F3' }], - ]; - for (const [label, title, init] of keys) { - const b = document.createElement('button'); - b.type = 'button'; - b.textContent = label; - b.title = title; - b.setAttribute('aria-label', title); - b.addEventListener('click', () => ctl.handleKeyStroke(new KeyboardEvent('keydown', init))); - bar.append(b); - } - document.body.append(bar); } diff --git a/B07_DesignDetail/openwebcad/src/ribbon/Ribbon.tsx b/B07_DesignDetail/openwebcad/src/ribbon/Ribbon.tsx index 954404559..d3ec81f83 100644 --- a/B07_DesignDetail/openwebcad/src/ribbon/Ribbon.tsx +++ b/B07_DesignDetail/openwebcad/src/ribbon/Ribbon.tsx @@ -35,11 +35,7 @@ const CommandButton: FC = ({ command, size, activeTool, onRu title={`${command.label}${command.aliases?.length ? ` (${command.aliases[0]})` : ''}${ command.hint ? ` — ${command.hint}` : '' }`} - onClick={(event) => { - // 초점 테두리가 남아 「선택」과 둘이 켜진 듯 보이지 않게(487) - event.currentTarget.blur(); - onRun(command); - }} + onClick={() => onRun(command)} > {command.glyph} {command.label} @@ -139,8 +135,7 @@ const Panel: FC<{ type="button" key={command.id} title={command.hint} - onClick={(event) => { - event.currentTarget.blur(); + onClick={() => { setOverflowAt(null); onRun(command); }} @@ -205,7 +200,16 @@ export const Ribbon: FC = ({ activeTool, widgets, onCommandRun }) = }; return ( -