Skip to content

Commit

Permalink
dev: basic conversion to internationalized
Browse files Browse the repository at this point in the history
  • Loading branch information
logaretm committed Feb 13, 2025
1 parent 37c55d7 commit aaf3d0a
Show file tree
Hide file tree
Showing 21 changed files with 145 additions and 192 deletions.
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
"dist/*.d.ts"
],
"dependencies": {
"@js-temporal/polyfill": "^0.4.4",
"@internationalized/date": "^3.7.0",
"@standard-schema/spec": "1.0.0",
"@standard-schema/utils": "^0.3.0",
"klona": "^2.0.6",
Expand Down
8 changes: 3 additions & 5 deletions packages/core/src/i18n/getCalendar.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import { CalendarIdentifier } from '../useCalendar';

export function getCalendar(locale: Intl.Locale): CalendarIdentifier {
export function getCalendar(locale: Intl.Locale): string {
if (locale.calendar) {
return locale.calendar as CalendarIdentifier;
return locale.calendar as string;
}

if ('calendars' in locale) {
return (locale.calendars as string[])[0] as CalendarIdentifier;
return (locale.calendars as string[])[0] as string;
}

return 'gregory';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import { MaybeRefOrGetter, shallowRef, toValue, watch } from 'vue';
import { Intl as TemporalIntl } from '@js-temporal/polyfill';
import { getUserLocale } from '../getUserLocale';
import { isEqual } from '../../utils/common';
import { DateFormatter } from '@internationalized/date';
import { getUserLocale } from './getUserLocale';
import { isEqual } from '../utils/common';

// TODO: May memory leak in SSR
const dateFormatterCache = new Map<string, TemporalIntl.DateTimeFormat>();
const dateFormatterCache = new Map<string, DateFormatter>();

function getFormatter(locale: string, options: Intl.DateTimeFormatOptions = {}) {
const cacheKey = locale + JSON.stringify(options);
console.log(cacheKey);
let formatter = dateFormatterCache.get(cacheKey);
if (!formatter) {
formatter = new TemporalIntl.DateTimeFormat(locale, options);
formatter = new DateFormatter(locale, options);
dateFormatterCache.set(cacheKey, formatter);
}

Expand Down
8 changes: 4 additions & 4 deletions packages/core/src/i18n/useLocale.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ import { getDirection } from './getDirection';
import { getWeekInfo } from './getWeekInfo';
import { Maybe, Reactivify } from '../types';
import { getCalendar } from './getCalendar';
import { CalendarIdentifier } from '../useCalendar';
import { Calendar } from '@internationalized/date';
import { getTimeZone } from './getTimezone';

export type NumberLocaleExtension = `nu-${string}`;

export interface LocaleExtension {
number: Maybe<NumberLocaleExtension>;
calendar: Maybe<CalendarIdentifier>;
calendar: Maybe<Calendar>;
}

/**
Expand All @@ -37,8 +37,8 @@ export function useLocale(
}

// Add the calendar locale extension if it's not already present
if (!code.includes('-ca-') && calExt) {
code += `-ca-${calExt}`;
if (!code.includes('-ca-') && calExt?.identifier) {
code += `-ca-${calExt.identifier}`;
}

code = code.replaceAll('--', '-');
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useNumberParser } from '.';
import { useNumberParser } from './useNumberParser';

const enNumber = 1234567890.12;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { MaybeRefOrGetter, toValue, watch } from 'vue';
import { getUserLocale } from '../getUserLocale';
import { getUserLocale } from './getUserLocale';

/**
* Stuff that are considered "literals" that's not part of the number itself and should be stripped out when parsing/validating.
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/useCalendar/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './useCalendar';
export * from './types';
export * from './useCalendarCell';
export * from './useCalendarPanel';
42 changes: 11 additions & 31 deletions packages/core/src/useCalendar/types.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,11 @@
import { Temporal } from '@js-temporal/polyfill';
import { WeekInfo } from '../i18n/getWeekInfo';
import { Ref } from 'vue';
import { Maybe } from '../types';

export type CalendarIdentifier =
| 'buddhist'
| 'chinese'
| 'coptic'
| 'dangi'
| 'ethioaa'
| 'ethiopic'
| 'gregory'
| 'hebrew'
| 'indian'
| 'islamic'
| 'islamic-umalqura'
| 'islamic-tbla'
| 'islamic-civil'
| 'islamic-rgsa'
| 'iso8601'
| 'japanese'
| 'persian'
| 'roc';
import type { ZonedDateTime, Calendar } from '@internationalized/date';

export interface CalendarDayCell {
type: 'day';
value: Temporal.ZonedDateTime;
value: ZonedDateTime;
dayOfMonth: number;
label: string;
isToday: boolean;
Expand All @@ -38,7 +18,7 @@ export interface CalendarDayCell {
export interface CalendarMonthCell {
type: 'month';
label: string;
value: Temporal.ZonedDateTime;
value: ZonedDateTime;
monthOfYear: number;
selected: boolean;
disabled: boolean;
Expand All @@ -48,7 +28,7 @@ export interface CalendarMonthCell {
export interface CalendarYearCell {
type: 'year';
label: string;
value: Temporal.ZonedDateTime;
value: ZonedDateTime;
year: number;
selected: boolean;
disabled: boolean;
Expand All @@ -62,11 +42,11 @@ export type CalendarPanelType = 'day' | 'month' | 'year';
export interface CalendarContext {
locale: Ref<string>;
weekInfo: Ref<WeekInfo>;
calendar: Ref<CalendarIdentifier>;
getSelectedDate: () => Temporal.ZonedDateTime;
getMinDate: () => Maybe<Temporal.ZonedDateTime>;
getMaxDate: () => Maybe<Temporal.ZonedDateTime>;
getFocusedDate: () => Temporal.ZonedDateTime;
setFocusedDate: (date: Temporal.ZonedDateTime) => void;
setDate: (date: Temporal.ZonedDateTime, panel?: CalendarPanelType) => void;
calendar: Ref<Calendar>;
getSelectedDate: () => ZonedDateTime;
getMinDate: () => Maybe<ZonedDateTime>;
getMaxDate: () => Maybe<ZonedDateTime>;
getFocusedDate: () => ZonedDateTime;
setFocusedDate: (date: ZonedDateTime) => void;
setDate: (date: ZonedDateTime, panel?: CalendarPanelType) => void;
}
68 changes: 32 additions & 36 deletions packages/core/src/useCalendar/useCalendar.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { computed, nextTick, provide, Ref, ref, shallowRef, toValue, watch } from 'vue';
import { Temporal } from '@js-temporal/polyfill';
import { CalendarContext, CalendarIdentifier, CalendarPanelType } from './types';
import { CalendarContext, CalendarPanelType } from './types';
import { hasKeyCode, normalizeProps, useUniqId, withRefCapture } from '../utils/common';
import { Maybe, Reactivify } from '../types';
import { useLocale } from '../i18n';
Expand All @@ -11,6 +10,7 @@ import { useLabel } from '../a11y';
import { useControlButtonProps } from '../helpers/useControlButtonProps';
import { CalendarContextKey, MONTHS_COLUMNS_COUNT, YEAR_CELLS_COUNT, YEARS_COLUMNS_COUNT } from './constants';
import { CalendarPanel, useCalendarPanel } from './useCalendarPanel';
import { Calendar, ZonedDateTime, fromDate, now } from '@internationalized/date';

export interface CalendarProps {
/**
Expand All @@ -21,17 +21,17 @@ export interface CalendarProps {
/**
* The current date to use for the calendar.
*/
currentDate?: Temporal.ZonedDateTime;
currentDate?: ZonedDateTime;

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

/**
* The calendar type to use for the calendar, e.g. `gregory`, `islamic-umalqura`, etc.
*/
calendar?: CalendarIdentifier;
calendar?: Calendar;

/**
* Whether the calendar is disabled.
Expand All @@ -51,12 +51,12 @@ export interface CalendarProps {
/**
* The minimum date to use for the calendar.
*/
minDate?: Maybe<Temporal.ZonedDateTime>;
minDate?: Maybe<ZonedDateTime>;

/**
* The maximum date to use for the calendar.
*/
maxDate?: Maybe<Temporal.ZonedDateTime>;
maxDate?: Maybe<ZonedDateTime>;

/**
* The format options for the days of the week.
Expand All @@ -81,12 +81,12 @@ export function useCalendar(_props: Reactivify<CalendarProps, 'onDaySelected'> =
const pickerEl = ref<HTMLElement>();
const gridEl = ref<HTMLElement>();
const calendarLabelEl = ref<HTMLElement>();
const { weekInfo, locale, calendar } = useLocale(props.locale, {
const { weekInfo, locale, calendar, timeZone } = useLocale(props.locale, {
calendar: () => toValue(props.calendar),
});

const selectedDate = computed(() => toValue(props.currentDate) ?? Temporal.Now.zonedDateTime(calendar.value));
const focusedDay = shallowRef<Temporal.ZonedDateTime>();
const selectedDate = computed(() => toValue(props.currentDate) ?? now(toValue(timeZone)));
const focusedDay = shallowRef<ZonedDateTime>();
const { isOpen } = usePopoverController(pickerEl, { disabled: props.disabled });

function getFocusedOrSelected() {
Expand All @@ -103,7 +103,7 @@ export function useCalendar(_props: Reactivify<CalendarProps, 'onDaySelected'> =
calendar,
getSelectedDate: () => selectedDate.value,
getFocusedDate: getFocusedOrSelected,
setDate: (date: Temporal.ZonedDateTime, panel?: CalendarPanelType) => {
setDate: (date: ZonedDateTime, panel?: CalendarPanelType) => {
props.onDaySelected?.(date);
if (panel) {
switchPanel(panel);
Expand All @@ -112,7 +112,7 @@ export function useCalendar(_props: Reactivify<CalendarProps, 'onDaySelected'> =
isOpen.value = false;
}
},
setFocusedDate: async (date: Temporal.ZonedDateTime) => {
setFocusedDate: async (date: ZonedDateTime) => {
focusedDay.value = date;
await nextTick();
focusCurrent();
Expand Down Expand Up @@ -175,7 +175,7 @@ export function useCalendar(_props: Reactivify<CalendarProps, 'onDaySelected'> =
}

if (!focusedDay.value) {
focusedDay.value = Temporal.ZonedDateTime.from(selectedDate.value);
focusedDay.value = fromDate(selectedDate.value.toDate(), selectedDate.value.timeZone);
}

await nextTick();
Expand Down Expand Up @@ -326,12 +326,12 @@ export function useCalendar(_props: Reactivify<CalendarProps, 'onDaySelected'> =
}

interface ShortcutDefinition {
fn: () => Temporal.ZonedDateTime | undefined;
fn: () => ZonedDateTime | undefined;
type: 'focus' | 'select';
}

export function useCalendarKeyboard(context: CalendarContext, currentPanel: Ref<CalendarPanel>) {
function withCheckedBounds(fn: () => Temporal.ZonedDateTime | undefined) {
function withCheckedBounds(fn: () => ZonedDateTime | undefined) {
const date = fn();
if (!date) {
return undefined;
Expand All @@ -340,11 +340,7 @@ export function useCalendarKeyboard(context: CalendarContext, currentPanel: Ref<
const minDate = context.getMinDate();
const maxDate = context.getMaxDate();

if (
date &&
((minDate && Temporal.ZonedDateTime.compare(date, minDate) < 0) ||
(maxDate && Temporal.ZonedDateTime.compare(date, maxDate) > 0))
) {
if (date && ((minDate && date.compare(minDate) < 0) || (maxDate && date.compare(maxDate) > 0))) {
return undefined;
}

Expand Down Expand Up @@ -433,21 +429,21 @@ export function useCalendarKeyboard(context: CalendarContext, currentPanel: Ref<
const type = currentPanel.value.type;
if (type === 'day') {
if (current.day === 1) {
return current.subtract({ months: 1 }).with({ day: 1 });
return current.subtract({ months: 1 }).set({ day: 1 });
}

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

if (type === 'month') {
if (current.month === 1) {
return current.subtract({ years: 1 }).with({ month: 1 });
return current.subtract({ years: 1 }).set({ month: 1 });
}

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

return current.with({ year: current.year - YEAR_CELLS_COUNT });
return current.set({ year: current.year - YEAR_CELLS_COUNT });
},
type: 'focus',
},
Expand All @@ -457,35 +453,35 @@ export function useCalendarKeyboard(context: CalendarContext, currentPanel: Ref<
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 });
if (current.day === current.calendar.getDaysInMonth(current)) {
return current.add({ months: 1 }).set({ day: 1 });
}

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

if (type === 'month') {
if (current.month === current.monthsInYear) {
return current.add({ years: 1 }).with({ month: 1 });
if (current.month === current.calendar.getMonthsInYear(current)) {
return current.add({ years: 1 }).set({ month: 1 });
}

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

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

return current.with({ year: current.year + YEAR_CELLS_COUNT });
return current.set({ year: current.year + YEAR_CELLS_COUNT });
},
},
Escape: {
type: 'focus',
fn: () => {
const selected = context.getSelectedDate().toPlainDateTime();
const focused = context.getFocusedDate().toPlainDateTime();
if (!selected.equals(focused)) {
const selected = context.getSelectedDate();
const focused = context.getFocusedDate();
if (selected.compare(focused) !== 0) {
return context.getSelectedDate();
}

Expand Down
Loading

0 comments on commit aaf3d0a

Please sign in to comment.