DatePicker
날짜 선택 컴포넌트
목차
캘린더 UI를 통해 날짜를 선택할 수 있는 컴포넌트입니다.
DatePicker는 Flutter/Web 양쪽에서 동일한 named-parameter API를 제공하는 통일 컴포넌트입니다. 내부적으로 팝업 캘린더는 Calendar를 사용합니다.
기본 사용법 (Basic Usage)#
// 기본 날짜 선택 — 내부 상태 관리 포함
DatePicker.simple(
placeholder: 'Select date',
onChanged: handleDateChanged,
)
// 외부 상태로 제어
DatePicker(
value: selectedDate,
placeholder: 'Select date',
onChanged: handleDateChanged,
)
// 날짜 범위 선택
DateRangePicker(
value: selectedRange,
onChanged: handleRangeChanged,
)
// 기본 날짜 선택 — ISO 8601 문자열(YYYY-MM-DD)로 값 전달
DatePicker(
onChanged: handleDateChanged,
placeholder: 'Select date',
value: selectedDate,
)
// Dialog 모드 — 중앙 모달 + Cancel/Save 액션
DatePicker(
onChanged: handleDateChanged,
value: selectedDate,
mode: CorePromptMode.dialog,
placeholder: 'Select date',
dialogTitle: const Text('Pick a date'),
)
// 날짜 범위 — Web도 DateRangePicker 지원
DateRangePicker(
onChanged: handleRangeChanged,
value: selectedRange, // ({start: 'YYYY-MM-DD', end: 'YYYY-MM-DD'})
mode: CorePromptMode.popover,
placeholder: const Text('Select range'),
)
// 범위 + Dialog 모드 (DateRangePicker 기본)
DateRangePicker(
onChanged: handleRangeChanged,
value: selectedRange,
placeholder: const Text('Select range'),
dialogTitle: const Text('Pick a range'),
)
빠른 오버라이드 (Chain)#
이미 만든 DatePicker 인스턴스에 스타일을 빠르게 덧붙이고 싶다면 withStyle 체인을 쓸 수 있습니다. 생성자의 style 슬롯 인자와 동일하게 동작하지만, 이미 구성된 위젯 위에서 바로 이어 쓸 수 있습니다.
class DatePickerChainExample extends StatefulWidget {
const DatePickerChainExample({super.key});
@override
State<DatePickerChainExample> createState() => _DatePickerChainExampleState();
}
class _DatePickerChainExampleState extends State<DatePickerChainExample> {
@override
Widget build(BuildContext context) {
return SizedBox(
width: CoreSpace.space256,
child:
ControlledDatePicker(
placeholder: 'Select date',
onChanged: (_) {},
).withStyle(
const CoreDatePickerStyle(
contentColor: CoreColor.token(CoreColors.primary),
placeholderColor: CoreColor.token(CoreColors.onSurfaceVariant),
valueTypography: CoreTextStyle.token(CoreTextStyles.bodyLarge),
trailingIconStyle: CoreIconStyle(
size: CoreSize.size20,
color: CoreColor.token(CoreColors.primary),
),
),
),
);
}
}
class DatePickerChainExample extends StatefulComponent {
const DatePickerChainExample({super.key});
@override
State<DatePickerChainExample> createState() => _DatePickerChainExampleState();
}
class _DatePickerChainExampleState extends State<DatePickerChainExample> {
String? _value;
@override
Component build(BuildContext context) {
return div(
[
DatePicker(
onChanged: (v) => setState(() => _value = v),
placeholder: 'Select date',
value: _value,
).withStyle(
const CoreDatePickerStyle(
contentColor: CoreColor.token(CoreColors.primary),
placeholderColor: CoreColor.token(CoreColors.onSurfaceVariant),
valueTypography: CoreTextStyle.token(CoreTextStyles.bodyLarge),
trailingIconStyle: CoreIconStyle(
size: CoreSize.size20,
color: CoreColor.token(CoreColors.primary),
),
),
),
],
classes: 'w-${CoreSpace.scale.space256}',
);
}
}
Live Preview#
기본 (single + popover)#
코드 보기
class DatePickerDefaultExample extends StatefulComponent {
const DatePickerDefaultExample({super.key});
@override
State<DatePickerDefaultExample> createState() =>
_DatePickerDefaultExampleState();
}
class _DatePickerDefaultExampleState extends State<DatePickerDefaultExample> {
String? _value;
@override
Component build(BuildContext context) {
return div(
[
DatePicker(
onChanged: (v) => setState(() => _value = v),
placeholder: 'Select date',
value: _value,
),
],
classes: 'w-${CoreSpace.scale.space256}',
);
}
}
class DatePickerDefaultExample extends StatefulWidget {
const DatePickerDefaultExample({super.key});
@override
State<DatePickerDefaultExample> createState() =>
_DatePickerDefaultExampleState();
}
class _DatePickerDefaultExampleState extends State<DatePickerDefaultExample> {
@override
Widget build(BuildContext context) {
return SizedBox(
width: CoreSpace.space256,
child: DatePicker.simple(
placeholder: 'Select date',
onChanged: (_) {},
),
);
}
}
단일 + 다이얼로그 모드#
코드 보기
class DatePickerDialogExample extends StatefulComponent {
const DatePickerDialogExample({super.key});
@override
State<DatePickerDialogExample> createState() =>
_DatePickerDialogExampleState();
}
class _DatePickerDialogExampleState extends State<DatePickerDialogExample> {
String? _value;
@override
Component build(BuildContext context) {
return div(
[
DatePicker(
onChanged: (v) => setState(() => _value = v),
value: _value,
mode: CorePromptMode.dialog,
placeholder: 'Select date',
dialogTitle: const Text('Pick a date'),
),
],
classes: 'w-${CoreSpace.scale.space256}',
);
}
}
class DatePickerDialogExample extends StatefulWidget {
const DatePickerDialogExample({super.key});
@override
State<DatePickerDialogExample> createState() =>
_DatePickerDialogExampleState();
}
class _DatePickerDialogExampleState extends State<DatePickerDialogExample> {
DateTime? _value;
@override
Widget build(BuildContext context) {
return SizedBox(
width: CoreSpace.space256,
child: DatePicker(
onChanged: (v) => setState(() => _value = v),
value: _value,
mode: CorePromptMode.dialog,
placeholder: 'Select date',
dialogTitle: const Text('Pick a date'),
),
);
}
}
범위 + 팝오버 모드#
코드 보기
class DatePickerRangeExample extends StatefulComponent {
const DatePickerRangeExample({super.key});
@override
State<DatePickerRangeExample> createState() =>
_DatePickerRangeExampleState();
}
class _DatePickerRangeExampleState extends State<DatePickerRangeExample> {
({String start, String end})? _value;
@override
Component build(BuildContext context) {
return div(
[
DateRangePicker(
onChanged: (v) => setState(() => _value = v),
value: _value,
mode: CorePromptMode.popover,
placeholder: Text('Select range'),
),
],
classes: 'w-${CoreSpace.scale.space256}',
);
}
}
class DatePickerRangeExample extends StatefulWidget {
const DatePickerRangeExample({super.key});
@override
State<DatePickerRangeExample> createState() =>
_DatePickerRangeExampleState();
}
class _DatePickerRangeExampleState extends State<DatePickerRangeExample> {
DateTimeRange? _value;
@override
Widget build(BuildContext context) {
return SizedBox(
width: CoreSpace.space256,
child: DateRangePicker(
onChanged: (v) => setState(() => _value = v),
value: _value,
mode: CorePromptMode.popover,
placeholder: const Text('Select range'),
),
);
}
}
범위 + 다이얼로그 모드 (DateRangePicker 기본)#
코드 보기
class DatePickerRangeDialogExample extends StatefulComponent {
const DatePickerRangeDialogExample({super.key});
@override
State<DatePickerRangeDialogExample> createState() =>
_DatePickerRangeDialogExampleState();
}
class _DatePickerRangeDialogExampleState
extends State<DatePickerRangeDialogExample> {
({String start, String end})? _value;
@override
Component build(BuildContext context) {
return div(
[
DateRangePicker(
onChanged: (v) => setState(() => _value = v),
value: _value,
placeholder: Text('Select range'),
dialogTitle: const Text('Pick a range'),
),
],
classes: 'w-${CoreSpace.scale.space256}',
);
}
}
class DatePickerRangeDialogExample extends StatefulWidget {
const DatePickerRangeDialogExample({super.key});
@override
State<DatePickerRangeDialogExample> createState() =>
_DatePickerRangeDialogExampleState();
}
class _DatePickerRangeDialogExampleState
extends State<DatePickerRangeDialogExample> {
DateTimeRange? _value;
@override
Widget build(BuildContext context) {
return SizedBox(
width: CoreSpace.space256,
child: DateRangePicker(
onChanged: (v) => setState(() => _value = v),
value: _value,
placeholder: const Text('Select range'),
dialogTitle: const Text('Pick a range'),
),
);
}
}
사용 시기 (When to Use)#
이 컴포넌트를 사용하세요:
- 폼에서 날짜 입력이 필요할 때 (생년월일, 예약일 등)
- 트리거 버튼과 캘린더 팝업을 결합한 UI가 필요할 때
- 체크인/체크아웃 같은 날짜 범위 선택이 필요할 때 (양쪽의
DateRangePicker사용)
대신 다른 컴포넌트를 사용하세요:
- TimePicker: 날짜 값 대신 시간 값을 선택할 때
- Calendar: 캘린더를 항상 인라인으로 표시할 때
- TextField: 날짜 그리드 없이 문자열만 받는 경우. 날짜 파싱·검증은 호출부가 담당
- Select: 미리 정의된 날짜 옵션 중 값 하나를 선택할 때
Props / Parameters#
공통 Contract 필드 (CoreDatePickerContract)를 한 표로 정리합니다. W 는 Flutter Widget
/ Web Jaspr Component, D 는 날짜 표현인 Flutter DateTime / Web ISO
String 입니다. 이 기호는 설명용이며 새로운 공개 타입이 아닙니다.
| 속성 | 타입 | 기본값 | 설명 |
|---|---|---|---|
value |
D? |
null |
single의 현재 선택 날짜 |
onChanged |
void Function(D?)? |
null |
single 날짜 선택·해제 커밋 |
placeholder |
String? |
null |
값이 없을 때 표시 텍스트 |
placeholderWidget |
W? |
null |
DatePicker의 커스텀 placeholder. 문자열보다 우선 |
enabled | bool | true | 상호작용 허용 여부 |
min | D? | null | 최소 선택 가능 날짜 |
max | D? | null | 최대 선택 가능 날짜 |
label | String? | null | 트리거 상단 라벨 |
labelMaxLines |
int? |
null |
라벨 줄 수 제한. null이면 제한 없음 |
labelOverflow |
CoreTextOverflow? |
null |
라벨 넘침 처리 |
description |
String? |
null |
트리거 하단 보조 텍스트 |
errorText |
String? |
null |
오류 메시지. 존재 시 description 숨김 |
mode |
CorePromptMode? |
single: popover / range: dialog | 선택을 표시할 표면 |
popoverPlacement |
CorePopoverPlacement |
bottomStart |
popover의 트리거 기준 위치 |
stateBuilder |
CoreCalendarDateStateBuilder? |
null |
내부 Calendar의 날짜 상태 판정 |
initialView |
CoreCalendarView? |
null |
DatePicker의 초기 캘린더 뷰. null이면 value 기준 |
firstDayOfWeek |
int |
DateTime.sunday |
내부 Calendar 의 한 주 시작 요일(
DateTime.monday
…
DateTime.sunday
). 로케일에서 유도하지 않고
Calendar
로 그대로 전달
|
dialogTitle |
W? |
null |
다이얼로그 제목 슬롯 |
datePickerStyle |
CoreDatePickerStyle? |
null |
single 스타일 슬롯 |
DateRangePicker#
범위 선택의 value / onChanged 는 Flutter DateTimeRange?, Web ({String start, String end})?
값을 받습니다. 스타일 슬롯 이름은 양쪽 모두 dateRangePickerStyle 이며 firstDayOfWeek 를 포함한 나머지 공통 필드의 의미는 위와 같습니다.
initialView 의 범위 선택 생성자 표면은 현재 양쪽이 일치하지 않는 API 결손이 있습니다.
Flutter 의 builder 가 돌려주는 위젯은 필드의 글자(value) 가 될 수 없어 읽기 전용 TextField
의 placeholder 자리에 놓이고, 값 색(contentColor)을 입습니다(value 는 비어 있습니다).
스타일 시스템 (Style System)#
DatePicker 의 모든 chrome / dimensional / nested-slot 오버라이드는 CoreDatePickerStyle 단일 슬롯으로 흐릅니다. 평면 chrome 필드(borderRadius
/ padding / height / gap / iconSize / popoverPadding
/ popoverGap)는 모두 제거되었습니다.
시맨틱 vs 스타일#
-
시맨틱 enum / behaviour: 위젯 파라미터로 직접 (
mode,placeholder,enabled,onChanged,min,max,label,description,errorText,dialogTitle) -
chrome / dimensional / 슬롯 스타일:
CoreDatePickerStyle한곳으로 (triggerStyle/popoverStyle/dialogStyle/calendarStyle/labelStyle/descriptionStyle/errorStyle/contentColor/descriptionColor/errorColor/labelBottomGapStyle) -
변형(variant) 교체: asChild —
dialogTitle/ 커스텀placeholder/ 커스텀builder(range) 등의 슬롯에 직접 위젯 주입
Resolve chain#
design system default for date picker
→ CoreDatePickerTheme.style // 프로젝트 공통
→ parent component slot override
→ widget.datePickerStyle // 인스턴스별 (range: dateRangePickerStyle)
각 nested 슬롯 스타일 (triggerStyle / popoverStyle / dialogStyle / calendarStyle) 은 자기 컴포넌트의 자체 resolve chain 으로 다시 한 번 머지됩니다. 예: 트리거는 읽기 전용
TextField 라서 triggerStyle 의 chrome 은 → CoreTextFieldTheme → 크기 행(md) → 부모 슬롯 →
widget.datePickerStyle.triggerStyle 순. 이전 버튼 트리거의 popoverStyle.triggerStyle
(CoreButtonStyle) 은 더 이상 읽지 않습니다.
CoreDatePickerStyle 필드#
| 필드 | 타입 | 설명 |
|---|---|---|
triggerStyle |
CoreTextFieldStyle? |
Trigger field chrome — raw-forwarded to the composed read-only
TextField
, over [defaultTriggerStyle]. The trigger's value colour ([contentColor]), placeholder tone ([placeholderColor]), value role ([valueTypography]), calendar glyph ([trailingIconStyle]) and error edge ([errorTriggerStyle]'s border colour) are folded in under it. An explicit
popoverStyle.triggerStyle
is the chrome of the earlier button trigger and is not read.
|
popoverStyle |
CorePopoverStyle? |
Popover style for the popover-mode picker (trigger + dropdown chrome). |
dialogStyle |
CoreDialogStyle? |
Dialog style for the modal-mode picker (panel + title + action button chrome). |
errorTriggerStyle |
CoreButtonStyle? |
Deprecated (since 0.173)
— Use triggerStyle: CoreTextFieldStyle(errorBorderColor: …). Trigger button chrome override while the picker holds an error. Merged on top of [defaultErrorTriggerStyle]; only its
borderColor
is read now — it becomes the trigger field's
errorBorderColor
.
null
takes the default alone.
|
calendarStyle |
CoreCalendarStyle? |
Calendar style applied to the inline / popover / dialog calendar grid. Drives nav buttons / date cells / arrow icons / state tokens. |
labelStyle |
CoreTextStyle? |
Label text style (text shown above the trigger). |
descriptionStyle |
CoreTextStyle? |
Description / helper text style (text shown below the trigger when no error). |
errorStyle | CoreTextStyle? | Error text style. |
valueTypography |
CoreTextStyle? |
Trigger value text style override (the role applied to both the selected-value text and the placeholder text — colour still comes from [contentColor] / [defaultPlaceholderColor]).
null
leaves the role to the trigger field's own row.
|
contentColor |
CoreColor? |
Trigger / label content text colour override.
null
defers to [defaultContentColor]. The Web label row and the trigger field's
valueStyle
both read this slot — for the trigger value
while enabled
(in error or not: an error changes the trigger's edge, see [defaultErrorTriggerStyle], not its label); a disabled value takes the field's own disabled foreground, which this style does not restate.
|
descriptionColor |
CoreColor? |
Description / helper text colour override. null defers to [defaultDescriptionColor]. |
errorColor |
CoreColor? |
Error text colour override. null defers to [defaultErrorColor]. |
disabledErrorColor |
CoreColor? |
Error text colour override while the picker is disabled.
null
defers to [defaultDisabledErrorColor].
|
disabledDescriptionColor |
CoreColor? |
Description text colour override while the picker is disabled.
null
defers to [defaultDisabledDescriptionColor]. [descriptionColor] colours the enabled picker only.
|
placeholderColor |
CoreColor? |
Trigger placeholder (no value selected) text colour override.
null
defers to [defaultPlaceholderColor].
|
trailingIconStyle |
CoreIconStyle? |
Trigger trailing (calendar) icon style override.
null
defers to [defaultTrailingIconStyle].
|
labelBottomGapStyle |
CoreGapStyle? |
Nested [CoreGapStyle] slot for the vertical gap between the label row and the trigger row — forwarded straight to the
Gap
widget that separates the rows.
null
defers to [defaultLabelBottomGapStyle].
|
descriptionTopGapStyle |
CoreGapStyle? |
Nested [CoreGapStyle] slot for the vertical gap between the trigger row and the description / error text row — forwarded straight to the
Gap
widget that separates the rows.
null
defers to [defaultDescriptionTopGapStyle].
|
옛 평면 필드 → 새 위치 매핑#
| 옛 chrome 필드 | 새 위치 |
|---|---|
height | triggerStyle.height |
padding |
triggerStyle.padding 또는 popoverStyle.panelPadding (의도에 따라) |
borderRadius | triggerStyle.borderRadius |
gap (트리거 내부) | 필드가 정합니다 (오른쪽 아이콘 자리) |
iconSize | trailingIconStyle.size |
popoverPadding | popoverStyle.panelPadding |
popoverGap | popoverStyle.placementOffset |
사용 예 (Flutter)#
DatePicker(
placeholder: 'Select date',
onChanged: handleChanged,
datePickerStyle: CoreDatePickerStyle(
triggerStyle: CoreTextFieldStyle(
height: 44,
),
popoverStyle: CorePopoverStyle(
panelBorderRadius: 12,
gap: 8,
),
calendarStyle: CoreCalendarStyle(
selectedColor: Color(0xFF3B82F6),
),
labelStyle: CoreTextStyle(fontWeight: 600),
),
)
변형 (Variants)#
Controlled (Flutter)#
// 내부 상태 관리 — simple 팩토리 또는 ControlledDatePicker
ControlledDatePicker(
initialValue: DateTime.now(),
onChanged: handleDateChanged,
)
날짜 범위 선택 (Flutter)#
DateRangePicker(
value: DateTimeRange(checkIn, checkOut),
onChanged: handleRangeChanged,
)
비활성 날짜 지정 (Flutter)#
DatePicker(
value: selectedDate,
onChanged: handleDateChanged,
stateBuilder: (date) =>
date.weekday <= 5
? CoreCalendarDateState.enabled
: CoreCalendarDateState.disabled,
)
동작 스펙 (Behavior)#
-
트리거는 입력창과 같은 모양입니다 — 읽기 전용
TextField의md행(높이 40 · 모서리 10 ·outlineVariant테두리 · 값 Regular 14 · 오른쪽 달력 아이콘)이고, 폭은 부모가 준 폭을 채우며, 포커스 테두리는primary입니다. 오류일 때는error로 바뀝니다. 박스는 합성한TextField가 정하므로 DatePicker 는 값 색(contentColor) · 달력(trailingIconStyle) · 오류 테두리색만 말하고,triggerStyle로 필드 모양을 덮을 수 있습니다. 값이 없을 때(value == null)는 placeholder 를, 있을 때는 날짜를 같은 트리거에 보입니다 — Figma 의Show value·Value는 이 두 모양을 고르는 캔버스 표현이고 코드 파라미터가 아닙니다(value는 이미DateTime?). -
single의 popover는 선택·해제를 즉시
onChanged로 알리고 짧은 강조 표시 후 닫습니다. range는 첫 시작일 클릭에는 커밋하지 않고 완료된 범위 또는 해제에만 콜백을 내며 닫습니다. 지연 단위는CoreDatePickerStyle.defaultPopoverDismissDelayMs의 ms입니다. -
dialog는 pending 선택을 별도로 두고 Save에서만 커밋합니다. Cancel·외부 닫힘은 변경을 버립니다. Web의 Save는 현재
_pending ?? _value를 사용해 명시적인 null 해제를 이전 값으로 되돌리는 결손이 있습니다. -
firstDayOfWeek는 popover · dialog 어느 표면이든 내부 Calendar 로 전달됩니다. 주 시작 요일은 지역의 속성이라 로케일에서 유도하지 않고 호출하는 쪽이 정합니다. 생략하면 일요일입니다. -
날짜 제한·stateBuilder는 내부 Calendar로 전달하며 stateBuilder가 min/max보다 우선합니다. 오류 메시지는 값을 검증하지 않고 표시를 정합니다.
errorText가 있으면 트리거는 테두리와 포커스 링만error색이 되고 글자색과 배경은 그대로입니다 — 텍스트 필드의 오류 모양과 같습니다. 오류 문구는 DatePicker 가 트리거 아래에 직접 그립니다(필드에는 문구가 아니라 오류 상태만 전달합니다). 오류 테두리색은triggerStyle.errorBorderColor로 바꿉니다(errorTriggerStyle의borderColor도 아직 읽지만 폐기 예정입니다).enabled: false는 트리거를 비활성화하고, 오류가 있어도 오류 표시 없이 일반 비활성 모양이 이깁니다.
표시 모드 (Flutter)#
// Popover 모드 (기본) — 트리거 아래에 캘린더 드롭다운
DatePicker(
mode: CorePromptMode.popover,
popoverPlacement: CorePopoverPlacement.bottomStart,
value: selectedDate,
onChanged: handleDateChanged,
)
// Dialog 모드 — 전체 화면 모달에 캘린더 표시
DatePicker(
mode: CorePromptMode.dialog,
dialogTitle: Text('날짜 선택'),
value: selectedDate,
onChanged: handleDateChanged,
)
DatePickerController (Flutter)#
final controller = DatePickerController(DateTime(2024, 3, 15));
// 프로그래매틱 제어
controller.value = DateTime.now();
ValueNotifier<DateTime?>기반ComponentControllermixin으로 폼 통합
범위 선택 (Flutter)#
DateRangePicker는 기본CorePromptMode.dialog사용Calendar의 range 모드를 활용하여 시작/끝 날짜를 동시에 선택
Web 구현#
- 트리거: 읽기 전용
TextField에 달력 아이콘과 placeholder/선택 텍스트를 합성 -
Popover mode:
Popover사용 —OverlayHost.popover레이어로 portal, ancestoroverflow: hidden/transform영향 없음 -
Dialog mode:
Dialog사용 —openboolean 으로 제어, Cancel/Save 액션으로 값 commit - 인터랙션: 트리거·닫힘은 합성된 Popover / Dialog가 처리합니다. non-modal popover의 Escape 경로는 Web에서 제공되며 Flutter는 같은 경로가 없습니다
-
Single 값은 ISO 8601 (
YYYY-MM-DD) 문자열, range 값은({String start, String end})레코드 min/max는 ISO 문자열로 전달 (내부에서DateTime으로 파싱)
사용 가이드라인 (Usage Guidelines)#
Do#
적절한 placeholder로 기대 형식을 안내하세요.
DatePicker(
placeholder: 'YYYY-MM-DD',
value: selectedDate,
onChanged: handleDateChanged,
)
빈 상태에서 사용자가 무엇을 입력해야 하는지 알 수 있습니다.
Don't#
미래/과거 날짜를 선택 가능하게 두지 마세요 (해당되는 경우).
// Bad — 과거 날짜에 예약 가능
DatePicker(value: selectedDate, onChanged: handleDateChanged)
// Good — 미래 날짜만 선택 가능
DatePicker(
min: DateTime.now(),
value: selectedDate,
onChanged: handleDateChanged,
)
Do#
범위 선택에는 DateRangePicker를 사용하세요 (Flutter).
DateRangePicker(
value: DateTimeRange(checkIn, checkOut),
onChanged: (range) {
setState(() { checkIn = range?.start; checkOut = range?.end; });
},
)
시작/끝 날짜가 시각적으로 연결되어 사용자가 범위를 쉽게 이해합니다.
Don't#
두 개의 독립 DatePicker로 범위를 구현하지 마세요.
시작일이 끝일보다 뒤일 수 있는 오류가 발생합니다. DateRangePicker가 자동으로 순서를 보장합니다.
접근성 (Accessibility)#
역할#
| 요소 | Flutter | Web |
|---|---|---|
| 루트 | 없음 — date_picker.dart 전체에 Semantics 가 0건입니다 |
역할 없는
<div>
.
errorText
가 있으면
aria-invalid="true"
,
enabled: false
면
aria-disabled="true"
|
| 트리거 |
읽기 전용
TextField
— 포커스할 수 있고 누를 수 있지만 글자를 칠 수는 없습니다. 팝오버 모드에서는
Popover
가
Semantics(expanded: …)
를 추가합니다
|
읽기 전용
<input>
. 팝오버 모드에서는
Popover
wrapper
<div>
가
aria-haspopup="dialog"
/
aria-expanded
를 붙입니다
|
| 팝업 패널 | 역할 없음 | Popover bubble 에 role="dialog" |
| 다이얼로그 패널 | showDialog 모달 라우트 |
Dialog 패널에 role="dialog" + aria-modal="true" |
| 캘린더 |
Semantics(container: true, label: "Calendar")
+ 그리드에
role: .grid
/
.row
/
.gridCell
/
.columnHeader
|
aria-label="Calendar"
(역할 없음) + 그리드에
role="grid"
/
"row"
/
"gridcell"
/
"columnheader"
|
| 날짜 셀 | Button / Clickable |
Button
, 또는 직접 그린 셀은
role="button"
+
tabindex="0"
(비활성은
aria-disabled="true"
)
|
label / description / errorText |
형제 Text — 트리거와 시맨틱으로 연결되지 않습니다 |
형제 <div> — <label for> 도 aria-describedby 도 없습니다 |
키보드#
| 키 | 동작 |
|---|---|
Enter / Space | 트리거에서 캘린더 팝업/다이얼로그 열기, 날짜 셀에서 날짜 선택 |
Tab | 활성 날짜마다 정지점이 하나씩 생깁니다 (roving tabindex 미적용) |
Escape |
Web 만
팝업을 닫습니다. Flutter 는 팝오버 모드에서 동작하지 않고,
mode: CorePromptMode.dialog
일 때만
showDialog
라우트가 처리합니다
|
← → ↑ ↓ |
캘린더가 이해하는 날짜 이동은
없습니다
. Flutter 에서는 프레임워크 기본 단축키(
DirectionalFocusIntent
)로 포커스가 방향 이동하지만 달 경계를 넘지 못하고, Web 에는 화살표 핸들러가 아예 없어 아무 일도 일어나지 않습니다
|
Page Up / Page Down | 없음 — 월 이동은 헤더의 이전/다음 버튼으로 합니다 |
스크린 리더#
- 트리거에 현재 선택 날짜가 읽힙니다 (선택 전에는
placeholder). -
팝업 캘린더는
aria-label="Calendar"를 가진, 역할 없는 컨테이너 — 그 안의 날짜 그리드가role="grid"(Web) /role: .grid(Flutter) 로 읽힘.role="application"은 없음 — 자세한 표는 Calendar 접근성 참조. -
errorText존재 시 Web 루트에만aria-invalid="true"가 붙습니다. Flutter 에는 대응하는 시맨틱이 없어, 오류 문구는 트리거와 무관한 별개의 텍스트로만 읽힙니다. -
비활성 상태는 양쪽 다 전달됩니다. Web 은 루트의
aria-disabled="true"로, Flutter 는 트리거TextField가 붙이는Semantics(enabled: false)로 전달합니다. -
label/description은 트리거의 접근 가능한 이름·설명에 들어가지 않습니다(양 플랫폼). 둘 다 형제 텍스트 노드로만 그려집니다 — 리더는 읽기 순서상 옆에서 만나지만 트리거에 포커스했을 때는 함께 읽히지 않습니다. - 날짜 선택 결과를 알리는 라이브 리전은 없습니다.
포커스 관리#
-
진입 — 팝오버 모드에서는 포커스가 트리거에 그대로 남습니다. 캘린더에 autofocus 가 없고
Popover는 기본값(modal: false)으로 합성되기 때문입니다.mode: CorePromptMode.dialog에서는 FluttershowDialog라우트가 자기 포커스 스코프를 가져가고, Web 은 배경이inert가 되어 첫Tab이 패널 안으로 들어갑니다. -
이탈 — 팝오버가 닫힐 때 트리거로 포커스를 되돌리지 않습니다(양 플랫폼). 다이얼로그 모드의 Flutter
showDialog는 라우트가 pop 되며 이전 포커스를 복원합니다. -
트랩 — 팝오버 모드에는 없습니다. Web 패널은 문서 끝의 오버레이 레이어로 포탈되므로 트리거에서
Tab을 눌러도 패널로 바로 가지 않고 문서 순서를 계속 걸어갑니다. 다이얼로그 모드에서만 트랩이 생깁니다 — Web 은OverlayHost가 앱 서브트리에inert를 걸어Tab이 패널 안에서만 돌고, Flutter 는 모달 라우트가 포커스를 가둡니다.
알려진 제약#
- 캘린더 안에서 화살표 키나 PageUp/PageDown 으로 날짜를 이동할 수 없습니다. 날짜 셀이 처리하는 키는 선택(Space / Enter)뿐이고 방향키 처리는 양 플랫폼 어디에도 없습니다. 대신 활성 날짜마다 tab 정지점이 하나씩 생기므로(roving tabindex 미적용) 한 달을 지나가려면 Tab 을 서른 번 넘게 눌러야 합니다. 월 이동은 헤더의 이전/다음 버튼으로 하십시오.
-
이전/다음 달 이동 버튼에 이름이 없습니다(양 플랫폼). 아이콘만 든
Button이라 리더는 "버튼" 으로만 읽습니다. 이 버튼들은Calendar내부에서 생성되어 호출부가 라벨을 실을 진입점이 없습니다. -
Flutter 에는
aria-invalid에 대응하는 시맨틱이 없습니다.date_picker.dart전체에Semantics가 없어errorText가 있는 상태가 Flutter 리더에 전달되지 않습니다(오류 문구 자체는 형제Text로 그려집니다). 비활성 상태만 트리거TextField를 통해 전달됩니다. -
팝업이 열려도 포커스가 캘린더로 이동하지 않고, 닫힐 때 트리거로 되돌아오지도 않습니다.
Popover를 기본값(modal: false)으로 합성하고 캘린더에 autofocus 가 없기 때문입니다. Web 은 패널이 문서 끝의 오버레이 레이어로 포탈되므로 트리거에서 Tab 을 눌러도 패널에 바로 닿지 않습니다.mode: CorePromptMode.dialog로 두면 Flutter 는showDialog라우트가, Web 은OverlayHost의inert배경이 배경을 포커스 대상에서 제외합니다.
크로스 플랫폼 차이점 (Platform Differences)#
DatePicker는CoreDatePickerContract를 통해 핵심 API(placeholder,enabled,onChanged,min,max,label,description,errorText)를 통일합니다. 아래는 플랫폼 고유 차이점만 나열합니다.
| 항목 | Flutter | Web |
|---|---|---|
| 값 타입 | DateTime? (single) / DateTimeRange? (range) |
String? ISO 8601 (single) / ({String start, String end})? (range) |
| 표시 모드 | CorePromptMode (popover/dialog) |
CorePromptMode (popover/dialog) |
| 범위 선택 | DateRangePicker (Flutter) |
DateRangePicker (Web) — 동일 contract |
| 컨트롤러 | DatePickerController (외부 상태) | 내부 상태 관리 |
| 캘린더 UI | Calendar | Calendar (동일) |
| Popover 인프라 | Popover + _popoverController |
Popover + _popoverController (동일) |
| Dialog 인프라 | Dialog + showDialog |
Dialog (open boolean 제어) |
| 값 소유 | value를 호출부에서 갱신하는 DatePicker | 내부 값과 외부 value 변경을 동기화 |
| DOM 인프라 | key · 호출부 시맨틱 래퍼 |
id / classes / css / attributes / 이벤트 표면 |