diff --git a/packages/bbui/package.json b/packages/bbui/package.json index 1cba8f61..2bf0cc72 100644 --- a/packages/bbui/package.json +++ b/packages/bbui/package.json @@ -14,7 +14,8 @@ }, "scripts": { "build": "vite build", - "dev": "vite build --watch --mode=dev" + "dev": "vite build --watch --mode=dev", + "test": "vitest run" }, "devDependencies": { "@sveltejs/vite-plugin-svelte": "^6.1.3", @@ -75,7 +76,6 @@ "@spectrum-css/vars": "3.0.1", "@supertoolmake/shared-core": "*", "@supertoolmake/string-templates": "*", - "date-fns": "4.1.0", "dayjs": "1.10.8", "easymde": "2.16.1", "sanitize-html": "2.17.6", diff --git a/packages/bbui/src/Form/Core/DatePicker/utils.spec.ts b/packages/bbui/src/Form/Core/DatePicker/utils.spec.ts new file mode 100644 index 00000000..7eeb6b69 --- /dev/null +++ b/packages/bbui/src/Form/Core/DatePicker/utils.spec.ts @@ -0,0 +1,19 @@ +import { describe, expect, it } from "vitest" +import { getLocaleStartDayOfWeek } from "./utils" + +describe("getLocaleStartDayOfWeek", () => { + it("uses the locale's first day of the week", () => { + expect(getLocaleStartDayOfWeek(["en-US"])).toBe("Sunday") + expect(getLocaleStartDayOfWeek(["en-GB"])).toBe("Monday") + expect(getLocaleStartDayOfWeek(["ar-AF"])).toBe("Saturday") + }) + + it("normalizes locale separators", () => { + expect(getLocaleStartDayOfWeek(["en_US"])).toBe("Sunday") + }) + + it("falls back for missing or invalid locale metadata", () => { + expect(getLocaleStartDayOfWeek([])).toBe("Monday") + expect(getLocaleStartDayOfWeek(["not-a-locale"])).toBe("Monday") + }) +}) diff --git a/packages/bbui/src/Form/Core/DatePicker/utils.ts b/packages/bbui/src/Form/Core/DatePicker/utils.ts index 95f25728..6299b5af 100644 --- a/packages/bbui/src/Form/Core/DatePicker/utils.ts +++ b/packages/bbui/src/Form/Core/DatePicker/utils.ts @@ -1,12 +1,18 @@ -import type { Locale } from "date-fns" -import * as dateFnsLocales from "date-fns/locale" - interface Input { max: number pad: number fallback: string } +interface LocaleWeekInfo { + firstDay?: number +} + +interface LocaleWithWeekInfo { + readonly weekInfo?: LocaleWeekInfo + getWeekInfo?: () => LocaleWeekInfo +} + export type Weekday = | "Sunday" | "Monday" @@ -34,52 +40,6 @@ const normalizeLocaleCode = (code?: string | null) => { return code.toLowerCase().replace(/_/g, "-") } -const normalizeLocaleKey = (key?: string) => { - if (!key) { - return null - } - return key - .replace(/_/g, "-") - .replace(/([a-z])([A-Z])/g, "$1-$2") - .toLowerCase() -} - -const localeLookup = (() => { - const lookup = new Map() - - const register = (key: string, locale: Locale | undefined) => { - if (!locale) { - return - } - const codeCandidates = new Set() - const normalizedKey = normalizeLocaleKey(key) - if (normalizedKey) { - codeCandidates.add(normalizedKey) - } - const normalizedLocaleCode = normalizeLocaleCode(locale.code) - if (normalizedLocaleCode) { - codeCandidates.add(normalizedLocaleCode) - } - for (const candidate of codeCandidates) { - if (!lookup.has(candidate)) { - lookup.set(candidate, locale) - } - const base = candidate.split("-")[0] - if (base && !lookup.has(base)) { - lookup.set(base, locale) - } - } - } - - for (const [key, locale] of Object.entries(dateFnsLocales)) { - if (typeof locale === "object" && locale) { - register(key, locale as Locale) - } - } - - return lookup -})() - const getNavigatorLocales = (): readonly string[] => { if (typeof navigator === "undefined") { return [] @@ -90,6 +50,22 @@ const getNavigatorLocales = (): readonly string[] => { return navigator.language ? [navigator.language] : [] } +const getLocaleWeekInfo = (code: string): LocaleWeekInfo | undefined => { + if (typeof Intl.Locale !== "function") { + return undefined + } + + try { + if (Intl.DateTimeFormat.supportedLocalesOf([code]).length === 0) { + return undefined + } + const locale = new Intl.Locale(code) as Intl.Locale & LocaleWithWeekInfo + return locale.weekInfo ?? locale.getWeekInfo?.() + } catch { + return undefined + } +} + export const getLocaleStartDayOfWeek = ( locales: readonly string[] = getNavigatorLocales() ): Weekday => { @@ -98,11 +74,9 @@ export const getLocaleStartDayOfWeek = ( if (!normalized) { continue } - const match = localeLookup.get(normalized) || localeLookup.get(normalized.split("-")[0]) - const weekStartsOn = match?.options?.weekStartsOn - if (typeof weekStartsOn === "number") { - const index = ((weekStartsOn % 7) + 7) % 7 - return WEEKDAY_BY_INDEX[index] + const firstDay = getLocaleWeekInfo(normalized)?.firstDay + if (typeof firstDay === "number" && firstDay >= 1 && firstDay <= 7) { + return WEEKDAY_BY_INDEX[firstDay % 7] } } return DEFAULT_WEEKDAY diff --git a/yarn.lock b/yarn.lock index 865f44ba..d6d7bbc2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7607,11 +7607,6 @@ data-view-byte-offset@^1.0.1: es-errors "^1.3.0" is-data-view "^1.0.1" -date-fns@4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-4.1.0.tgz#64b3d83fff5aa80438f5b1a633c2e83b8a1c2d14" - integrity sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg== - dayjs@1.10.8: version "1.10.8" resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.8.tgz#267df4bc6276fcb33c04a6735287e3f429abec41"