Skip to content

Commit

Permalink
fix: reset calendar cells to have zero time component
Browse files Browse the repository at this point in the history
  • Loading branch information
logaretm committed Feb 16, 2025
1 parent 5afbb65 commit cc569ca
Show file tree
Hide file tree
Showing 7 changed files with 96 additions and 29 deletions.
32 changes: 28 additions & 4 deletions packages/core/src/useCalendar/useCalendar.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -465,22 +465,46 @@ describe('useCalendar', () => {

// Test next button (next set of years)
await fireEvent.click(screen.getByText('Next'));
expect(screen.getByText(currentDate.add({ years: 9 }).set({ month: 1, day: 1 }).toString())).toBeInTheDocument();
expect(
screen.getByText(
currentDate
.add({ years: 9 })
.set({ month: 1, day: 1, hour: 0, minute: 0, second: 0, millisecond: 0 })
.toString(),
),
).toBeInTheDocument();

// Test previous button (previous set of years)
await fireEvent.click(screen.getByText('Previous'));
expect(screen.getByText(currentDate.add({ years: 8 }).set({ month: 1, day: 1 }).toString())).toBeInTheDocument();
expect(
screen.getByText(
currentDate
.add({ years: 8 })
.set({ month: 1, day: 1, hour: 0, minute: 0, second: 0, millisecond: 0 })
.toString(),
),
).toBeInTheDocument();

// Test multiple clicks
await fireEvent.click(screen.getByText('Previous'));
await fireEvent.click(screen.getByText('Previous'));
expect(
screen.getByText(currentDate.subtract({ years: 10 }).set({ month: 1, day: 1 }).toString()),
screen.getByText(
currentDate
.subtract({ years: 10 })
.set({ month: 1, day: 1, hour: 0, minute: 0, second: 0, millisecond: 0 })
.toString(),
),
).toBeInTheDocument();

await fireEvent.click(screen.getByText('Next'));
expect(
screen.getByText(currentDate.subtract({ years: 9 }).set({ month: 1, day: 1 }).toString()),
screen.getByText(
currentDate
.subtract({ years: 9 })
.set({ month: 1, day: 1, hour: 0, minute: 0, second: 0, millisecond: 0 })
.toString(),
),
).toBeInTheDocument();
});
});
Expand Down
5 changes: 2 additions & 3 deletions packages/core/src/useCalendar/useCalendar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import { createDisabledContext } from '../helpers/createDisabledContext';
import { exposeField, FormField, useFormField } from '../useFormField';
import { useInputValidity } from '../validation';
import { fromDateToCalendarZonedDateTime, useTemporalStore } from '../useDateTimeField/useTemporalStore';
import { isTemporalPartial } from '../useDateTimeField/temporalPartial';

