Skip to content

Commit

Permalink
feat: make home/end, page up/down keys work with month and years panels
Browse files Browse the repository at this point in the history
  • Loading branch information
logaretm committed Feb 10, 2025
1 parent ef961db commit 37c55d7
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 34 deletions.
6 changes: 6 additions & 0 deletions packages/core/src/useCalendar/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,9 @@ import { InjectionKey } from 'vue';
import { CalendarContext } from './types';

export const CalendarContextKey: InjectionKey<CalendarContext> = Symbol('CalendarContext');

export const YEAR_CELLS_COUNT = 9;

export const MONTHS_COLUMNS_COUNT = 3;

export const YEARS_COLUMNS_COUNT = 3;
3 changes: 1 addition & 2 deletions packages/core/src/useCalendar/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Temporal } from '@js-temporal/polyfill';
import { MaybeRefOrGetter } from 'vue';
import { WeekInfo } from '../i18n/getWeekInfo';
import { Ref } from 'vue';
import { Maybe } from '../types';
Expand Down Expand Up @@ -64,7 +63,7 @@ export interface CalendarContext {
locale: Ref<string>;
weekInfo: Ref<WeekInfo>;
calendar: Ref<CalendarIdentifier>;
selectedDate: MaybeRefOrGetter<Temporal.ZonedDateTime>;
getSelectedDate: () => Temporal.ZonedDateTime;
getMinDate: () => Maybe<Temporal.ZonedDateTime>;
getMaxDate: () => Maybe<Temporal.ZonedDateTime>;
getFocusedDate: () => Temporal.ZonedDateTime;
Expand Down
87 changes: 64 additions & 23 deletions packages/core/src/useCalendar/useCalendar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { usePopoverController } from '../helpers/usePopoverController';
import { blockEvent } from '../utils/events';
import { useLabel } from '../a11y';
import { useControlButtonProps } from '../helpers/useControlButtonProps';
import { CalendarContextKey } from './constants';
import { CalendarContextKey, MONTHS_COLUMNS_COUNT, YEAR_CELLS_COUNT, YEARS_COLUMNS_COUNT } from './constants';
import { CalendarPanel, useCalendarPanel } from './useCalendarPanel';

export interface CalendarProps {
Expand Down Expand Up @@ -101,7 +101,7 @@ export function useCalendar(_props: Reactivify<CalendarProps, 'onDaySelected'> =
weekInfo,
locale,
calendar,
selectedDate,
getSelectedDate: () => selectedDate.value,
getFocusedDate: getFocusedOrSelected,
setDate: (date: Temporal.ZonedDateTime, panel?: CalendarPanelType) => {
props.onDaySelected?.(date);
Expand Down Expand Up @@ -256,13 +256,21 @@ export function useCalendar(_props: Reactivify<CalendarProps, 'onDaySelected'> =
});

const panelGridProps = computed(() => {
const panelType = currentPanel.value.type;
const columns =
panelType === 'day'
? currentPanel.value.daysOfTheWeek.length
: panelType === 'month'
? MONTHS_COLUMNS_COUNT
: YEARS_COLUMNS_COUNT;

return withRefCapture(
{
id: `${calendarId}-g`,
role: 'grid',
style: {
display: 'grid',
gridTemplateColumns: `repeat(${currentPanel.value.type === 'day' ? currentPanel.value.daysOfTheWeek.length : 3}, 1fr)`,
gridTemplateColumns: `repeat(${columns}, 1fr)`,
},
},
gridEl,
Expand Down Expand Up @@ -391,61 +399,94 @@ export function useCalendarKeyboard(context: CalendarContext, currentPanel: Ref<
},
PageDown: {
fn: () => {
if (currentPanel.value.type !== 'day') {
return undefined;
const type = currentPanel.value.type;
if (type === 'day') {
return context.getFocusedDate().add({ months: 1 });
}

return context.getFocusedDate().add({ months: 1 });
if (type === 'month') {
return context.getFocusedDate().add({ years: 1 });
}

return context.getFocusedDate().add({ years: YEAR_CELLS_COUNT });
},
type: 'focus',
},
PageUp: {
fn: () => {
if (currentPanel.value.type !== 'day') {
return undefined;
const type = currentPanel.value.type;
if (type === 'day') {
return context.getFocusedDate().subtract({ months: 1 });
}

return context.getFocusedDate().subtract({ months: 1 });
if (type === 'month') {
return context.getFocusedDate().subtract({ years: 1 });
}

return context.getFocusedDate().subtract({ years: YEAR_CELLS_COUNT });
},
type: 'focus',
},
Home: {
fn: () => {
if (currentPanel.value.type !== 'day') {
return undefined;
const current = context.getFocusedDate();
const type = currentPanel.value.type;
if (type === 'day') {
if (current.day === 1) {
return current.subtract({ months: 1 }).with({ day: 1 });
}

return current.with({ day: 1 });
}

const current = context.getFocusedDate();
if (current.day === 1) {
return current.subtract({ months: 1 }).with({ day: 1 });
if (type === 'month') {
if (current.month === 1) {
return current.subtract({ years: 1 }).with({ month: 1 });
}

return current.with({ month: 1 });
}

return current.with({ day: 1 });
return current.with({ year: current.year - YEAR_CELLS_COUNT });
},
type: 'focus',
},
End: {
type: 'focus',
fn: () => {
if (currentPanel.value.type !== 'day') {
return undefined;
const type = currentPanel.value.type;
const current = context.getFocusedDate();
if (type === 'day') {
if (current.day === current.daysInMonth) {
return current.add({ months: 1 }).with({ day: 1 });
}

return current.with({ day: current.daysInMonth });
}

const current = context.getFocusedDate();
if (current.day === current.daysInMonth) {
return current.add({ months: 1 }).with({ day: 1 });
if (type === 'month') {
if (current.month === current.monthsInYear) {
return current.add({ years: 1 }).with({ month: 1 });
}

return current.with({ month: current.monthsInYear });
}

const selected = context.getSelectedDate();
if (selected.year !== current.year) {
return selected.with({ year: current.year });
}

return current.with({ day: current.daysInMonth });
return current.with({ year: current.year + YEAR_CELLS_COUNT });
},
},
Escape: {
type: 'focus',
fn: () => {
const selected = toValue(context.selectedDate).toPlainDateTime();
const selected = context.getSelectedDate().toPlainDateTime();
const focused = context.getFocusedDate().toPlainDateTime();
if (!selected.equals(focused)) {
return Temporal.ZonedDateTime.from(toValue(context.selectedDate));
return context.getSelectedDate();
}

return undefined;
Expand Down
19 changes: 10 additions & 9 deletions packages/core/src/useCalendar/useCalendarPanel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useDateFormatter } from '../i18n';
import { Temporal } from '@js-temporal/polyfill';
import { Reactivify } from '../types';
import { normalizeProps } from '../utils/common';
import { YEAR_CELLS_COUNT } from './constants';

export interface CalendarDayPanel {
type: 'day';
Expand Down Expand Up @@ -64,7 +65,7 @@ export function useCalendarPanel(_props: Reactivify<CalendarPanelProps>, context

const panelLabel = computed(() => {
if (panelType.value === 'day') {
return monthFormatter.value.format(context.getFocusedDate().toPlainDateTime());
return `${monthFormatter.value.format(context.getFocusedDate().toPlainDateTime())} ${yearFormatter.value.format(context.getFocusedDate().toPlainDateTime())}`;
}

if (panelType.value === 'month') {
Expand All @@ -78,13 +79,13 @@ export function useCalendarPanel(_props: Reactivify<CalendarPanelProps>, context
}

function useCalendarDaysPanel(
{ weekInfo, getFocusedDate, calendar, selectedDate, locale, getMinDate, getMaxDate }: CalendarContext,
{ weekInfo, getFocusedDate, calendar, getSelectedDate, locale, getMinDate, getMaxDate }: CalendarContext,
daysOfWeekFormat?: MaybeRefOrGetter<Intl.DateTimeFormatOptions['weekday']>,
) {
const dayFormatter = useDateFormatter(locale, () => ({ weekday: toValue(daysOfWeekFormat) ?? 'short' }));

const days = computed<CalendarDayCell[]>(() => {
const current = toValue(selectedDate);
const current = getSelectedDate();
const focused = getFocusedDate();
const startOfMonth = focused.with({ day: 1 });

Expand Down Expand Up @@ -152,14 +153,14 @@ function useCalendarDaysPanel(
}

function useCalendarMonthsPanel(
{ getFocusedDate, locale, selectedDate, getMinDate, getMaxDate }: CalendarContext,
{ getFocusedDate, locale, getSelectedDate, getMinDate, getMaxDate }: CalendarContext,
monthFormat?: MaybeRefOrGetter<Intl.DateTimeFormatOptions['month']>,
) {
const monthFormatter = useDateFormatter(locale, () => ({ month: toValue(monthFormat) ?? 'long' }));

const months = computed<CalendarMonthCell[]>(() => {
const focused = getFocusedDate();
const current = toValue(selectedDate);
const current = getSelectedDate();
const minDate = getMinDate();
const maxDate = getMaxDate();

Expand Down Expand Up @@ -193,19 +194,19 @@ function useCalendarMonthsPanel(
}

function useCalendarYearsPanel(
{ getFocusedDate, locale, selectedDate, getMinDate, getMaxDate }: CalendarContext,
{ getFocusedDate, locale, getSelectedDate, getMinDate, getMaxDate }: CalendarContext,
yearFormat?: MaybeRefOrGetter<Intl.DateTimeFormatOptions['year']>,
) {
const yearFormatter = useDateFormatter(locale, () => ({ year: toValue(yearFormat) ?? 'numeric' }));

const years = computed<CalendarYearCell[]>(() => {
const focused = getFocusedDate();
const current = toValue(selectedDate);
const current = getSelectedDate();
const minDate = getMinDate();
const maxDate = getMaxDate();

return Array.from({ length: 9 }, (_, i) => {
const startYear = Math.floor(focused.year / 9) * 9;
return Array.from({ length: YEAR_CELLS_COUNT }, (_, i) => {
const startYear = Math.floor(focused.year / YEAR_CELLS_COUNT) * YEAR_CELLS_COUNT;
const date = focused.with({ year: startYear + i, month: 1, day: 1 });
let disabled = false;

Expand Down

0 comments on commit 37c55d7

Please sign in to comment.