/** * 캘린더의 각 이벤트 항목을 정의합니다. */ export interface CalendarItem extends CalendarInterval { /** * 각 이벤트를 고유하게 정의하는 ID입니다. */ id: string; /** * 시작 시점 (inclusive) * from <= x */ from: Date; /** * 종료 시점 (non-inclusive) * x < to * from = to를 동일하게 지정하는 경우 "특정 시점" (instant)를 지정합니다. * @example 2025-01-01 23:59:59.999까지 시간을 지정하고 싶다면 to는 2025-01-02 자정 (00:00:00.000) 이 됩니다. * 즉, "1월 1일"을 선택하면 0시-24시를 선택하는 것이므로 from은 1월 1일 자정 ~ to는 1월 2일 자정이 선택됩니다. */ to: Date; } /** * 캘린더 UI가 어떤 항목을 어떻게 수정하고 있는 지에 대한 상태입니다. */ export interface CalendarCursor { /** * 수정중인 CalendarItem 항목입니다. 지정되지 않은 경우 각 UI 컴포넌트에서 필요시 새로운 항목을 생성합니다. */ item?: Partial | null; /** * item의 from/to 중 어떤 항목을 수정하고 있는 지를 지정합니다. 지정되지 않은 경우 "from"이 우선합니다. */ focus?: 'from' | 'to' | null; /** * 사용자가 from -> to와 같이 한 바퀴를 돌아서, 모든 from/to에 대해 수정할 기회를 얻었는지를 체크하는 변수입니다. */ toggled?: boolean; /** * 사용자가 마우스 호버를 하고 있는 from 시점입니다. 지정된 경우 DateInput이 해당 시점을 placeholder로 표시합니다. */ fromHover?: Date | null; /** * 사용자가 마우스 호버를 하고 있는 to 시점입니다. 지정된 경우 DateInput이 해당 시점을 placeholder로 표시합니다. */ toHover?: Date | null; /** * 사용자가 직접 모달을 연 것과 같이, 수정 작업에 유의미하게 참여하고 있는지 여부를 지정합니다. */ active?: boolean; /** * 사용자가 필드에 키보드로 직접 값을 입력하고 있는지 여부를 지정합니다. */ typing?: boolean; /** * 사용자가 필드에 키보드로 잘못된 값을 입력하고 있는지 여부를 지정합니다. */ typingInvalid?: boolean; } /** * from/to 시점 사이의 연속된 구간을 정의합니다. [from ... to) */ export interface CalendarInterval { /** * 시작 시점 (inclusive) * from <= x */ from: Date; /** * 종료 시점 (non-inclusive) * x < to */ to: Date; } /** * constraint를 실행하고 난 뒤에 반환되는 결과 값입니다. * 해당 시점에 대한 가용 여부와, 어느 시점까지 그 가용 여부가 유효한 지를 담고 있습니다. * 이를 사용하면, 제약 조건이 연속적인 경우 다시 계산하지 않아도 되고, 가용 여부가 바뀌는 시점도 간단하게 계산할 수 있으며, * 특정 시간 단위에 묶이지 않기 때문에 최적화와 범용성 모든 측면에서 이점이 있습니다. * * DatePicker의 다양한 컴포넌트들은 이를 활용해서 가용 여부나, 특정 일자에 대해 어느 시각에 정확히 가용이 되는지를 계산하며, * 아래와 같은 연속적인 구간을 즉시 계산할 수 있다는 점을 활용해서 boolean 로직 등 여러 제약 조건을 합치는 함수 또한 * 간단하게 구성할 수 있습니다. * * |----- 시각 -----> * * [----------- available -----------) * | | * fromOrTo until */ export interface CalendarConstraintHint { /** * constraint의 해당 시점에 대한 가용 여부를 정의합니다. */ available: boolean; /** * 해당 힌트가 마지막으로 유효한 시각을 정의합니다. * * non-inclusive하기 때문에 해당 시각 자체는 제약 조건의 범위에서 벗어났음을 유의해 주세요 (-1ms까지만 유효함) * null이 지정된 경우 무한히 유효하다는 것을 나타냅니다. */ until: Date | null; /** * 해당 시점이 비활성화된 경우, UI 상에서 비활성화가 아니라 숨겨야 하는지 여부를 지정합니다. */ hideInterval?: boolean; /** * 해당 시점이 비활성화된 사유를 지정합니다. 현재는 사용되지 않고 있지만, 추후 에러 처리가 필요한 경우 사용할 예정입니다. */ reason?: string; } /** * constraint 검증시 활용되는 해당 시각의 선택 맥락입니다. * 현재 어떤 아이템을 수정하고 있는지, 전체 목록에는 어떤 아이템들이 있는 지를 전달합니다. */ export interface CalendarConstraintContext { /** * 캘린더의 전체 목록입니다. * * 기존 항목을 수정하는 케이스에서는 current과 동일한 항목이 items에도 동시에 존재할 수 있습니다. * 이 경우 constraint쪽에서 current와 일치하는 items가 없는지 별도로 확인해야 합니다. */ items: CalendarItem[]; /** * 현재 수정중인 항목입니다. */ current?: Partial | null; /** * current의 from/to 중 어떤 항목을 수정하고 있는 지를 지정합니다. 지정되지 않은 경우 "from"이 우선합니다. */ focus?: 'from' | 'to' | null; } /** * 특정 시점 (시각)에 대한 캘린더의 제약 조건을 계산하는 함수입니다. */ export interface CalendarConstraint { (context: CalendarConstraintContext, fromOrTo: Date): CalendarConstraintHint; } /** * 각 캘린더의 "해상도", 즉 최소 분해 단위를 정의하는 객체입니다. * 가령, 사용자가 "2025-01-01"와 같은 특정 시점을 입력하면, 캘린더는 내부적으로 해당 값을 * 두 시점 (시작, 끝)을 나타내는 CalendarInterval로 변환해서 연산에 사용합니다. * UI마다 분해 단위가 크게 다르므로 (달력은 일 단위, TimeInput은 분 단위 등), 별도의 객체로 * 만들어서 관리하고 있습니다. */ export interface CalendarResolution { /** * date를 담고 있는 인터벌 단위를 반환합니다. */ resolve(date: Date): CalendarInterval; /** * date를 담고 있는 인터벌의 시작 시점을 반환합니다. */ start(date: Date): Date; /** * date를 담고 있는 인터벌의 종료 시점을 반환합니다. */ end(date: Date): Date; } /** * 캘린더 UI가 수정하고자 하는 항목을 지정합니다. * - 지정하지 않으면 range 입력으로 구성되어 시작일, 종료일을 차례대로 선택할 수 있습니다. * - "from"으로 지정하면 range 입력에서 시작일만 선택할 수 있고, 종료일은 비활성화됩니다. * - "to"로 지정하면 range 입력에서 종료일만 선택할 수 있고, 시작일은 비활성화됩니다. * - "all"로 지정하면 단일 날짜 선택으로 인식해서, 시작일/종료일을 모두 한 번에 선택합니다. */ export type CalendarInputTarget = 'from' | 'to' | 'all' | undefined; /** * 캘린더 UI들이 공통적으로 사용하는 prop을 지정합니다. * 각 UI 구성요소는 독립적으로 동작할 수 있게 구성되어 있어, 해당 prop들만 전달하면 cursor에 있는 캘린더 항목을 수정할 수 있습니다. */ export interface CalendarInputCommonProps { /** * 해당 캘린더 UI가 사용할 해상도를 지정합니다. */ resolution: CalendarResolution; /** * 캘린더 항목이 지켜야 하는 제약 조건을 설정합니다. */ constraint: CalendarConstraint; /** * UI가 갖고 있는 캘린더 항목 목록입니다. */ items: CalendarItem[]; /** * 현재 수정중인 캘린더 항목과, 내부 상태를 담고 있는 커서를 지정합니다. */ cursor: CalendarCursor | null; /** * 캘린더 UI가 수정하고자 하는 항목을 지정합니다. */ target?: CalendarInputTarget; disabled?: boolean; /** * 프리셋이나 props로 전달된 값에 대해서 validation을 진행하지 않도록 합니다. 대신, 사용자가 * 수정하는 순간 validation을 진행합니다. * 레거시 datepicker v1과 같은 동작을 지원하기 위해 제공하는 플래그입니다. 일반적인 상황에서는 * 사용하지 않는 것이 권장됩니다. * @deprecated */ tolerateInvalidValues?: boolean; /** * 커서가 수정되려고 할 때 실행되는 함수입니다. */ onCursorChange: (cursor: CalendarCursor | null) => void; /** * 커서의 수정을 마치고, 캘린더 항목에 다시 추가되려고 할 때 실행되는 함수입니다. */ onConfirm: (item: CalendarItem) => void; /** * 커서의 수정을 취소하려고 할 때 실행되는 함수입니다. */ onCancel?: () => void; } /** * DatePicker에서 사용하는 프리셋입니다. */ export interface CalendarPresetItem { label: string; interval: CalendarInterval; } /** * 캘린더가 달력에 보여줄 선택 단위를 지정합니다. * day의 경우 일반 캘린더가 표시되고, month/year/decade의 경우 특수한 캘린더가 표시됩니다. * hour, minute은 시간 선택 (TimeInput/TimeSideInput)에 사용됩니다. */ export type CalendarPeriodName = 'decade' | 'year' | 'month' | 'day' | 'hour' | 'minute'; /** * DatePicker가 사용할 최소 시간 단위를 지정합니다. */ export type DatePickerUnit = 'decade' | 'year' | 'month' | 'week' | 'day' | 'minute' | 'second';