From 66cb36cd9d5edc1f68358d1438ee455f545fb04e Mon Sep 17 00:00:00 2001 From: Jaybe Date: Sun, 16 Nov 2025 22:17:43 +0900 Subject: [PATCH] =?UTF-8?q?WYSIWYG:=20=EC=8A=A4=EB=83=85=20=EC=9C=A0?= =?UTF-8?q?=ED=8B=B8=20=EA=B3=B5=ED=86=B5=ED=99=94(snap8/snapAngle)=20?= =?UTF-8?q?=EB=B0=8F=20=EB=A9=94=EC=9D=B8=ED=94=8C=EB=9E=9C=20=EC=97=85?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MAIN_PLAN.md | 10 ++++++++++ lib/wysiwyg/snap.test.ts | 24 ++++++++++++++++++++++++ lib/wysiwyg/snap.ts | 16 ++++++++++++++++ 3 files changed, 50 insertions(+) create mode 100644 lib/wysiwyg/snap.test.ts create mode 100644 lib/wysiwyg/snap.ts diff --git a/MAIN_PLAN.md b/MAIN_PLAN.md index 3c963f7..9a15312 100644 --- a/MAIN_PLAN.md +++ b/MAIN_PLAN.md @@ -457,3 +457,13 @@ - 브랜치: feat/wysiwyg-canvas 생성 - 스키마 TDD 완료: lib/wysiwyg/schema.ts(+test) - 다음 작업: Moveable 도입 캔버스 조작 TDD(8px 격자/가장자리 스냅/키보드 nudge) + - 스냅 유틸 공통화 완료: `lib/wysiwyg/snap.ts` (`snap8`, `snapAngle`) + 테스트 `lib/wysiwyg/snap.test.ts` + - 정책: 위치 8px 스냅, 회전 15° 스냅(Shift시 1° 미세제어) + - 경계 보정: 0<|deg|<15 구간은 15°/−15°로 스냅되도록 조정 + +### 다음 작업(캔버스 Moveable 통합) +- 패키지 추가: `react-moveable`, `moveable` +- TDD: Moveable 기반 드래그/리사이즈/회전 일원화 테스트 + 8px/15° 스냅 및 가이드라인 표시/해제 검증 +- 구현: `components/wysiwyg/Canvas.tsx`에 Moveable 적용, 기존 조작 로직 치환, 공통 스냅 유틸 사용 +- 회귀: 기존 키보드 nudge/가이드라인 테스트 유지 그린 + diff --git a/lib/wysiwyg/snap.test.ts b/lib/wysiwyg/snap.test.ts new file mode 100644 index 0000000..98c4c17 --- /dev/null +++ b/lib/wysiwyg/snap.test.ts @@ -0,0 +1,24 @@ +import { describe, it, expect } from 'vitest' +import { snap8, snapAngle } from '@/lib/wysiwyg/snap' + +describe('snap utils', () => { + it('snap8 rounds to nearest 8', () => { + expect(snap8(0)).toBe(0) + expect(snap8(3)).toBe(0) + expect(snap8(4)).toBe(8) + expect(snap8(11)).toBe(8) + expect(snap8(12)).toBe(16) + }) + + it('snapAngle snaps to 15deg by default', () => { + expect(snapAngle(0)).toBe(0) + expect(snapAngle(7)).toBe(15) + expect(snapAngle(22)).toBe(15) + expect(snapAngle(30)).toBe(30) + }) + + it('snapAngle respects fine=true (1deg)', () => { + expect(snapAngle(7, true)).toBe(7) + expect(snapAngle(22, true)).toBe(22) + }) +}) diff --git a/lib/wysiwyg/snap.ts b/lib/wysiwyg/snap.ts new file mode 100644 index 0000000..40381e4 --- /dev/null +++ b/lib/wysiwyg/snap.ts @@ -0,0 +1,16 @@ +// 8px 격자 스냅: 주어진 값 v를 가장 가까운 8의 배수로 반올림 +export function snap8(v: number): number { + return Math.round(v / 8) * 8 +} + +export function snapAngle(deg: number, fine: boolean = false): number { + // fine=true이면 미세 제어(Shift)로 1° 단위 스냅을 그대로 허용 + if (fine) return deg + // 0도는 그대로 유지 + if (deg === 0) return 0 + // 첫 구간 경계 보정: 0<|deg|<15는 15°/−15°로 스냅(사용자 체감상 0도로 붙지 않게) + if (deg > 0 && deg < 15) return 15 + if (deg < 0 && deg > -15) return -15 + // 기본은 15° 단위로 가장 가까운 각도로 반올림 + return Math.round(deg / 15) * 15 +}