Skip to content

Commit

Permalink
feat: maintain the field's date value type
Browse files Browse the repository at this point in the history
  • Loading branch information
logaretm committed Jan 26, 2025
1 parent 5efcb6d commit 539f8fc
Show file tree
Hide file tree
Showing 6 changed files with 182 additions and 146 deletions.
2 changes: 1 addition & 1 deletion packages/core/src/useCalendar/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export type CalendarIdentifier =
| 'roc';

export interface CalendarDay {
value: Temporal.PlainDate;
value: Temporal.ZonedDateTime;
dayOfMonth: number;
isToday: boolean;
isSelected: boolean;
Expand Down
20 changes: 10 additions & 10 deletions packages/core/src/useCalendar/useCalendar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ export interface CalendarProps {
/**
* The current date to use for the calendar.
*/
currentDate?: Temporal.PlainDate | Temporal.PlainDateTime;
currentDate?: Temporal.ZonedDateTime;

/**
* The callback to call when a day is selected.
*/
onDaySelected?: (day: Temporal.PlainDate) => void;
onDaySelected?: (day: Temporal.ZonedDateTime) => void;

/**
* The calendar type to use for the calendar, e.g. `gregory`, `islamic-umalqura`, etc.
Expand All @@ -32,8 +32,8 @@ interface CalendarContext {
locale: Ref<string>;
weekInfo: Ref<WeekInfo>;
calendar: Ref<CalendarIdentifier>;
currentDate: MaybeRefOrGetter<Temporal.PlainDate | Temporal.PlainDateTime>;
setDay: (date: Temporal.PlainDate) => void;
currentDate: MaybeRefOrGetter<Temporal.ZonedDateTime>;
setDay: (date: Temporal.ZonedDateTime) => void;
}

export const CalendarContextKey: InjectionKey<CalendarContext> = Symbol('CalendarContext');
Expand All @@ -44,14 +44,14 @@ export function useCalendar(_props: Reactivify<CalendarProps, 'onDaySelected'> =
calendar: () => toValue(props.calendar),
});

const currentDate = computed(() => toValue(props.currentDate) ?? Temporal.Now.plainDate(calendar.value));
const currentDate = computed(() => toValue(props.currentDate) ?? Temporal.Now.zonedDateTime(calendar.value));

const context: CalendarContext = {
weekInfo,
locale,
calendar,
currentDate,
setDay: (date: Temporal.PlainDate) => {
setDay: (date: Temporal.ZonedDateTime) => {
props.onDaySelected?.(date);
},
};
Expand Down Expand Up @@ -93,9 +93,9 @@ function useDaysOfTheWeek({ weekInfo, locale, currentDate }: CalendarContext) {
}[] = [];
for (let i = 0; i < daysPerWeek; i++) {
days.push({
long: longFormatter.value.format(current.add({ days: i })),
short: shortFormatter.value.format(current.add({ days: i })),
narrow: narrowFormatter.value.format(current.add({ days: i })),
long: longFormatter.value.format(current.add({ days: i }).toPlainDateTime()),
short: shortFormatter.value.format(current.add({ days: i }).toPlainDateTime()),
narrow: narrowFormatter.value.format(current.add({ days: i }).toPlainDateTime()),
});
}

Expand All @@ -122,7 +122,7 @@ function useCalendarDays({ weekInfo, currentDate, calendar }: CalendarContext) {
const totalDays = daysInMonth + daysToSubtract;
const remainingDays = 7 - (totalDays % 7);
const gridDays = totalDays + (remainingDays === 7 ? 0 : remainingDays);
const now = Temporal.Now.plainDate(calendar.value);
const now = Temporal.Now.zonedDateTime(calendar.value);

return Array.from({ length: gridDays }, (_, i) => {
const dayOfMonth = firstDay.add({ days: i });
Expand Down
111 changes: 24 additions & 87 deletions packages/core/src/useDateTimeField/useDateTimeField.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import { Maybe, Reactivify, StandardSchema } from '../types';
import { Reactivify, StandardSchema } from '../types';
import { CalendarIdentifier, CalendarProps } from '../useCalendar';
import { createDescribedByProps, isNullOrUndefined, normalizeProps, useUniqId, withRefCapture } from '../utils/common';
import { createDescribedByProps, normalizeProps, useUniqId, withRefCapture } from '../utils/common';
import { computed, shallowRef, toValue } from 'vue';
import { exposeField, useFormField } from '../useFormField';
import { useDateTimeSegmentGroup } from './useDateTimeSegmentGroup';
import { FieldTypePrefixes } from '../constants';
import { useDateFormatter, useLocale } from '../i18n';
import { useErrorMessage, useLabel } from '../a11y';
import { TemporalValue } from './types';
import { DateValue } from './types';
import { Temporal, toTemporalInstant } from '@js-temporal/polyfill';
import { useTemporalStore } from './useTemporalStore';
import { Temporal } from '@js-temporal/polyfill';

export interface DateTimeFieldProps {
/**
Expand Down Expand Up @@ -60,17 +59,17 @@ export interface DateTimeFieldProps {
/**
* The value to use for the field.
*/
value?: DateValue;
value?: Date;

/**
* The model value to use for the field.
*/
modelValue?: DateValue;
modelValue?: Date;

/**
* The schema to use for the field.
*/
schema?: StandardSchema<DateValue>;
schema?: StandardSchema<Date>;
}

export function useDateTimeField(_props: Reactivify<DateTimeFieldProps, 'schema'>) {
Expand All @@ -82,22 +81,30 @@ export function useDateTimeField(_props: Reactivify<DateTimeFieldProps, 'schema'

const formatter = useDateFormatter(locale, props.formatOptions);
const controlId = useUniqId(FieldTypePrefixes.DateTimeField);
const { dateValue, temporalValue } = useTemporalStore({
calendar: calendar,
timeZone: timeZone,
locale: locale,
initialValue: toValue(props.modelValue) ?? toValue(props.value) ?? new Date(),
});

const field = useFormField<DateValue>({
const field = useFormField<Date>({
path: props.name,
disabled: props.disabled,
initialValue: toValue(props.modelValue) ?? toValue(props.value) ?? new Date(),
initialValue: dateValue.value,
schema: props.schema,
});

const { fieldValue } = field;
function onValueChange(value: Temporal.ZonedDateTime) {
temporalValue.value = value;
field.setValue(dateValue.value);
}

const { segments } = useDateTimeSegmentGroup({
formatter,
controlEl,
calendar,
timeZone,
dateValue: () => normalizeDateValue(fieldValue.value, locale.value, timeZone.value, calendar.value),
onValueChange: field.setValue,
temporalValue,
onValueChange,
});

const { labelProps, labelledByProps } = useLabel({
Expand All @@ -118,19 +125,8 @@ export function useDateTimeField(_props: Reactivify<DateTimeFieldProps, 'schema'

const calendarProps: Reactivify<CalendarProps, 'onDaySelected'> = {
locale: () => locale.value,
currentDate: () =>
normalizeAsCalendarDate(fieldValue.value, locale.value, timeZone.value, calendar.value).toPlainDate(),
onDaySelected: day => {
const nextValue = normalizeAsCalendarDate(fieldValue.value, locale.value, timeZone.value, calendar.value);

field.setValue(
nextValue.with({
year: day.year,
month: day.month,
day: day.day,
}),
);
},
currentDate: temporalValue,
onDaySelected: onValueChange,
};

const controlProps = computed(() => {
Expand Down Expand Up @@ -159,62 +155,3 @@ export function useDateTimeField(_props: Reactivify<DateTimeFieldProps, 'schema'
field,
);
}

// TODO: VALUE NORMALIZER
function normalizeDateValue(
value: Maybe<DateValue>,
locale: string,
timeZone: string,
calendar: CalendarIdentifier,
): TemporalValue {
if (isNullOrUndefined(value)) {
return getNowAsTemporalValue(timeZone, calendar);
}

if (value instanceof Date) {
return toTemporalInstant.call(value).toZonedDateTime({
timeZone,
calendar,
});
}

return value;
}

function getNowAsTemporalValue(timeZone: string, calendar: CalendarIdentifier) {
return Temporal.Now.zonedDateTime(timeZone, calendar);
}

function normalizeAsCalendarDate(
value: Maybe<DateValue>,
locale: string,
timeZone: string,
calendar: CalendarIdentifier,
) {
if (isNullOrUndefined(value)) {
return getNowAsTemporalValue(timeZone, calendar).toPlainDateTime();
}

if (value instanceof Temporal.ZonedDateTime) {
return value.toPlainDateTime();
}

if (value instanceof Temporal.PlainDate) {
return value.toPlainDateTime();
}

if (value instanceof Temporal.PlainDateTime) {
return value;
}

if (value instanceof Date || value instanceof Temporal.Instant) {
const resolvedOptions = new Intl.DateTimeFormat(locale).resolvedOptions();

return (value instanceof Date ? toTemporalInstant.call(value) : value).toZonedDateTime({
timeZone: resolvedOptions.timeZone,
calendar: resolvedOptions.calendar,
});
}

throw new Error('Invalid calendar value used');
}
Loading

0 comments on commit 539f8fc

Please sign in to comment.