export interface CalendarProps {
/**
Expand Down Expand Up @@ -178,8 +177,8 @@ export function useCalendar(_props: Reactivify<CalendarProps, 'field' | 'schema'
await nextTick();
focusCurrent();
},
getMinDate: () => (isTemporalPartial(min.value) ? undefined : min.value),
getMaxDate: () => (isTemporalPartial(max.value) ? undefined : max.value),
getMinDate: () => min.value,
getMaxDate: () => max.value,
};

provide(CalendarContextKey, context);
Expand Down
15 changes: 12 additions & 3 deletions packages/core/src/useCalendar/useCalendarView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ function useCalendarDaysView(
const days = computed<CalendarDayCell[]>(() => {
const current = getSelectedDate();
const focused = getFocusedDate();
const startOfMonth = focused.set({ day: 1 });
const startOfMonth = focused.set({ day: 1, hour: 0, minute: 0, second: 0, millisecond: 0 });

const firstDayOfWeek = weekInfo.value.firstDay;
const startDayOfWeek = startOfMonth.toDate().getDay();
Expand Down Expand Up @@ -176,7 +176,7 @@ function useCalendarMonthsView(
const maxDate = getMaxDate();

return Array.from({ length: focused.calendar.getMonthsInYear(focused) }, (_, i) => {
const date = focused.set({ month: i + 1, day: 1 });
const date = focused.set({ month: i + 1, day: 1, hour: 0, minute: 0, second: 0, millisecond: 0 });
let disabled = false;

if (minDate && minDate.month < date.month) {
Expand Down Expand Up @@ -218,7 +218,16 @@ function useCalendarYearsView(

return Array.from({ length: YEAR_CELLS_COUNT }, (_, i) => {
const startYear = Math.floor(focused.year / YEAR_CELLS_COUNT) * YEAR_CELLS_COUNT;
const date = focused.set({ year: startYear + i, month: 1, day: 1 });
const date = focused.set({
year: startYear + i,
month: 1,
day: 1,
hour: 0,
minute: 0,
second: 0,
millisecond: 0,
});

let disabled = false;

if (minDate && minDate.year < date.year) {
Expand Down
4 changes: 3 additions & 1 deletion packages/core/src/useDateTimeField/useDateTimeField.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { useDateTimeSegmentGroup } from './useDateTimeSegmentGroup';
import { FieldTypePrefixes } from '../constants';
import { useDateFormatter, useLocale } from '../i18n';
import { useErrorMessage, useLabel } from '../a11y';
import { useTemporalStore } from './useTemporalStore';
import { fromDateToCalendarZonedDateTime, useTemporalStore } from './useTemporalStore';
import { ZonedDateTime, Calendar } from '@internationalized/date';
import { useInputValidity } from '../validation';

Expand Down Expand Up @@ -130,6 +130,8 @@ export function useDateTimeField(_props: Reactivify<DateTimeFieldProps, 'schema'
temporalValue,
onValueChange,
onTouched: () => field.setTouched(true),
min: computed(() => fromDateToCalendarZonedDateTime(toValue(props.min), calendar.value, timeZone.value)),
max: computed(() => fromDateToCalendarZonedDateTime(toValue(props.max), calendar.value, timeZone.value)),
});

const { labelProps, labelledByProps } = useLabel({
Expand Down
49 changes: 37 additions & 12 deletions packages/core/src/useDateTimeField/useDateTimeSegmentGroup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ export interface DateTimeSegmentGroupProps {
temporalValue: MaybeRefOrGetter<ZonedDateTime | TemporalPartial>;
direction?: MaybeRefOrGetter<Direction>;
controlEl: Ref<HTMLElement | undefined>;
min?: MaybeRefOrGetter<Maybe<ZonedDateTime>>;
max?: MaybeRefOrGetter<Maybe<ZonedDateTime>>;
onValueChange: (value: ZonedDateTime) => void;
onTouched: () => void;
}
Expand All @@ -57,6 +59,8 @@ export function useDateTimeSegmentGroup({
direction,
locale,
controlEl,
min,
max,
onValueChange,
onTouched,
}: DateTimeSegmentGroupProps) {
Expand All @@ -68,6 +72,8 @@ export function useDateTimeSegmentGroup({

const { setPart, addToPart } = useDateArithmetic({
currentDate: temporalValue,
min,
max,
});

const segments = computed(() => {
Expand Down Expand Up @@ -315,9 +321,26 @@ export function useDateTimeSegmentGroup({

interface ArithmeticInit {
currentDate: MaybeRefOrGetter<ZonedDateTime | TemporalPartial>;
min?: MaybeRefOrGetter<Maybe<ZonedDateTime>>;
max?: MaybeRefOrGetter<Maybe<ZonedDateTime>>;
}

function useDateArithmetic({ currentDate }: ArithmeticInit) {
function useDateArithmetic({ currentDate, min, max }: ArithmeticInit) {
function clampDate(date: ZonedDateTime) {
const minDate = toValue(min);
const maxDate = toValue(max);

if (minDate && date.compare(minDate) < 0) {
return minDate;
}

if (maxDate && date.compare(maxDate) > 0) {
return maxDate;
}

return date;
}

function setPart(part: DateTimeSegmentType, value: number) {
const date = toValue(currentDate);
if (!isEditableSegmentType(part)) {
Expand All @@ -339,7 +362,7 @@ function useDateArithmetic({ currentDate }: ArithmeticInit) {
};
}

return newDate;
return clampDate(newDate);
}

function addToPart(part: DateTimeSegmentType, diff: number) {
Expand Down Expand Up @@ -377,23 +400,25 @@ function useDateArithmetic({ currentDate }: ArithmeticInit) {
[part]: true,
};

return newDate;
return clampDate(newDate);
}

// Preserves the day, month, and year when adding to the part so it doesn't overflow.
const day = date.day;
const month = date.month;
const year = date.year;

return date
.add({
[durationPart]: diff,
})
.set({
day: part !== 'day' && part !== 'weekday' ? day : undefined,
month: part !== 'month' ? month : undefined,
year: part !== 'year' ? year : undefined,
});
return clampDate(
date
.add({
[durationPart]: diff,
})
.set({
day: part !== 'day' && part !== 'weekday' ? day : undefined,
month: part !== 'month' ? month : undefined,
year: part !== 'year' ? year : undefined,
}),
);
}

return {
Expand Down
18 changes: 14 additions & 4 deletions packages/core/src/useDateTimeField/useTemporalStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,26 @@ interface TemporalValueStoreInit {

export function useTemporalStore(init: TemporalValueStoreInit) {
const model = init.model;
function normalizeNullish(value: Maybe<ZonedDateTime>): ZonedDateTime | TemporalPartial {
if (isNullOrUndefined(value)) {
return createTemporalPartial(toValue(init.calendar), toValue(init.timeZone));
}

return value;
}

const temporalVal = shallowRef<ZonedDateTime | TemporalPartial>(
fromDateToCalendarZonedDateTime(model.get(), toValue(init.calendar), toValue(init.timeZone)),
normalizeNullish(fromDateToCalendarZonedDateTime(model.get(), toValue(init.calendar), toValue(init.timeZone))),
);

watch(model.get, value => {
if (!value && isTemporalPartial(temporalVal.value)) {
return;
}

temporalVal.value = fromDateToCalendarZonedDateTime(value, toValue(init.calendar), toValue(init.timeZone));
temporalVal.value = normalizeNullish(
fromDateToCalendarZonedDateTime(value, toValue(init.calendar), toValue(init.timeZone)),
);
});

function toDate(value: Maybe<DateValue>): Maybe<Date> {
Expand Down Expand Up @@ -62,10 +72,10 @@ export function fromDateToCalendarZonedDateTime(
date: Maybe<Date>,
calendar: Calendar,
timeZone: string,
): ZonedDateTime {
): ZonedDateTime | null | undefined {
const zonedDt = toZonedDateTime(date, timeZone);
if (!zonedDt) {
return createTemporalPartial(calendar, timeZone);
return zonedDt;
}

return toCalendar(toTimeZone(zonedDt, timeZone), calendar);
Expand Down
2 changes: 0 additions & 2 deletions packages/playground/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,5 @@ const maxDate = new Date('2025-02-18');
second: '2-digit',
}"
/>

<Calendar name="date" label="select a date" :min="minDate" :max="maxDate" />
</div>
</template>

0 comments on commit cc569ca

Please sign in to comment.