-
-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: draft date fields and segments * feat: implement focus directionality * feat: single increments * feat: define which segments are incrementable * fix: focus order when clicking not editable part * feat: add temporals * fix: maintain stability of the various segments * feat: allow overriding locale config for ease of access * feat(perf): optimize date formatter instantiation * feat: basic calendar implementation * feat: rework the locale to hold the calendar data * feat: hook the calendar with the date field * fix: auto add the calendar system to the given locale * feat: extract the calendar as a prop on the date field for ease * feat: optimizations around timezone and calendar instantiation * feat: maintain the field's date value type * fix: disallow interaction with literal segments * feat: allow editing segment values with keyboard * fix: prevent editing non numeric parts * feat: auto focus the next segment if the current one is done * fix: ensure model is synced with temporal value * fix: re-resolve number parser after locale change * Revert "fix: re-resolve number parser after locale change" This reverts commit c7e114d. * fix: re-resolve number parser after locale change * feat: arrow key navigation of calendar * feat: more shortcuts * feat: next/previous month buttons and month/year label * feat: build a control btn util * fix: make it a prop factory instead * feat: implement max and min date validation * feat: implement placeholders * fix: types * fix: set date to undefined if a part is cleared * feat: implement date panels * fix: year panel label * feat: make the next/previous buttons adapt to current panel type * feat: make home/end, page up/down keys work with month and years panels * dev: basic conversion to internationalized * feat: use @internationalized/date instead * chore: testing bundle-size * chore: added changeset * test: added calendar tests * test: added tests for some date field utils * test: added tests for datetime segment * fix: timezone initialization when passing them to calendarProps * fix: validation state of the useDateTimeField * fix: validation should run whenever the date value changes * fix: touched state * fix: better numeric segment check and added keyboard hints * fix: types * feat: rename week days prop * fix: aria labels for the grid * feat: rename panel types and add allowed panel types option * test: fix broken tests * feat: added spinbutton role to the date segment * refactor: make the calendar API more standalone * refactor: promote the calendar composable to be a full field optionally * refactor: reduce memory footprint with min/max dates * fix: reset calendar cells to have zero time component * fix: properly clamp the date with segments * refactor: externalize picker logic * feat: added aria props to picker logic * fix: types and focused date logic * fix: focused date should match selected * fix: hook cells into the disabled context * feat: auto inject the calendar direction * fix: properly render days in their locale * fix: completely block click event if the cell is disabled * feat: disable the navigation if readonly * test: added test case for readonly and disabled * fix: direction support in segment groups and readonly/disabled support * fix: name should not be required * fix: switch to EG locale in example cause its my country
- Loading branch information
Showing
47 changed files
with
4,958 additions
and
76 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'@formwerk/core': minor | ||
--- | ||
|
||
feat: implement useDateTimeField |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './useControlButtonProps'; |
29 changes: 29 additions & 0 deletions
29
packages/core/src/helpers/useControlButtonProps/useControlButtonProps.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import { computed, shallowRef } from 'vue'; | ||
import { isButtonElement, withRefCapture } from '../../utils/common'; | ||
|
||
interface ControlButtonProps { | ||
[key: string]: unknown; | ||
disabled?: boolean; | ||
} | ||
|
||
export function useControlButtonProps(props: () => ControlButtonProps) { | ||
const buttonEl = shallowRef<HTMLElement>(); | ||
|
||
const buttonProps = computed(() => { | ||
const isBtn = isButtonElement(buttonEl.value); | ||
const { disabled, ...rest } = props(); | ||
|
||
return withRefCapture( | ||
{ | ||
type: isBtn ? ('button' as const) : undefined, | ||
role: isBtn ? undefined : 'button', | ||
[isBtn ? 'disabled' : 'aria-disabled']: disabled || undefined, | ||
tabindex: '-1', | ||
...rest, | ||
}, | ||
buttonEl, | ||
); | ||
}); | ||
|
||
return buttonProps; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
export function getCalendar(locale: Intl.Locale): string { | ||
if (locale.calendar) { | ||
return locale.calendar as string; | ||
} | ||
|
||
if ('calendars' in locale) { | ||
return (locale.calendars as string[])[0] as string; | ||
} | ||
|
||
return 'gregory'; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export function getTimeZone(locale: Intl.Locale) { | ||
return new Intl.DateTimeFormat(locale).resolvedOptions().timeZone; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import { isCallable, warn } from '../utils/common'; | ||
|
||
export interface WeekInfo { | ||
firstDay: number; | ||
weekend: number[]; | ||
} | ||
|
||
export function getWeekInfo(locale: Intl.Locale): WeekInfo { | ||
const fallbackInfo: WeekInfo = { firstDay: 7, weekend: [6, 7] }; | ||
|
||
try { | ||
if ('weekInfo' in locale) { | ||
return (locale.weekInfo as WeekInfo) || fallbackInfo; | ||
} | ||
|
||
if ('getWeekInfo' in locale && isCallable(locale.getWeekInfo)) { | ||
return (locale.getWeekInfo() as WeekInfo) || fallbackInfo; | ||
} | ||
|
||
throw new Error(`Cannot determine week info for locale ${locale}`); | ||
} catch { | ||
warn(`Cannot determine week info for locale ${locale}`); | ||
|
||
return fallbackInfo; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import { MaybeRefOrGetter, shallowRef, toValue, watch } from 'vue'; | ||
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, DateFormatter>(); | ||
|
||
function getFormatter(locale: string, options: Intl.DateTimeFormatOptions = {}) { | ||
const cacheKey = locale + JSON.stringify(options); | ||
let formatter = dateFormatterCache.get(cacheKey); | ||
if (!formatter) { | ||
formatter = new DateFormatter(locale, options); | ||
dateFormatterCache.set(cacheKey, formatter); | ||
} | ||
|
||
return formatter; | ||
} | ||
|
||
export function useDateFormatter( | ||
locale: MaybeRefOrGetter<string | undefined>, | ||
opts?: MaybeRefOrGetter<Intl.DateTimeFormatOptions | undefined>, | ||
) { | ||
const resolvedLocale = getUserLocale(); | ||
const formatter = shallowRef(getFormatter(toValue(locale) || resolvedLocale, toValue(opts))); | ||
|
||
watch( | ||
() => ({ | ||
locale: toValue(locale) || resolvedLocale, | ||
opts: toValue(opts), | ||
}), | ||
(config, oldConfig) => { | ||
if (!isEqual(config, oldConfig)) { | ||
formatter.value = getFormatter(config.locale, config.opts); | ||
} | ||
}, | ||
); | ||
|
||
return formatter; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,57 @@ | ||
import { computed } from 'vue'; | ||
import { computed, MaybeRefOrGetter, toValue } from 'vue'; | ||
import { getConfig } from '../config'; | ||
import { getDirection } from './getDirection'; | ||
import { getWeekInfo } from './getWeekInfo'; | ||
import { Maybe, Reactivify } from '../types'; | ||
import { Calendar, GregorianCalendar } from '@internationalized/date'; | ||
import { getTimeZone } from './getTimezone'; | ||
|
||
export type NumberLocaleExtension = `nu-${string}`; | ||
|
||
export interface LocaleExtension { | ||
number: Maybe<NumberLocaleExtension>; | ||
calendar: Maybe<Calendar>; | ||
timeZone: Maybe<string>; | ||
} | ||
|
||
/** | ||
* Composable that resolves the currently configured locale and direction. | ||
*/ | ||
export function useLocale() { | ||
const locale = computed(() => getConfig().locale); | ||
const direction = computed(() => getDirection(locale.value)); | ||
export function useLocale( | ||
localeCode?: MaybeRefOrGetter<Maybe<string>>, | ||
extensions: Partial<Reactivify<LocaleExtension>> = {}, | ||
) { | ||
const localeString = computed(() => { | ||
let code = toValue(localeCode) || getConfig().locale; | ||
const calExt = toValue(extensions.calendar); | ||
const numExt = toValue(extensions.number); | ||
|
||
// Add the base locale extension if it's not already present | ||
if (!code.includes('-u-') && (numExt || calExt)) { | ||
code += '-u-'; | ||
} | ||
|
||
// Add the number locale extension if it's not already present | ||
if (!code.includes('-nu-') && numExt) { | ||
code += `-nu-${numExt}`; | ||
} | ||
|
||
// Add the calendar locale extension if it's not already present | ||
if (!code.includes('-ca-') && calExt?.identifier) { | ||
code += `-ca-${calExt.identifier}`; | ||
} | ||
|
||
code = code.replaceAll('--', '-'); | ||
|
||
return code; | ||
}); | ||
|
||
const localeInstance = computed(() => new Intl.Locale(localeString.value)); | ||
const direction = computed(() => getDirection(localeInstance.value)); | ||
const weekInfo = computed(() => getWeekInfo(localeInstance.value)); | ||
const calendar = computed(() => toValue(extensions.calendar) ?? (new GregorianCalendar() as Calendar)); | ||
const timeZone = computed(() => toValue(extensions.timeZone) ?? getTimeZone(localeInstance.value)); | ||
const locale = computed(() => localeInstance.value.toString()); | ||
|
||
return { locale, direction }; | ||
return { locale, direction, weekInfo, calendar, timeZone }; | ||
} |
2 changes: 1 addition & 1 deletion
2
...re/src/i18n/useNumberParser/index.spec.ts → ...ges/core/src/i18n/useNumberParser.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
export * from './useCalendar'; | ||
export * from './types'; | ||
export * from './useCalendarCell'; | ||
export * from './useCalendarView'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import { WeekInfo } from '../i18n/getWeekInfo'; | ||
import { Ref } from 'vue'; | ||
import { Maybe } from '../types'; | ||
import type { ZonedDateTime, Calendar } from '@internationalized/date'; | ||
|
||
export interface CalendarDayCell { | ||
type: 'day'; | ||
value: ZonedDateTime; | ||
dayOfMonth: number; | ||
label: string; | ||
isToday: boolean; | ||
isOutsideMonth: boolean; | ||
selected: boolean; | ||
disabled: boolean; | ||
focused: boolean; | ||
} | ||
|
||
export interface CalendarMonthCell { | ||
type: 'month'; | ||
label: string; | ||
value: ZonedDateTime; | ||
monthOfYear: number; | ||
selected: boolean; | ||
disabled: boolean; | ||
focused: boolean; | ||
} | ||
|
||
export interface CalendarYearCell { | ||
type: 'year'; | ||
label: string; | ||
value: ZonedDateTime; | ||
year: number; | ||
selected: boolean; | ||
disabled: boolean; | ||
focused: boolean; | ||
} | ||
|
||
export type CalendarCellProps = CalendarDayCell | CalendarMonthCell | CalendarYearCell; | ||
|
||
export type CalendarViewType = 'weeks' | 'months' | 'years'; | ||
|
||
export interface CalendarContext { | ||
locale: Ref<string>; | ||
weekInfo: Ref<WeekInfo>; | ||
calendar: Ref<Calendar>; | ||
timeZone: Ref<string>; | ||
getSelectedDate: () => ZonedDateTime; | ||
getMinDate: () => Maybe<ZonedDateTime>; | ||
getMaxDate: () => Maybe<ZonedDateTime>; | ||
getFocusedDate: () => ZonedDateTime; | ||
setFocusedDate: (date: ZonedDateTime) => void; | ||
setDate: (date: ZonedDateTime, view?: CalendarViewType) => void; | ||
} |
Oops, something went wrong.