/* ============================================================================ * 서명전에 — TDS NumberKeypad 컴포넌트 * · 숫자만 입력받는 커스텀 키패드. PIN · 주민등록번호 뒷자리 · 카드번호 등 * 보안이 중요한 수치 입력에 사용해요. * · 기본 배치는 3×4 격자: * [1 2 3] * [4 5 6] * [7 8 9] * [ _ 0 ⌫ ] * 숫자 배열은 `numbers` prop 으로 재배치 가능 — 기본 [1,2,3,4,5,6,7,8,9,0]. * 길이는 반드시 10 이어야 해요. 10이 아니면 경고 후 기본값으로 fallback. * * · 보안 모드 (`secure = true`): * - 사용자가 키를 눌렀을 때 실제 값 외에 **유령 키 이벤트 2회** 를 * 추가 발송해 키로거 측면채널 공격을 방해해요. * - 유령 키는 눌린 키의 상/하/좌/우 인접칸을 제외한 나머지 숫자 중에서 * 난수로 2개 선택해 `onKeyClick(value, { ghost: true })` 로 통지. * - 실제 값은 `onKeyClick(value, { ghost: false })`. 호출 순서는 * "실제값 먼저, 유령 2회 뒤" 로 고정해 호출부가 첫 번째 이벤트만 * 신뢰하면 되도록 했어요. * - 호출부에서 구분하지 않으려면 `onSecureKey(value)` 콜백을 쓰세요. * 이 콜백은 실제 키 한 번만 호출돼요. * * · 시각 스펙 (TDS NumberKeypad 2026-03): * - 3열 × 4행 격자, 각 셀 높이 56px, hairline `--tds-grey-opacity-200` * - 배경 `var(--bg-default)` = 흰색, 누름 시 `--tds-grey-100` * - 글자 `var(--label)` (grey-900), 타이포 `--ty-4` + bold * - Backspace 아이콘 24px mono, `--label` 컬러 * * · props (NumberKeypadProps): * numbers? number[] | string[] 길이 10, 기본 [1..9,0] * secure? boolean — 유령 키 이벤트 활성 (기본 false) * onKeyClick* (value, meta) => void * meta = { ghost: boolean } * secure=false 면 meta.ghost 는 항상 false. * onSecureKey? (value) => void — 실제 키 한 번만 받고 싶을 때 * onBackspaceClick* () => void * disabled? boolean — 전체 비활성 (기본 false) * backspaceAriaLabel? string — 기본 "한 글자 지우기" * className, style — 컨테이너 속성 패스스루 * * · 접근성: * - 각 숫자 키는 ` ); } // 빈 셀 (row=3, col=0) cells.push(