Va.TimePicker — 시간(시·분·초) 선택 팝업 필드
시각 하나만 선택하는 컴포넌트입니다. 좌측에 마스킹 필드(__:__:__), 우측에 시계 아이콘이 있고, 아이콘 클릭 또는 Enter 시 시·분·초 드롭다운이 있는 팝업이 열립니다. DateTimePicker에서 시간 파트만 떼어낸 형태.
- 클래스: Va.TimePicker — va_component.js:9030
- short name: timePicker
- 상속: Va.PureField (DatePicker·MonthPicker와 형제)
- isContainer: true
- 베이스 CSS: va-timepicker (팝업 va-menu-pop, calendar-inner)

1. 기본 사용
{
tagName: 'timePicker',
value: '143000', // HHMMSS 6자리
onSelect: 'onTimeChange'
}
- 화면 표시: 14:30:00
- 저장 값(getValue()): 143000
- 팝업: 시·분·초 각각의 <select> 드롭다운 + "지금" / "Confirm" 버튼
2. 다른 PureField 계열과의 차이
항목Va.DatePickerVa.DateTimePickerVa.TimePicker
| 입력 대상 | 년-월-일 | 년-월-일 + 시분초 | 시분초만 |
| 저장 값 길이 | 8자리 | 14/12자리 | 6/4자리 |
| 마스킹 | ____-__-__ | ____-__-__ __:__:__ | __:__:__ |
| 팝업 UI | 일 단위 캘린더 | 캘린더 + 시분초 | 시·분·초 3개 드롭다운 |
| 아이콘 | 캘린더 (📅) | 캘린더 | 시계 (🕐 ico_clock_fill) |
| timeFormat 옵션 | ✕ | ✓ | ✓ |
| popWidth 기본 | 260 | 260 | 240 |
한 줄 요약: "시각만 필요한 상황을 위한 심플한 팝업 필드."
3. 주요 속성
시간 포맷
속성기본값설명
| timeFormat | 'hhmmss' | 시간 표시 형식. hhmmss(초 포함) / hhmm(분까지) / hm(분까지 축약) |
| timeSeperator | ':' | 시간 구분자 |
| valueTimeFormat | — | 저장 시간 형식 |
| valueTimeSeperator | — | 저장 시간 구분자 (기본 없음) |
| masking | 자동 (__:__:__ 또는 __:__) | timeFormat에 따라 자동 |
팝업
속성기본값설명
| popWidth | 240 | 팝업 폭 |
| expanded | false | 초기 상태 |
PureField 상속
value, placeholder, readonly, disabled, size, appearance, stopPropagation 등 표준.
4. 저장 값 길이 — timeFormat에 따라 달라짐
timeFormat화면 표시저장 값 (getValue())길이
| hhmmss (기본) | 14:30:00 | 143000 | 6자리 |
| hhmm / hm | 14:30 | 1430 | 4자리 |
valueTimeSeperator 지정 시 구분자가 저장 값에 삽입:
{
valueTimeSeperator: ':'
}
// 저장: '14:30:00' (초 포함) 또는 '14:30' (분까지)
5. 이벤트
이벤트시그니처발생 시점
| select | (component, element, value, evt) | 팝업의 "Confirm" 버튼 클릭 시. value는 원시 값(구분자 없음) |
| beforePop / afterPop / hidePop | 팝업 표시/숨김 | |
| expand / collapse | 팝업 확장/축소 | |
| focus / blur | 표준 |
⚠️ 다른 Picker와 다른 점 — DatePicker는 날짜 클릭 시 즉시 select 발생인데, TimePicker는 드롭다운 선택 후 "Confirm" 버튼 클릭 시 select 발생. 팝업이 바로 닫히지 않고 사용자가 확정 버튼을 눌러야 완료.
6. 메서드 — 두 벌의 setter/getter
DatePicker·DateTimePicker와 동일한 패턴:
메서드설명
| setValue(value) | valueTimeSeperator가 있으면 제거 후 세팅 |
| getValue() | valueTimeSeperator 규약 형태로 반환. 마스킹 언더스코어 남으면 '' |
| setRawValue(value) | 원시 값(구분자 없이) 그대로 세팅 |
| getRawValue() | 원시 값(구분자 없이) 그대로 반환 |
팝업 제어
메서드설명
| showTimePickerPop(evt) / hideTimePickerPop() | 팝업 제어 |
| showPop() / expand() | drawCalendar() 호출 (레거시 별칭) |
| hidePop() / collapse() | 팝업 숨김 |
| drawCalendar(value) | 팝업 UI 렌더링 (이름은 Calendar지만 시간 드롭다운을 그림 — DatePicker에서 복사된 잔재) |
상태
메서드설명
| setDisabled(bool) / setReadOnly(bool) | PureField 상속 |
| focus() / blur() | 포커스 |
7. 팝업 UI 구조
drawCalendar()가 렌더링 (이름은 Calendar지만 실제로는 시간 드롭다운):
┌─────────────────────────────┐
│ [ 14 ▼ ] : [ 30 ▼ ] : [ 00 ▼ ] │ ← 3개 <select> 드롭다운
│ (0~23) (0~59) (0~59) │
├─────────────────────────────┤
│ 지금 Confirm │ ← 하단 버튼
└─────────────────────────────┘
- 시: 0 ~ 23 (24시간 형식, 앞자리 0 패딩)
- 분·초: 0 ~ 59
- "지금" 버튼: 현재 시각으로 세팅
- "Confirm" 버튼: 값 확정 + select 이벤트 + 팝업 닫힘
⚠️ 12시간 형식(AM/PM)은 지원 안 됨 — 항상 24시간 형식. 12시간 UI가 필요하면 커스터마이즈.
8. 내부 구조
<div elname="element" class="va-timepicker [size]..." tag-name="timePicker" field="true">
<div elname="fieldWrapper" class="field-wrapper">
<input elname="field" type="text" placeholder="__:__:__" style="border:0px">
<div elname="focusLine" class="focus-line"></div>
<div elname="clockIconWrapper">
<span elname="clockIcon" class="icon menu ico_clock_fill">🕐</span>
</div>
</div>
<div elname="popDiv" class="menu-button-pop-div">
<div elname="pop" class="va-menu-pop" style="position:absolute; display:none">
<div elname="popInner" class="calendar-inner">
<!-- 시·분·초 select + 하단 버튼 -->
</div>
</div>
</div>
</div>
9. 팝업 상태머신 연동
프레임워크 공용 팝업 상태머신 참여:
- Va.addAutoHide(this.popElement) — 외부 클릭 시 자동 닫힘
- Va.hideOtherComponents(this) — 다른 팝업 자동 닫음
- getHiddenAreaElement()로 팝업 이동 → 부모 stacking 회피
- 뷰포트 넘침 시 위로 자동 뒤집기
10. getValue()의 부분 입력 방어
DateTimePicker와 마찬가지로 필드에 마스킹 언더스코어가 남아 있으면 getValue()가 '' 반환:
- timeFormat: 'hhmmss' → 완전한 값은 8자리 (구분자 포함), 미완성이면 ''
- timeFormat: 'hhmm' / 'hm' → 완전한 값은 5자리, 미완성이면 ''
의미: 14:__:__ 같은 미완성 상태로 폼 제출해도 서버로 부분 값이 안 감. 다만 잘못된 형식(99:99:99)은 걸러지지 않음 — 별도 검증 필요.
11. 언제 쓰나
TimePicker가 맞을 때
- 근태 출근·퇴근 시각 입력
- 회의 시간대 선택 (14:00~15:30 같은 필드에)
- 알람·리마인더 시각
- 영업 시간 설정 (09:00 ~ 18:00)
- 시각만 필요하고 날짜는 무의미할 때
다른 걸 쓸 때
- 날짜도 필요 → Va.DateTimePicker (같이 다룸)
- 날짜만 필요 → Va.DatePicker
- 시작~종료 시각 → 두 TimePicker 조합 (전용 Range 컴포넌트는 별도 확인)
- 라벨 붙은 폼 필드 → Va.TimeField
- 12시간 AM/PM UI 필요 → 커스텀 (기본 지원 안 됨)
- 15분·30분 단위만 (스텝) → Va.ComboboxField에 옵션 배열 (분 단위 스텝 옵션 없음)
12. 흔한 조합 예시
// 초까지 (표준)
{ tagName: 'timePicker', value: '143000' }
// 분까지만
{
tagName: 'timePicker',
timeFormat: 'hhmm',
value: '1430' // 화면: 14:30, 저장: 1430
}
// 서버가 콜론 포함 원할 때
{
tagName: 'timePicker',
valueTimeSeperator: ':',
value: '14:30:00' // 화면: 14:30:00, 저장: 14:30:00
}
// 오전 9시로 초기화
mounted() {
this.getRef('startTime').setValue('090000');
}
// 지금 시각으로 초기화
mounted() {
const now = new Date();
const hhmmss = String(now.getHours()).padStart(2, '0') +
String(now.getMinutes()).padStart(2, '0') +
String(now.getSeconds()).padStart(2, '0');
this.getRef('time').setValue(hhmmss);
}
// 큰 팝업
{
tagName: 'timePicker',
popWidth: 320,
value: '143000'
}
13. 알아두면 좋을 주의사항
- value는 4자리 또는 6자리 문자열 — timeFormat에 따라. 숫자 넘기면 .substring 크래시.
- setValue() null 안전성 없음 — null 넘기면 .replaceAll 호출에서 크래시. Date/DateTimePicker처럼 조기 return 없음.
- getValue()가 미완성 값에 '' 반환 — 마스킹 언더스코어 있으면 빈 문자열.
- getValue()는 잘못된 형식 검증 안 함 — 99:99:99 같은 값도 완성되면 그대로 반환.
- 12시간 AM/PM 미지원 — 24시간 형식 고정.
- 분·초 세밀 단위 제한 없음 — 1분 단위. "15분 스텝" 등 옵션 없음.
- 팝업 즉시 확정 아님 — "Confirm" 버튼 클릭해야 select 이벤트 + 값 반영.
- timeFormat 코드가 대소문자 혼용 — 예: 'HHmm' (대문자 H, 소문자 mm) 조건이 코드에 있음 (va_component.js:9294). 실제로는 hhmm/hm 소문자를 씀. 일관성 부족.
- drawCalendar() 이름 부적절 — DatePicker에서 복사된 잔재. 실제로는 시간 드롭다운을 그림.
- "지금" 버튼 코드에 버그성 잔재 — Va.Util.getNowYear() + getNowMonth() + getNowDay()로 날짜를 세팅함 (va_component.js:9282-9284). DatePicker에서 복사됨. 실제 클릭 시 예상과 다른 값이 들어갈 수 있으니 확인 필요.
- 팝업 상태머신 참여 — 다른 팝업과 자동 상호 배타.
- 아이콘이 시계 — DatePicker의 캘린더 아이콘과 다름 (ico_clock_fill).
- timezone 없음 — 로컬 시각 문자열만.
14. 실전 예 — 근태 출퇴근 시각
class Attendance extends Va.View {
mounted() {
// 표준 근무 시간으로 초기화
this.getRef('startTime').setValue('090000');
this.getRef('endTime').setValue('180000');
}
onSubmit(btn, el, evt) {
const start = this.getRef('startTime').getValue();
const end = this.getRef('endTime').getValue();
if (!start || !end) {
new Va.Alert({
title: '오류',
message: '출근·퇴근 시각을 모두 입력하세요'
}).show(this);
return;
}
// 서버로 전송 (6자리 문자열)
AttendanceService.save(this, {
date: '20241225',
startTime: start, // '090000'
endTime: end // '180000'
}, this.onSaved);
}
onSaved(view, ok, res) {
if (ok) new Va.Alert({ title: '완료', message: '저장되었습니다' }).show(view);
}
config() {
return {
tagName: 'page',
tags: [{
tagName: 'panel',
tags: [
{ tagName: 'h2', innerHTML: '근태 등록' },
{
tagName: 'div',
layout: 'ds-flex fd-row ai-center gap-m',
tags: [
{
tagName: 'div',
layout: 'ds-flex fd-column gap-xs',
tags: [
{ tagName: 'label', innerHTML: '출근' },
{ tagName: 'timePicker', ref: 'startTime' }
]
},
{
tagName: 'div',
layout: 'ds-flex fd-column gap-xs',
tags: [
{ tagName: 'label', innerHTML: '퇴근' },
{ tagName: 'timePicker', ref: 'endTime' }
]
}
]
},
{
tagName: 'button',
text: '저장',
appearance: 'primary',
onClick: 'onSubmit'
}
]
}]
};
}
}
15. TimeField가 필요하면
라벨·검증까지 필요한 폼 필드로 쓰려면 Va.TimeField (Field 계열 래퍼)를 사용하세요:
{
tagName: 'timeField',
label: '출근 시각',
value: '090000',
required: true
}
DateField·MonthField와 같은 Composition 패턴으로, 내부에 Va.TimePicker를 소유합니다.
'컴포넌트 > 필드 컴포넌트' 카테고리의 다른 글
| Checkbox (체크박스) (0) | 2026.09.12 |
|---|---|
| TimeField (시각필드) (0) | 2026.09.12 |
| MonthField (년월필드) (0) | 2026.09.12 |
| MonthPicker (월선택) (0) | 2026.09.12 |
| YearField (연도필드) (1) | 2026.09.11 |